source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java @ 5241

Revision 5200, 59.3 KB checked in by akks, 5 weeks ago (diff)

see #7626, fix #7463: keys Ctrl-Shift-Up/Down, Enter, Spacebar work better in toggle dialogs
Enter and Spacebar = useful actions for list items (select, toggle, etc.)

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