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