001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.dialogs;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.Color;
007    import java.awt.Component;
008    import java.awt.Dimension;
009    import java.awt.Font;
010    import java.awt.Point;
011    import java.awt.Rectangle;
012    import java.awt.event.ActionEvent;
013    import java.awt.event.InputEvent;
014    import java.awt.event.KeyEvent;
015    import java.awt.event.MouseEvent;
016    import java.beans.PropertyChangeEvent;
017    import java.beans.PropertyChangeListener;
018    import java.lang.ref.WeakReference;
019    import java.util.ArrayList;
020    import java.util.Arrays;
021    import java.util.Collections;
022    import java.util.List;
023    import java.util.concurrent.CopyOnWriteArrayList;
024    
025    import javax.swing.AbstractAction;
026    import javax.swing.Action;
027    import javax.swing.DefaultCellEditor;
028    import javax.swing.DefaultListSelectionModel;
029    import javax.swing.ImageIcon;
030    import javax.swing.JCheckBox;
031    import javax.swing.JComponent;
032    import javax.swing.JLabel;
033    import javax.swing.JMenuItem;
034    import javax.swing.JPopupMenu;
035    import javax.swing.JSlider;
036    import javax.swing.JTable;
037    import javax.swing.JTextField;
038    import javax.swing.JViewport;
039    import javax.swing.KeyStroke;
040    import javax.swing.ListSelectionModel;
041    import javax.swing.UIManager;
042    import javax.swing.event.ChangeEvent;
043    import javax.swing.event.ChangeListener;
044    import javax.swing.event.ListDataEvent;
045    import javax.swing.event.ListSelectionEvent;
046    import javax.swing.event.ListSelectionListener;
047    import javax.swing.event.TableModelEvent;
048    import javax.swing.event.TableModelListener;
049    import javax.swing.table.AbstractTableModel;
050    import javax.swing.table.DefaultTableCellRenderer;
051    import javax.swing.table.TableCellRenderer;
052    import javax.swing.table.TableModel;
053    
054    import org.openstreetmap.josm.Main;
055    import org.openstreetmap.josm.actions.MergeLayerAction;
056    import org.openstreetmap.josm.gui.MapFrame;
057    import org.openstreetmap.josm.gui.MapView;
058    import org.openstreetmap.josm.gui.SideButton;
059    import org.openstreetmap.josm.gui.help.HelpUtil;
060    import org.openstreetmap.josm.gui.io.SaveLayersDialog;
061    import org.openstreetmap.josm.gui.layer.JumpToMarkerActions;
062    import org.openstreetmap.josm.gui.layer.Layer;
063    import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
064    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
065    import org.openstreetmap.josm.gui.util.GuiHelper;
066    import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
067    import org.openstreetmap.josm.tools.CheckParameterUtil;
068    import org.openstreetmap.josm.tools.ImageProvider;
069    import org.openstreetmap.josm.tools.InputMapUtils;
070    import org.openstreetmap.josm.tools.MultikeyActionsHandler;
071    import org.openstreetmap.josm.tools.MultikeyShortcutAction;
072    import org.openstreetmap.josm.tools.MultikeyShortcutAction.MultikeyInfo;
073    import org.openstreetmap.josm.tools.Shortcut;
074    
075    /**
076     * This is a toggle dialog which displays the list of layers. Actions allow to
077     * change the ordering of the layers, to hide/show layers, to activate layers,
078     * and to delete layers.
079     *
080     */
081    public class LayerListDialog extends ToggleDialog {
082        /** the unique instance of the dialog */
083        static private LayerListDialog instance;
084    
085        /**
086         * Creates the instance of the dialog. It's connected to the map frame <code>mapFrame</code>
087         *
088         * @param mapFrame the map frame
089         */
090        static public void createInstance(MapFrame mapFrame) {
091            if (instance != null)
092                throw new IllegalStateException("Dialog was already created");
093            instance = new LayerListDialog(mapFrame);
094    
095        }
096    
097        /**
098         * Replies the instance of the dialog
099         *
100         * @return the instance of the dialog
101         * @throws IllegalStateException thrown, if the dialog is not created yet
102         * @see #createInstance(MapFrame)
103         */
104        static public LayerListDialog getInstance() throws IllegalStateException {
105            if (instance == null)
106                throw new IllegalStateException("Dialog not created yet. Invoke createInstance() first");
107            return instance;
108        }
109    
110        /** the model for the layer list */
111        private LayerListModel model;
112    
113        /** the selection model */
114        private DefaultListSelectionModel selectionModel;
115    
116        /** the list of layers (technically its a JTable, but appears like a list) */
117        private LayerList layerList;
118    
119        private SideButton opacityButton;
120    
121        ActivateLayerAction activateLayerAction;
122        ShowHideLayerAction showHideLayerAction;
123    
124        //TODO This duplicates ShowHide actions functionality
125        /** stores which layer index to toggle and executes the ShowHide action if the layer is present */
126        private final class ToggleLayerIndexVisibility extends AbstractAction {
127            int layerIndex = -1;
128            public ToggleLayerIndexVisibility(int layerIndex) {
129                this.layerIndex = layerIndex;
130            }
131            @Override
132            public void actionPerformed(ActionEvent e) {
133                final Layer l = model.getLayer(model.getRowCount() - layerIndex - 1);
134                if(l != null) {
135                    l.toggleVisible();
136                }
137            }
138        }
139    
140        private final Shortcut[] visibilityToggleShortcuts = new Shortcut[10];
141        private final ToggleLayerIndexVisibility[] visibilityToggleActions = new ToggleLayerIndexVisibility[10];
142        /**
143         * registers (shortcut to toggle right hand side toggle dialogs)+(number keys) shortcuts
144         * to toggle the visibility of the first ten layers.
145         */
146        private final void createVisibilityToggleShortcuts() {
147            final int[] k = { KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_3, KeyEvent.VK_4,
148                    KeyEvent.VK_5, KeyEvent.VK_6, KeyEvent.VK_7, KeyEvent.VK_8,
149                    KeyEvent.VK_9, KeyEvent.VK_0 };
150    
151            for(int i=0; i < 10; i++) {
152                visibilityToggleShortcuts[i] = Shortcut.registerShortcut("subwindow:layers:toggleLayer" + (i+1),
153                        tr("Toggle visibility of layer: {0}", (i+1)), k[i], Shortcut.ALT);
154                visibilityToggleActions[i] = new ToggleLayerIndexVisibility(i);
155                Main.registerActionShortcut(visibilityToggleActions[i], visibilityToggleShortcuts[i]);
156            }
157        }
158    
159        /**
160         * Create an layer list and attach it to the given mapView.
161         */
162        protected LayerListDialog(MapFrame mapFrame) {
163            super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."),
164                    Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L,
165                            Shortcut.ALT_SHIFT), 100, true);
166    
167            // create the models
168            //
169            selectionModel = new DefaultListSelectionModel();
170            selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
171            model = new LayerListModel(selectionModel);
172    
173            // create the list control
174            //
175            layerList = new LayerList(model);
176            layerList.setSelectionModel(selectionModel);
177            layerList.addMouseListener(new PopupMenuHandler());
178            layerList.setBackground(UIManager.getColor("Button.background"));
179            layerList.putClientProperty("terminateEditOnFocusLost", true);
180            layerList.putClientProperty("JTable.autoStartsEdit", false);
181            layerList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
182            layerList.setTableHeader(null);
183            layerList.setShowGrid(false);
184            layerList.setIntercellSpacing(new Dimension(0, 0));
185            layerList.getColumnModel().getColumn(0).setCellRenderer(new ActiveLayerCellRenderer());
186            layerList.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(new ActiveLayerCheckBox()));
187            layerList.getColumnModel().getColumn(0).setMaxWidth(12);
188            layerList.getColumnModel().getColumn(0).setPreferredWidth(12);
189            layerList.getColumnModel().getColumn(0).setResizable(false);
190            layerList.getColumnModel().getColumn(1).setCellRenderer(new LayerVisibleCellRenderer());
191            layerList.getColumnModel().getColumn(1).setCellEditor(new LayerVisibleCellEditor(new LayerVisibleCheckBox()));
192            layerList.getColumnModel().getColumn(1).setMaxWidth(16);
193            layerList.getColumnModel().getColumn(1).setPreferredWidth(16);
194            layerList.getColumnModel().getColumn(1).setResizable(false);
195            layerList.getColumnModel().getColumn(2).setCellRenderer(new LayerNameCellRenderer());
196            layerList.getColumnModel().getColumn(2).setCellEditor(new LayerNameCellEditor(new JTextField()));
197            for (KeyStroke ks : new KeyStroke[] {
198                    KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK),
199                    KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK),
200                    KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK),
201                    KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_MASK),
202                    KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK),
203                    KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK),
204                    KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0),
205                    KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0),
206            })
207            {
208                layerList.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ks, new Object());
209            }
210    
211            // init the model
212            //
213            final MapView mapView = mapFrame.mapView;
214            model.populate();
215            model.setSelectedLayer(mapView.getActiveLayer());
216            model.addLayerListModelListener(
217                    new LayerListModelListener() {
218                        @Override
219                        public void makeVisible(int row, Layer layer) {
220                            layerList.scrollToVisible(row, 0);
221                            layerList.repaint();
222                        }
223                        @Override
224                        public void refresh() {
225                            layerList.repaint();
226                        }
227                    }
228                    );
229    
230            // -- move up action
231            MoveUpAction moveUpAction = new MoveUpAction();
232            adaptTo(moveUpAction, model);
233            adaptTo(moveUpAction,selectionModel);
234    
235            // -- move down action
236            MoveDownAction moveDownAction = new MoveDownAction();
237            adaptTo(moveDownAction, model);
238            adaptTo(moveDownAction,selectionModel);
239    
240            // -- activate action
241            activateLayerAction = new ActivateLayerAction();
242            activateLayerAction.updateEnabledState();
243            MultikeyActionsHandler.getInstance().addAction(activateLayerAction);
244            adaptTo(activateLayerAction, selectionModel);
245    
246            JumpToMarkerActions.initialize();
247    
248            // -- show hide action
249            showHideLayerAction = new ShowHideLayerAction();
250            MultikeyActionsHandler.getInstance().addAction(showHideLayerAction);
251            adaptTo(showHideLayerAction, selectionModel);
252    
253            //-- layer opacity action
254            LayerOpacityAction layerOpacityAction = new LayerOpacityAction();
255            adaptTo(layerOpacityAction, selectionModel);
256            opacityButton = new SideButton(layerOpacityAction, false);
257    
258            // -- merge layer action
259            MergeAction mergeLayerAction = new MergeAction();
260            adaptTo(mergeLayerAction, model);
261            adaptTo(mergeLayerAction,selectionModel);
262    
263            // -- duplicate layer action
264            DuplicateAction duplicateLayerAction = new DuplicateAction();
265            adaptTo(duplicateLayerAction, model);
266            adaptTo(duplicateLayerAction, selectionModel);
267    
268            //-- delete layer action
269            DeleteLayerAction deleteLayerAction = new DeleteLayerAction();
270            layerList.getActionMap().put("deleteLayer", deleteLayerAction);
271            adaptTo(deleteLayerAction, selectionModel);
272            getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
273                    KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"delete"
274                    );
275            getActionMap().put("delete", deleteLayerAction);
276    
277            // Activate layer on Enter key press
278            InputMapUtils.addEnterAction(layerList, new AbstractAction() {
279                public void actionPerformed(ActionEvent e) {
280                    activateLayerAction.actionPerformed(null);
281                    layerList.requestFocus();
282                }
283            });
284    
285            // Show/Activate layer on Enter key press
286            InputMapUtils.addSpacebarAction(layerList, showHideLayerAction);
287    
288            createLayout(layerList, true, Arrays.asList(new SideButton[] {
289                    new SideButton(moveUpAction, false),
290                    new SideButton(moveDownAction, false),
291                    new SideButton(activateLayerAction, false),
292                    new SideButton(showHideLayerAction, false),
293                    opacityButton,
294                    new SideButton(mergeLayerAction, false),
295                    new SideButton(duplicateLayerAction, false),
296                    new SideButton(deleteLayerAction, false)
297            }));
298    
299            createVisibilityToggleShortcuts();
300        }
301    
302        @Override
303        public void showNotify() {
304            MapView.addLayerChangeListener(activateLayerAction);
305            MapView.addLayerChangeListener(model);
306            model.populate();
307        }
308    
309        @Override
310        public void hideNotify() {
311            MapView.removeLayerChangeListener(model);
312            MapView.removeLayerChangeListener(activateLayerAction);
313        }
314    
315        public LayerListModel getModel() {
316            return model;
317        }
318    
319        protected interface IEnabledStateUpdating {
320            void updateEnabledState();
321        }
322    
323        /**
324         * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that
325         * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()}
326         * on every {@link ListSelectionEvent}.
327         *
328         * @param listener  the listener
329         * @param listSelectionModel  the source emitting {@link ListSelectionEvent}s
330         */
331        protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) {
332            listSelectionModel.addListSelectionListener(
333                    new ListSelectionListener() {
334                        @Override
335                        public void valueChanged(ListSelectionEvent e) {
336                            listener.updateEnabledState();
337                        }
338                    }
339                    );
340        }
341    
342        /**
343         * Wires <code>listener</code> to <code>listModel</code> in such a way, that
344         * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()}
345         * on every {@link ListDataEvent}.
346         *
347         * @param listener  the listener
348         * @param listSelectionModel  the source emitting {@link ListDataEvent}s
349         */
350        protected void adaptTo(final IEnabledStateUpdating listener, LayerListModel listModel) {
351            listModel.addTableModelListener(
352                    new TableModelListener() {
353    
354                        @Override
355                        public void tableChanged(TableModelEvent e) {
356                            listener.updateEnabledState();
357                        }
358                    }
359                    );
360        }
361    
362        @Override
363        public void destroy() {
364            for(int i=0; i < 10; i++) {
365                Main.unregisterActionShortcut(visibilityToggleActions[i], visibilityToggleShortcuts[i]);
366            }
367            MultikeyActionsHandler.getInstance().removeAction(activateLayerAction);
368            MultikeyActionsHandler.getInstance().removeAction(showHideLayerAction);
369            JumpToMarkerActions.unregisterActions();
370            super.destroy();
371            instance = null;
372        }
373    
374        /**
375         * The action to delete the currently selected layer
376         */
377        public final class DeleteLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
378            /**
379             * Creates a {@link DeleteLayerAction} which will delete the currently
380             * selected layers in the layer dialog.
381             *
382             */
383            public DeleteLayerAction() {
384                putValue(SMALL_ICON,ImageProvider.get("dialogs", "delete"));
385                putValue(SHORT_DESCRIPTION, tr("Delete the selected layers."));
386                putValue(NAME, tr("Delete"));
387                putValue("help", HelpUtil.ht("/Dialog/LayerList#DeleteLayer"));
388                updateEnabledState();
389            }
390    
391            protected boolean enforceUploadOrSaveModifiedData(List<Layer> selectedLayers) {
392                SaveLayersDialog dialog = new SaveLayersDialog(Main.parent);
393                List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>();
394                for (Layer l: selectedLayers) {
395                    if (! (l instanceof OsmDataLayer)) {
396                        continue;
397                    }
398                    OsmDataLayer odl = (OsmDataLayer)l;
399                    if ((odl.requiresSaveToFile() || odl.requiresUploadToServer()) && odl.data.isModified()) {
400                        layersWithUnmodifiedChanges.add(odl);
401                    }
402                }
403                dialog.prepareForSavingAndUpdatingLayersBeforeDelete();
404                if (!layersWithUnmodifiedChanges.isEmpty()) {
405                    dialog.getModel().populate(layersWithUnmodifiedChanges);
406                    dialog.setVisible(true);
407                    switch(dialog.getUserAction()) {
408                    case CANCEL: return false;
409                    case PROCEED: return true;
410                    default: return false;
411                    }
412                }
413                return true;
414            }
415    
416            @Override
417            public void actionPerformed(ActionEvent e) {
418                List<Layer> selectedLayers = getModel().getSelectedLayers();
419                if (selectedLayers.isEmpty())
420                    return;
421                if (! enforceUploadOrSaveModifiedData(selectedLayers))
422                    return;
423                for(Layer l: selectedLayers) {
424                    Main.main.removeLayer(l);
425                }
426            }
427    
428            @Override
429            public void updateEnabledState() {
430                setEnabled(! getModel().getSelectedLayers().isEmpty());
431            }
432    
433            @Override
434            public Component createMenuComponent() {
435                return new JMenuItem(this);
436            }
437    
438            @Override
439            public boolean supportLayers(List<Layer> layers) {
440                return true;
441            }
442    
443            @Override
444            public boolean equals(Object obj) {
445                return obj instanceof DeleteLayerAction;
446            }
447    
448            @Override
449            public int hashCode() {
450                return getClass().hashCode();
451            }
452        }
453    
454        public final class ShowHideLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction, MultikeyShortcutAction {
455    
456            private WeakReference<Layer> lastLayer;
457            private Shortcut multikeyShortcut;
458    
459            /**
460             * Creates a {@link ShowHideLayerAction} which will toggle the visibility of
461             * the currently selected layers
462             *
463             */
464            public ShowHideLayerAction(boolean init) {
465                putValue(NAME, tr("Show/hide"));
466                putValue(SMALL_ICON, ImageProvider.get("dialogs", "showhide"));
467                putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the selected layer."));
468                putValue("help", HelpUtil.ht("/Dialog/LayerList#ShowHideLayer"));
469                multikeyShortcut = Shortcut.registerShortcut("core_multikey:showHideLayer", tr("Multikey: {0}",
470                        tr("Show/hide layer")), KeyEvent.VK_S, Shortcut.SHIFT);
471                multikeyShortcut.setAccelerator(this);
472                if (init) {
473                    updateEnabledState();
474                }
475            }
476    
477            public ShowHideLayerAction() {
478                this(true);
479            }
480    
481            @Override
482            public Shortcut getMultikeyShortcut() {
483                return multikeyShortcut;
484            }
485    
486            @Override
487            public void actionPerformed(ActionEvent e) {
488                for(Layer l : model.getSelectedLayers()) {
489                    l.toggleVisible();
490                }
491            }
492    
493            @Override
494            public void executeMultikeyAction(int index, boolean repeat) {
495                Layer l = LayerListDialog.getLayerForIndex(index);
496                if (l != null) {
497                    l.toggleVisible();
498                    lastLayer = new WeakReference<Layer>(l);
499                } else if (repeat && lastLayer != null) {
500                    l = lastLayer.get();
501                    if (LayerListDialog.isLayerValid(l)) {
502                        l.toggleVisible();
503                    }
504                }
505            }
506    
507            @Override
508            public void updateEnabledState() {
509                setEnabled(!model.getSelectedLayers().isEmpty());
510            }
511    
512            @Override
513            public Component createMenuComponent() {
514                return new JMenuItem(this);
515            }
516    
517            @Override
518            public boolean supportLayers(List<Layer> layers) {
519                return true;
520            }
521    
522            @Override
523            public boolean equals(Object obj) {
524                return obj instanceof ShowHideLayerAction;
525            }
526    
527            @Override
528            public int hashCode() {
529                return getClass().hashCode();
530            }
531    
532            @Override
533            public List<MultikeyInfo> getMultikeyCombinations() {
534                return LayerListDialog.getLayerInfoByClass(Layer.class);
535            }
536    
537            @Override
538            public MultikeyInfo getLastMultikeyAction() {
539                if (lastLayer != null)
540                    return LayerListDialog.getLayerInfo(lastLayer.get());
541                return null;
542            }
543        }
544    
545        public final class LayerOpacityAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
546            private Layer layer;
547            private JPopupMenu popup;
548            private JSlider slider = new JSlider(JSlider.VERTICAL);
549    
550            /**
551             * Creates a {@link LayerOpacityAction} which allows to chenge the
552             * opacity of one or more layers.
553             *
554             * @param layer  the layer. Must not be null.
555             * @exception IllegalArgumentException thrown, if layer is null
556             */
557            public LayerOpacityAction(Layer layer) throws IllegalArgumentException {
558                this();
559                putValue(NAME, tr("Opacity"));
560                CheckParameterUtil.ensureParameterNotNull(layer, "layer");
561                this.layer = layer;
562                updateEnabledState();
563            }
564    
565            /**
566             * Creates a {@link ShowHideLayerAction} which will toggle the visibility of
567             * the currently selected layers
568             *
569             */
570            public LayerOpacityAction() {
571                putValue(NAME, tr("Opacity"));
572                putValue(SHORT_DESCRIPTION, tr("Adjust opacity of the layer."));
573                putValue(SMALL_ICON, ImageProvider.get("dialogs/layerlist", "transparency"));
574                updateEnabledState();
575    
576                popup = new JPopupMenu();
577                slider.addChangeListener(new ChangeListener() {
578                    @Override
579                    public void stateChanged(ChangeEvent e) {
580                        setOpacity((double)slider.getValue()/100);
581                    }
582                });
583                popup.add(slider);
584            }
585    
586            private void setOpacity(double value) {
587                if (!isEnabled()) return;
588                if (layer != null) {
589                    layer.setOpacity(value);
590                } else {
591                    for(Layer layer: model.getSelectedLayers()) {
592                        layer.setOpacity(value);
593                    }
594                }
595            }
596    
597            private double getOpacity() {
598                if (layer != null)
599                    return layer.getOpacity();
600                else {
601                    double opacity = 0;
602                    List<Layer> layers = model.getSelectedLayers();
603                    for(Layer layer: layers) {
604                        opacity += layer.getOpacity();
605                    }
606                    return opacity / layers.size();
607                }
608            }
609    
610            @Override
611            public void actionPerformed(ActionEvent e) {
612                slider.setValue((int)Math.round(getOpacity()*100));
613                if (e.getSource() == opacityButton) {
614                    popup.show(opacityButton, 0, opacityButton.getHeight());
615                } else {
616                    // Action can be trigger either by opacity button or by popup menu (in case toggle buttons are hidden).
617                    // In that case, show it in the middle of screen (because opacityButton is not visible)
618                    popup.show(Main.parent, Main.parent.getWidth() / 2, (Main.parent.getHeight() - popup.getHeight()) / 2);
619                }
620            }
621    
622            @Override
623            public void updateEnabledState() {
624                if (layer == null) {
625                    setEnabled(! getModel().getSelectedLayers().isEmpty());
626                } else {
627                    setEnabled(true);
628                }
629            }
630    
631            @Override
632            public Component createMenuComponent() {
633                return new JMenuItem(this);
634            }
635    
636            @Override
637            public boolean supportLayers(List<Layer> layers) {
638                return true;
639            }
640    
641            @Override
642            public boolean equals(Object obj) {
643                return obj instanceof LayerOpacityAction;
644            }
645    
646            @Override
647            public int hashCode() {
648                return getClass().hashCode();
649            }
650        }
651    
652        /**
653         * The action to activate the currently selected layer
654         */
655    
656        public final class ActivateLayerAction extends AbstractAction implements IEnabledStateUpdating, MapView.LayerChangeListener, MultikeyShortcutAction{
657            private  Layer layer;
658            private Shortcut multikeyShortcut;
659    
660            public ActivateLayerAction(Layer layer) {
661                this();
662                CheckParameterUtil.ensureParameterNotNull(layer, "layer");
663                this.layer = layer;
664                putValue(NAME, tr("Activate"));
665                updateEnabledState();
666            }
667    
668            public ActivateLayerAction() {
669                putValue(NAME, tr("Activate"));
670                putValue(SMALL_ICON, ImageProvider.get("dialogs", "activate"));
671                putValue(SHORT_DESCRIPTION, tr("Activate the selected layer"));
672                multikeyShortcut = Shortcut.registerShortcut("core_multikey:activateLayer", tr("Multikey: {0}",
673                        tr("Activate layer")), KeyEvent.VK_A, Shortcut.SHIFT);
674                multikeyShortcut.setAccelerator(this);
675                putValue("help", HelpUtil.ht("/Dialog/LayerList#ActivateLayer"));
676            }
677    
678            @Override
679            public Shortcut getMultikeyShortcut() {
680                return multikeyShortcut;
681            }
682    
683            @Override
684            public void actionPerformed(ActionEvent e) {
685                Layer toActivate;
686                if (layer != null) {
687                    toActivate = layer;
688                } else {
689                    toActivate = model.getSelectedLayers().get(0);
690                }
691                execute(toActivate);
692            }
693    
694            private void execute(Layer layer) {
695                // model is  going to be updated via LayerChangeListener
696                // and PropertyChangeEvents
697                Main.map.mapView.setActiveLayer(layer);
698                layer.setVisible(true);
699            }
700    
701            protected boolean isActiveLayer(Layer layer) {
702                if (Main.map == null) return false;
703                if (Main.map.mapView == null) return false;
704                return Main.map.mapView.getActiveLayer() == layer;
705            }
706    
707            @Override
708            public void updateEnabledState() {
709                GuiHelper.runInEDTAndWait(new Runnable() {
710                    @Override
711                    public void run() {
712                        if (layer == null) {
713                            if (getModel().getSelectedLayers().size() != 1) {
714                                setEnabled(false);
715                                return;
716                            }
717                            Layer selectedLayer = getModel().getSelectedLayers().get(0);
718                            setEnabled(!isActiveLayer(selectedLayer));
719                        } else {
720                            setEnabled(!isActiveLayer(layer));
721                        }
722                    }
723                });
724            }
725    
726            @Override
727            public void activeLayerChange(Layer oldLayer, Layer newLayer) {
728                updateEnabledState();
729            }
730            @Override
731            public void layerAdded(Layer newLayer) {
732                updateEnabledState();
733            }
734            @Override
735            public void layerRemoved(Layer oldLayer) {
736                updateEnabledState();
737            }
738    
739            @Override
740            public void executeMultikeyAction(int index, boolean repeat) {
741                Layer l = LayerListDialog.getLayerForIndex(index);
742                if (l != null) {
743                    execute(l);
744                }
745            }
746    
747            @Override
748            public List<MultikeyInfo> getMultikeyCombinations() {
749                return LayerListDialog.getLayerInfoByClass(Layer.class);
750            }
751    
752            @Override
753            public MultikeyInfo getLastMultikeyAction() {
754                return null; // Repeating action doesn't make much sense for activating
755            }
756        }
757    
758        /**
759         * The action to merge the currently selected layer into another layer.
760         */
761        public final class MergeAction extends AbstractAction implements IEnabledStateUpdating {
762            private  Layer layer;
763    
764            public MergeAction(Layer layer) throws IllegalArgumentException {
765                this();
766                CheckParameterUtil.ensureParameterNotNull(layer, "layer");
767                this.layer = layer;
768                putValue(NAME, tr("Merge"));
769                updateEnabledState();
770            }
771    
772            public MergeAction() {
773                putValue(NAME, tr("Merge"));
774                putValue(SMALL_ICON, ImageProvider.get("dialogs", "mergedown"));
775                putValue(SHORT_DESCRIPTION, tr("Merge this layer into another layer"));
776                putValue("help", HelpUtil.ht("/Dialog/LayerList#MergeLayer"));
777                updateEnabledState();
778            }
779    
780            @Override
781            public void actionPerformed(ActionEvent e) {
782                if (layer != null) {
783                    new MergeLayerAction().merge(layer);
784                } else {
785                    if (getModel().getSelectedLayers().size() == 1) {
786                        Layer selectedLayer = getModel().getSelectedLayers().get(0);
787                        new MergeLayerAction().merge(selectedLayer);
788                    } else {
789                        new MergeLayerAction().merge(getModel().getSelectedLayers());
790                    }
791                }
792            }
793    
794            protected boolean isActiveLayer(Layer layer) {
795                if (Main.map == null) return false;
796                if (Main.map.mapView == null) return false;
797                return Main.map.mapView.getActiveLayer() == layer;
798            }
799    
800            @Override
801            public void updateEnabledState() {
802                if (layer == null) {
803                    if (getModel().getSelectedLayers().isEmpty()) {
804                        setEnabled(false);
805                    } else  if (getModel().getSelectedLayers().size() > 1) {
806                        Layer firstLayer = getModel().getSelectedLayers().get(0);
807                        for (Layer l: getModel().getSelectedLayers()) {
808                            if (l != firstLayer && (!l.isMergable(firstLayer) || !firstLayer.isMergable(l))) {
809                                setEnabled(false);
810                                return;
811                            }
812                        }
813                        setEnabled(true);
814                    } else {
815                        Layer selectedLayer = getModel().getSelectedLayers().get(0);
816                        List<Layer> targets = getModel().getPossibleMergeTargets(selectedLayer);
817                        setEnabled(!targets.isEmpty());
818                    }
819                } else {
820                    List<Layer> targets = getModel().getPossibleMergeTargets(layer);
821                    setEnabled(!targets.isEmpty());
822                }
823            }
824        }
825    
826        /**
827         * The action to merge the currently selected layer into another layer.
828         */
829        public final class DuplicateAction extends AbstractAction implements IEnabledStateUpdating {
830            private  Layer layer;
831    
832            public DuplicateAction(Layer layer) throws IllegalArgumentException {
833                this();
834                CheckParameterUtil.ensureParameterNotNull(layer, "layer");
835                this.layer = layer;
836                updateEnabledState();
837            }
838    
839            public DuplicateAction() {
840                putValue(NAME, tr("Duplicate"));
841                putValue(SMALL_ICON, ImageProvider.get("dialogs", "duplicatelayer"));
842                putValue(SHORT_DESCRIPTION, tr("Duplicate this layer"));
843                putValue("help", HelpUtil.ht("/Dialog/LayerList#DuplicateLayer"));
844                updateEnabledState();
845            }
846    
847            private void duplicate(Layer layer) {
848                if (Main.map == null || Main.map.mapView == null)
849                    return;
850    
851                List<String> layerNames = new ArrayList<String>();
852                for (Layer l: Main.map.mapView.getAllLayers()) {
853                    layerNames.add(l.getName());
854                }
855                if (layer instanceof OsmDataLayer) {
856                    OsmDataLayer oldLayer = (OsmDataLayer)layer;
857                    // Translators: "Copy of {layer name}"
858                    String newName = tr("Copy of {0}", oldLayer.getName());
859                    int i = 2;
860                    while (layerNames.contains(newName)) {
861                        // Translators: "Copy {number} of {layer name}"
862                        newName = tr("Copy {1} of {0}", oldLayer.getName(), i);
863                        i++;
864                    }
865                    Main.main.addLayer(new OsmDataLayer(oldLayer.data.clone(), newName, null));
866                }
867            }
868    
869            @Override
870            public void actionPerformed(ActionEvent e) {
871                if (layer != null) {
872                    duplicate(layer);
873                } else {
874                    duplicate(getModel().getSelectedLayers().get(0));
875                }
876            }
877    
878            protected boolean isActiveLayer(Layer layer) {
879                if (Main.map == null || Main.map.mapView == null)
880                    return false;
881                return Main.map.mapView.getActiveLayer() == layer;
882            }
883    
884            @Override
885            public void updateEnabledState() {
886                if (layer == null) {
887                    if (getModel().getSelectedLayers().size() == 1) {
888                        setEnabled(getModel().getSelectedLayers().get(0) instanceof OsmDataLayer);
889                    } else {
890                        setEnabled(false);
891                    }
892                } else {
893                    setEnabled(layer instanceof OsmDataLayer);
894                }
895            }
896        }
897    
898        private static class ActiveLayerCheckBox extends JCheckBox {
899            public ActiveLayerCheckBox() {
900                setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
901                ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank");
902                ImageIcon active = ImageProvider.get("dialogs/layerlist", "active");
903                setIcon(blank);
904                setSelectedIcon(active);
905                setRolloverIcon(blank);
906                setRolloverSelectedIcon(active);
907                setPressedIcon(ImageProvider.get("dialogs/layerlist", "active-pressed"));
908            }
909        }
910    
911        private static class LayerVisibleCheckBox extends JCheckBox {
912            private final ImageIcon icon_eye;
913            private final ImageIcon icon_eye_translucent;
914            private boolean isTranslucent;
915            public LayerVisibleCheckBox() {
916                setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
917                icon_eye = ImageProvider.get("dialogs/layerlist", "eye");
918                icon_eye_translucent = ImageProvider.get("dialogs/layerlist", "eye-translucent");
919                setIcon(ImageProvider.get("dialogs/layerlist", "eye-off"));
920                setPressedIcon(ImageProvider.get("dialogs/layerlist", "eye-pressed"));
921                setSelectedIcon(icon_eye);
922                isTranslucent = false;
923            }
924    
925            public void setTranslucent(boolean isTranslucent) {
926                if (this.isTranslucent == isTranslucent) return;
927                if (isTranslucent) {
928                    setSelectedIcon(icon_eye_translucent);
929                } else {
930                    setSelectedIcon(icon_eye);
931                }
932                this.isTranslucent = isTranslucent;
933            }
934    
935            public void updateStatus(Layer layer) {
936                boolean visible = layer.isVisible();
937                setSelected(visible);
938                setTranslucent(layer.getOpacity()<1.0);
939                setToolTipText(visible ? tr("layer is currently visible (click to hide layer)") : tr("layer is currently hidden (click to show layer)"));
940            }
941        }
942    
943        private static class ActiveLayerCellRenderer implements TableCellRenderer {
944            JCheckBox cb;
945            public ActiveLayerCellRenderer() {
946                cb = new ActiveLayerCheckBox();
947            }
948    
949            @Override
950            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
951                boolean active =  value != null && (Boolean) value;
952                cb.setSelected(active);
953                cb.setToolTipText(active ? tr("this layer is the active layer") : tr("this layer is not currently active (click to activate)"));
954                return cb;
955            }
956        }
957    
958        private static class LayerVisibleCellRenderer implements TableCellRenderer {
959            LayerVisibleCheckBox cb;
960            public LayerVisibleCellRenderer() {
961                this.cb = new LayerVisibleCheckBox();
962            }
963    
964            @Override
965            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
966                if (value != null) {
967                    cb.updateStatus((Layer)value);
968                }
969                return cb;
970            }
971        }
972    
973        private static class LayerVisibleCellEditor extends DefaultCellEditor {
974            LayerVisibleCheckBox cb;
975            public LayerVisibleCellEditor(LayerVisibleCheckBox cb) {
976                super(cb);
977                this.cb = cb;
978            }
979    
980            @Override
981            public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
982                cb.updateStatus((Layer)value);
983                return cb;
984            }
985        }
986    
987        private class LayerNameCellRenderer extends DefaultTableCellRenderer {
988    
989            protected boolean isActiveLayer(Layer layer) {
990                if (Main.map == null) return false;
991                if (Main.map.mapView == null) return false;
992                return Main.map.mapView.getActiveLayer() == layer;
993            }
994    
995            @Override
996            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
997                if (value == null)
998                    return this;
999                Layer layer = (Layer)value;
1000                JLabel label = (JLabel)super.getTableCellRendererComponent(table,
1001                        layer.getName(), isSelected, hasFocus, row, column);
1002                if (isActiveLayer(layer)) {
1003                    label.setFont(label.getFont().deriveFont(Font.BOLD));
1004                }
1005                if(Main.pref.getBoolean("dialog.layer.colorname", true)) {
1006                    Color c = layer.getColor(false);
1007                    if(c != null) {
1008                        Color oc = null;
1009                        for(Layer l : model.getLayers()) {
1010                            oc = l.getColor(false);
1011                            if(oc != null) {
1012                                if(oc.equals(c)) {
1013                                    oc = null;
1014                                } else {
1015                                    break;
1016                                }
1017                            }
1018                        }
1019                        /* not more than one color, don't use coloring */
1020                        if(oc == null) {
1021                            c = null;
1022                        }
1023                    }
1024                    if(c == null) {
1025                        c = Main.pref.getUIColor(isSelected ? "Table.selectionForeground" : "Table.foreground");
1026                    }
1027                    label.setForeground(c);
1028                }
1029                label.setIcon(layer.getIcon());
1030                label.setToolTipText(layer.getToolTipText());
1031                return label;
1032            }
1033        }
1034    
1035        private static class LayerNameCellEditor extends DefaultCellEditor {
1036            public LayerNameCellEditor(JTextField tf) {
1037                super(tf);
1038            }
1039    
1040            @Override
1041            public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
1042                JTextField tf = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
1043                tf.setText(value == null ? "" : ((Layer) value).getName());
1044                return tf;
1045            }
1046        }
1047    
1048        class PopupMenuHandler extends PopupMenuLauncher {
1049            @Override
1050            public void launch(MouseEvent evt) {
1051                Point p = evt.getPoint();
1052                int index = layerList.rowAtPoint(p);
1053                if (index < 0) return;
1054                if (!layerList.getCellRect(index, 2, false).contains(evt.getPoint()))
1055                    return;
1056                if (!layerList.isRowSelected(index)) {
1057                    layerList.setRowSelectionInterval(index, index);
1058                }
1059                Layer layer = model.getLayer(index);
1060                LayerListPopup menu = new LayerListPopup(getModel().getSelectedLayers(), layer);
1061                menu.show(layerList, p.x, p.y-3);
1062            }
1063        }
1064    
1065        /**
1066         * The action to move up the currently selected entries in the list.
1067         */
1068        class MoveUpAction extends AbstractAction implements  IEnabledStateUpdating{
1069            public MoveUpAction() {
1070                putValue(NAME, tr("Move up"));
1071                putValue(SMALL_ICON, ImageProvider.get("dialogs", "up"));
1072                putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row up."));
1073                updateEnabledState();
1074            }
1075    
1076            @Override
1077            public void updateEnabledState() {
1078                setEnabled(model.canMoveUp());
1079            }
1080    
1081            @Override
1082            public void actionPerformed(ActionEvent e) {
1083                model.moveUp();
1084            }
1085        }
1086    
1087        /**
1088         * The action to move down the currently selected entries in the list.
1089         */
1090        class MoveDownAction extends AbstractAction implements IEnabledStateUpdating {
1091            public MoveDownAction() {
1092                putValue(NAME, tr("Move down"));
1093                putValue(SMALL_ICON, ImageProvider.get("dialogs", "down"));
1094                putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row down."));
1095                updateEnabledState();
1096            }
1097    
1098            @Override
1099            public void updateEnabledState() {
1100                setEnabled(model.canMoveDown());
1101            }
1102    
1103            @Override
1104            public void actionPerformed(ActionEvent e) {
1105                model.moveDown();
1106            }
1107        }
1108    
1109        /**
1110         * Observer interface to be implemented by views using {@link LayerListModel}
1111         *
1112         */
1113        public interface LayerListModelListener {
1114            public void makeVisible(int index, Layer layer);
1115            public void refresh();
1116        }
1117    
1118        /**
1119         * The layer list model. The model manages a list of layers and provides methods for
1120         * moving layers up and down, for toggling their visibility, and for activating a layer.
1121         *
1122         * The model is a {@link TableModel} and it provides a {@link ListSelectionModel}. It expects
1123         * to be configured with a {@link DefaultListSelectionModel}. The selection model is used
1124         * to update the selection state of views depending on messages sent to the model.
1125         *
1126         * The model manages a list of {@link LayerListModelListener} which are mainly notified if
1127         * the model requires views to make a specific list entry visible.
1128         *
1129         * It also listens to {@link PropertyChangeEvent}s of every {@link Layer} it manages, in particular to
1130         * the properties {@link Layer#VISIBLE_PROP} and {@link Layer#NAME_PROP}.
1131         */
1132        public class LayerListModel extends AbstractTableModel implements MapView.LayerChangeListener, PropertyChangeListener {
1133            /** manages list selection state*/
1134            private DefaultListSelectionModel selectionModel;
1135            private CopyOnWriteArrayList<LayerListModelListener> listeners;
1136    
1137            /**
1138             * constructor
1139             *
1140             * @param selectionModel the list selection model
1141             */
1142            private LayerListModel(DefaultListSelectionModel selectionModel) {
1143                this.selectionModel = selectionModel;
1144                listeners = new CopyOnWriteArrayList<LayerListModelListener>();
1145            }
1146    
1147            /**
1148             * Adds a listener to this model
1149             *
1150             * @param listener the listener
1151             */
1152            public void addLayerListModelListener(LayerListModelListener listener) {
1153                if (listener != null) {
1154                    listeners.addIfAbsent(listener);
1155                }
1156            }
1157    
1158            /**
1159             * removes a listener from  this model
1160             * @param listener the listener
1161             *
1162             */
1163            public void removeLayerListModelListener(LayerListModelListener listener) {
1164                listeners.remove(listener);
1165            }
1166    
1167            /**
1168             * Fires a make visible event to listeners
1169             *
1170             * @param index the index of the row to make visible
1171             * @param layer the layer at this index
1172             * @see LayerListModelListener#makeVisible(int, Layer)
1173             */
1174            protected void fireMakeVisible(int index, Layer layer) {
1175                for (LayerListModelListener listener : listeners) {
1176                    listener.makeVisible(index, layer);
1177                }
1178            }
1179    
1180            /**
1181             * Fires a refresh event to listeners of this model
1182             *
1183             * @see LayerListModelListener#refresh()
1184             */
1185            protected void fireRefresh() {
1186                for (LayerListModelListener listener : listeners) {
1187                    listener.refresh();
1188                }
1189            }
1190    
1191            /**
1192             * Populates the model with the current layers managed by
1193             * {@link MapView}.
1194             *
1195             */
1196            public void populate() {
1197                for (Layer layer: getLayers()) {
1198                    // make sure the model is registered exactly once
1199                    //
1200                    layer.removePropertyChangeListener(this);
1201                    layer.addPropertyChangeListener(this);
1202                }
1203                fireTableDataChanged();
1204            }
1205    
1206            /**
1207             * Marks <code>layer</code> as selected layer. Ignored, if
1208             * layer is null.
1209             *
1210             * @param layer the layer.
1211             */
1212            public void setSelectedLayer(Layer layer) {
1213                if (layer == null)
1214                    return;
1215                int idx = getLayers().indexOf(layer);
1216                if (idx >= 0) {
1217                    selectionModel.setSelectionInterval(idx, idx);
1218                }
1219                ensureSelectedIsVisible();
1220            }
1221    
1222            /**
1223             * Replies the list of currently selected layers. Never null, but may
1224             * be empty.
1225             *
1226             * @return the list of currently selected layers. Never null, but may
1227             * be empty.
1228             */
1229            public List<Layer> getSelectedLayers() {
1230                ArrayList<Layer> selected = new ArrayList<Layer>();
1231                for (int i=0; i<getLayers().size(); i++) {
1232                    if (selectionModel.isSelectedIndex(i)) {
1233                        selected.add(getLayers().get(i));
1234                    }
1235                }
1236                return selected;
1237            }
1238    
1239            /**
1240             * Replies a the list of indices of the selected rows. Never null,
1241             * but may be empty.
1242             *
1243             * @return  the list of indices of the selected rows. Never null,
1244             * but may be empty.
1245             */
1246            public List<Integer> getSelectedRows() {
1247                ArrayList<Integer> selected = new ArrayList<Integer>();
1248                for (int i=0; i<getLayers().size();i++) {
1249                    if (selectionModel.isSelectedIndex(i)) {
1250                        selected.add(i);
1251                    }
1252                }
1253                return selected;
1254            }
1255    
1256            /**
1257             * Invoked if a layer managed by {@link MapView} is removed
1258             *
1259             * @param layer the layer which is removed
1260             */
1261            protected void onRemoveLayer(Layer layer) {
1262                if (layer == null)
1263                    return;
1264                layer.removePropertyChangeListener(this);
1265                final int size = getRowCount();
1266                final List<Integer> rows = getSelectedRows();
1267                GuiHelper.runInEDTAndWait(new Runnable() {
1268                    @Override
1269                    public void run() {
1270                        if (rows.isEmpty() && size > 0) {
1271                            selectionModel.setSelectionInterval(size-1, size-1);
1272                        }
1273                        fireTableDataChanged();
1274                        fireRefresh();
1275                        ensureActiveSelected();
1276                    }
1277                });
1278            }
1279    
1280            /**
1281             * Invoked when a layer managed by {@link MapView} is added
1282             *
1283             * @param layer the layer
1284             */
1285            protected void onAddLayer(Layer layer) {
1286                if (layer == null) return;
1287                layer.addPropertyChangeListener(this);
1288                fireTableDataChanged();
1289                int idx = getLayers().indexOf(layer);
1290                layerList.setRowHeight(idx, Math.max(16, layer.getIcon().getIconHeight()));
1291                selectionModel.setSelectionInterval(idx, idx);
1292                ensureSelectedIsVisible();
1293            }
1294    
1295            /**
1296             * Replies the first layer. Null if no layers are present
1297             *
1298             * @return the first layer. Null if no layers are present
1299             */
1300            public Layer getFirstLayer() {
1301                if (getRowCount() == 0) return null;
1302                return getLayers().get(0);
1303            }
1304    
1305            /**
1306             * Replies the layer at position <code>index</code>
1307             *
1308             * @param index the index
1309             * @return the layer at position <code>index</code>. Null,
1310             * if index is out of range.
1311             */
1312            public Layer getLayer(int index) {
1313                if (index < 0 || index >= getRowCount())
1314                    return null;
1315                return getLayers().get(index);
1316            }
1317    
1318            /**
1319             * Replies true if the currently selected layers can move up
1320             * by one position
1321             *
1322             * @return true if the currently selected layers can move up
1323             * by one position
1324             */
1325            public boolean canMoveUp() {
1326                List<Integer> sel = getSelectedRows();
1327                return !sel.isEmpty() && sel.get(0) > 0;
1328            }
1329    
1330            /**
1331             * Move up the currently selected layers by one position
1332             *
1333             */
1334            public void moveUp() {
1335                if (!canMoveUp()) return;
1336                List<Integer> sel = getSelectedRows();
1337                for (int row : sel) {
1338                    Layer l1 = getLayers().get(row);
1339                    Layer l2 = getLayers().get(row-1);
1340                    Main.map.mapView.moveLayer(l2,row);
1341                    Main.map.mapView.moveLayer(l1, row-1);
1342                }
1343                fireTableDataChanged();
1344                selectionModel.clearSelection();
1345                for (int row : sel) {
1346                    selectionModel.addSelectionInterval(row-1, row-1);
1347                }
1348                ensureSelectedIsVisible();
1349            }
1350    
1351            /**
1352             * Replies true if the currently selected layers can move down
1353             * by one position
1354             *
1355             * @return true if the currently selected layers can move down
1356             * by one position
1357             */
1358            public boolean canMoveDown() {
1359                List<Integer> sel = getSelectedRows();
1360                return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1;
1361            }
1362    
1363            /**
1364             * Move down the currently selected layers by one position
1365             *
1366             */
1367            public void moveDown() {
1368                if (!canMoveDown()) return;
1369                List<Integer> sel = getSelectedRows();
1370                Collections.reverse(sel);
1371                for (int row : sel) {
1372                    Layer l1 = getLayers().get(row);
1373                    Layer l2 = getLayers().get(row+1);
1374                    Main.map.mapView.moveLayer(l1, row+1);
1375                    Main.map.mapView.moveLayer(l2, row);
1376                }
1377                fireTableDataChanged();
1378                selectionModel.clearSelection();
1379                for (int row : sel) {
1380                    selectionModel.addSelectionInterval(row+1, row+1);
1381                }
1382                ensureSelectedIsVisible();
1383            }
1384    
1385            /**
1386             * Make sure the first of the selected layers is visible in the
1387             * views of this model.
1388             *
1389             */
1390            protected void ensureSelectedIsVisible() {
1391                int index = selectionModel.getMinSelectionIndex();
1392                if (index < 0) return;
1393                if (index >= getLayers().size()) return;
1394                Layer layer = getLayers().get(index);
1395                fireMakeVisible(index, layer);
1396            }
1397    
1398            /**
1399             * Replies a list of layers which are possible merge targets
1400             * for <code>source</code>
1401             *
1402             * @param source the source layer
1403             * @return a list of layers which are possible merge targets
1404             * for <code>source</code>. Never null, but can be empty.
1405             */
1406            public List<Layer> getPossibleMergeTargets(Layer source) {
1407                ArrayList<Layer> targets = new ArrayList<Layer>();
1408                if (source == null)
1409                    return targets;
1410                for (Layer target : getLayers()) {
1411                    if (source == target) {
1412                        continue;
1413                    }
1414                    if (target.isMergable(source) && source.isMergable(target)) {
1415                        targets.add(target);
1416                    }
1417                }
1418                return targets;
1419            }
1420    
1421            /**
1422             * Replies the list of layers currently managed by {@link MapView}.
1423             * Never null, but can be empty.
1424             *
1425             * @return the list of layers currently managed by {@link MapView}.
1426             * Never null, but can be empty.
1427             */
1428            public List<Layer> getLayers() {
1429                if (Main.map == null || Main.map.mapView == null)
1430                    return Collections.<Layer>emptyList();
1431                return Main.map.mapView.getAllLayersAsList();
1432            }
1433    
1434            /**
1435             * Ensures that at least one layer is selected in the layer dialog
1436             *
1437             */
1438            protected void ensureActiveSelected() {
1439                if (getLayers().isEmpty())
1440                    return;
1441                final Layer activeLayer = getActiveLayer();
1442                if (activeLayer != null) {
1443                    // there's an active layer - select it and make it
1444                    // visible
1445                    int idx = getLayers().indexOf(activeLayer);
1446                    selectionModel.setSelectionInterval(idx, idx);
1447                    ensureSelectedIsVisible();
1448                } else {
1449                    // no active layer - select the first one and make
1450                    // it visible
1451                    selectionModel.setSelectionInterval(0, 0);
1452                    ensureSelectedIsVisible();
1453                }
1454            }
1455    
1456            /**
1457             * Replies the active layer. null, if no active layer is available
1458             *
1459             * @return the active layer. null, if no active layer is available
1460             */
1461            protected Layer getActiveLayer() {
1462                if (Main.map == null || Main.map.mapView == null) return null;
1463                return Main.map.mapView.getActiveLayer();
1464            }
1465    
1466            /* ------------------------------------------------------------------------------ */
1467            /* Interface TableModel                                                           */
1468            /* ------------------------------------------------------------------------------ */
1469    
1470            @Override
1471            public int getRowCount() {
1472                List<Layer> layers = getLayers();
1473                if (layers == null) return 0;
1474                return layers.size();
1475            }
1476    
1477            @Override
1478            public int getColumnCount() {
1479                return 3;
1480            }
1481    
1482            @Override
1483            public Object getValueAt(int row, int col) {
1484                if (row >= 0 && row < getLayers().size()) {
1485                    switch (col) {
1486                    case 0: return getLayers().get(row) == getActiveLayer();
1487                    case 1: return getLayers().get(row);
1488                    case 2: return getLayers().get(row);
1489                    default: throw new RuntimeException();
1490                    }
1491                }
1492                return null;
1493            }
1494    
1495            @Override
1496            public boolean isCellEditable(int row, int col) {
1497                if (col == 0 && getActiveLayer() == getLayers().get(row))
1498                    return false;
1499                return true;
1500            }
1501    
1502            @Override
1503            public void setValueAt(Object value, int row, int col) {
1504                Layer l = getLayers().get(row);
1505                switch (col) {
1506                case 0:
1507                    Main.map.mapView.setActiveLayer(l);
1508                    l.setVisible(true);
1509                    break;
1510                case 1:
1511                    l.setVisible((Boolean) value);
1512                    break;
1513                case 2:
1514                    l.setName((String) value);
1515                    break;
1516                default: throw new RuntimeException();
1517                }
1518                fireTableCellUpdated(row, col);
1519            }
1520    
1521            /* ------------------------------------------------------------------------------ */
1522            /* Interface LayerChangeListener                                                  */
1523            /* ------------------------------------------------------------------------------ */
1524            @Override
1525            public void activeLayerChange(final Layer oldLayer, final Layer newLayer) {
1526                GuiHelper.runInEDTAndWait(new Runnable() {
1527                    @Override
1528                    public void run() {
1529                        if (oldLayer != null) {
1530                            int idx = getLayers().indexOf(oldLayer);
1531                            if (idx >= 0) {
1532                                fireTableRowsUpdated(idx,idx);
1533                            }
1534                        }
1535    
1536                        if (newLayer != null) {
1537                            int idx = getLayers().indexOf(newLayer);
1538                            if (idx >= 0) {
1539                                fireTableRowsUpdated(idx,idx);
1540                            }
1541                        }
1542                        ensureActiveSelected();
1543                    }
1544                });
1545            }
1546    
1547            @Override
1548            public void layerAdded(Layer newLayer) {
1549                onAddLayer(newLayer);
1550            }
1551    
1552            @Override
1553            public void layerRemoved(final Layer oldLayer) {
1554                onRemoveLayer(oldLayer);
1555            }
1556    
1557            /* ------------------------------------------------------------------------------ */
1558            /* Interface PropertyChangeListener                                               */
1559            /* ------------------------------------------------------------------------------ */
1560            @Override
1561            public void propertyChange(PropertyChangeEvent evt) {
1562                if (evt.getSource() instanceof Layer) {
1563                    Layer layer = (Layer)evt.getSource();
1564                    final int idx = getLayers().indexOf(layer);
1565                    if (idx < 0) return;
1566                    fireRefresh();
1567                }
1568            }
1569        }
1570    
1571        static class LayerList extends JTable {
1572            public LayerList(TableModel dataModel) {
1573                super(dataModel);
1574            }
1575    
1576            public void scrollToVisible(int row, int col) {
1577                if (!(getParent() instanceof JViewport))
1578                    return;
1579                JViewport viewport = (JViewport) getParent();
1580                Rectangle rect = getCellRect(row, col, true);
1581                Point pt = viewport.getViewPosition();
1582                rect.setLocation(rect.x - pt.x, rect.y - pt.y);
1583                viewport.scrollRectToVisible(rect);
1584            }
1585        }
1586    
1587        /**
1588         * Creates a {@link ShowHideLayerAction} for <code>layer</code> in the
1589         * context of this {@link LayerListDialog}.
1590         *
1591         * @param layer the layer
1592         * @return the action
1593         */
1594        public ShowHideLayerAction createShowHideLayerAction() {
1595            ShowHideLayerAction act = new ShowHideLayerAction(true);
1596            act.putValue(Action.NAME, tr("Show/Hide"));
1597            return act;
1598        }
1599    
1600        /**
1601         * Creates a {@link DeleteLayerAction} for <code>layer</code> in the
1602         * context of this {@link LayerListDialog}.
1603         *
1604         * @param layer the layer
1605         * @return the action
1606         */
1607        public DeleteLayerAction createDeleteLayerAction() {
1608            // the delete layer action doesn't depend on the current layer
1609            return new DeleteLayerAction();
1610        }
1611    
1612        /**
1613         * Creates a {@link ActivateLayerAction} for <code>layer</code> in the
1614         * context of this {@link LayerListDialog}.
1615         *
1616         * @param layer the layer
1617         * @return the action
1618         */
1619        public ActivateLayerAction createActivateLayerAction(Layer layer) {
1620            return new ActivateLayerAction(layer);
1621        }
1622    
1623        /**
1624         * Creates a {@link MergeLayerAction} for <code>layer</code> in the
1625         * context of this {@link LayerListDialog}.
1626         *
1627         * @param layer the layer
1628         * @return the action
1629         */
1630        public MergeAction createMergeLayerAction(Layer layer) {
1631            return new MergeAction(layer);
1632        }
1633    
1634        public static Layer getLayerForIndex(int index) {
1635    
1636            if (!Main.isDisplayingMapView())
1637                return null;
1638    
1639            List<Layer> layers = Main.map.mapView.getAllLayersAsList();
1640    
1641            if (index < layers.size() && index >= 0)
1642                return layers.get(index);
1643            else
1644                return null;
1645        }
1646    
1647        // This is not Class<? extends Layer> on purpose, to allow asking for layers implementing some interface
1648        public static List<MultikeyInfo> getLayerInfoByClass(Class<?> layerClass) {
1649    
1650            List<MultikeyInfo> result = new ArrayList<MultikeyShortcutAction.MultikeyInfo>();
1651    
1652            if (!Main.isDisplayingMapView())
1653                return result;
1654    
1655            List<Layer> layers = Main.map.mapView.getAllLayersAsList();
1656    
1657            int index = 0;
1658            for (Layer l: layers) {
1659                if (layerClass.isAssignableFrom(l.getClass())) {
1660                    result.add(new MultikeyInfo(index, l.getName()));
1661                }
1662                index++;
1663            }
1664    
1665            return result;
1666        }
1667    
1668        public static boolean isLayerValid(Layer l) {
1669            if (l == null)
1670                return false;
1671    
1672            if (!Main.isDisplayingMapView())
1673                return false;
1674    
1675            return Main.map.mapView.getAllLayersAsList().indexOf(l) >= 0;
1676        }
1677    
1678        public static MultikeyInfo getLayerInfo(Layer l) {
1679    
1680            if (l == null)
1681                return null;
1682    
1683            if (!Main.isDisplayingMapView())
1684                return null;
1685    
1686            int index = Main.map.mapView.getAllLayersAsList().indexOf(l);
1687            if (index < 0)
1688                return null;
1689    
1690            return new MultikeyInfo(index, l.getName());
1691        }
1692    }