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