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

Last change on this file since 11893 was 11893, checked in by Don-vip, 2 years ago

sonar - squid:S1126 - Return of boolean expressions should not be wrapped into an "if-then-else" statement

  • Property svn:eol-style set to native
File size: 45.7 KB
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.GraphicsEnvironment;
11import java.awt.event.ActionEvent;
12import java.awt.event.InputEvent;
13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
15import java.beans.PropertyChangeEvent;
16import java.beans.PropertyChangeListener;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collections;
20import java.util.List;
21import java.util.Objects;
22import java.util.concurrent.CopyOnWriteArrayList;
23
24import javax.swing.AbstractAction;
25import javax.swing.DefaultCellEditor;
26import javax.swing.DefaultListSelectionModel;
27import javax.swing.DropMode;
28import javax.swing.ImageIcon;
29import javax.swing.JCheckBox;
30import javax.swing.JComponent;
31import javax.swing.JLabel;
32import javax.swing.JTable;
33import javax.swing.KeyStroke;
34import javax.swing.ListSelectionModel;
35import javax.swing.UIManager;
36import javax.swing.event.ListDataEvent;
37import javax.swing.event.ListSelectionEvent;
38import javax.swing.table.AbstractTableModel;
39import javax.swing.table.DefaultTableCellRenderer;
40import javax.swing.table.TableCellRenderer;
41import javax.swing.table.TableModel;
42
43import org.openstreetmap.josm.Main;
44import org.openstreetmap.josm.actions.MergeLayerAction;
45import org.openstreetmap.josm.data.preferences.AbstractProperty;
46import org.openstreetmap.josm.gui.MapView;
47import org.openstreetmap.josm.gui.SideButton;
48import org.openstreetmap.josm.gui.dialogs.layer.ActivateLayerAction;
49import org.openstreetmap.josm.gui.dialogs.layer.DeleteLayerAction;
50import org.openstreetmap.josm.gui.dialogs.layer.DuplicateAction;
51import org.openstreetmap.josm.gui.dialogs.layer.LayerListTransferHandler;
52import org.openstreetmap.josm.gui.dialogs.layer.LayerVisibilityAction;
53import org.openstreetmap.josm.gui.dialogs.layer.MergeAction;
54import org.openstreetmap.josm.gui.dialogs.layer.MoveDownAction;
55import org.openstreetmap.josm.gui.dialogs.layer.MoveUpAction;
56import org.openstreetmap.josm.gui.dialogs.layer.ShowHideLayerAction;
57import org.openstreetmap.josm.gui.layer.JumpToMarkerActions;
58import org.openstreetmap.josm.gui.layer.Layer;
59import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
60import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
61import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
62import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
63import org.openstreetmap.josm.gui.layer.MainLayerManager;
64import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
65import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
66import org.openstreetmap.josm.gui.layer.NativeScaleLayer;
67import org.openstreetmap.josm.gui.util.GuiHelper;
68import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
69import org.openstreetmap.josm.gui.widgets.JosmTextField;
70import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
71import org.openstreetmap.josm.gui.widgets.ScrollableTable;
72import org.openstreetmap.josm.tools.ImageProvider;
73import org.openstreetmap.josm.tools.InputMapUtils;
74import org.openstreetmap.josm.tools.MultikeyActionsHandler;
75import org.openstreetmap.josm.tools.MultikeyShortcutAction.MultikeyInfo;
76import org.openstreetmap.josm.tools.Shortcut;
77
78/**
79 * This is a toggle dialog which displays the list of layers. Actions allow to
80 * change the ordering of the layers, to hide/show layers, to activate layers,
81 * and to delete layers.
82 * <p>
83 * Support for multiple {@link LayerListDialog} is currently not complete but intended for the future.
84 * @since 17
85 */
86public class LayerListDialog extends ToggleDialog {
87    /** the unique instance of the dialog */
88    private static volatile LayerListDialog instance;
89
90    /**
91     * Creates the instance of the dialog. It's connected to the layer manager
92     *
93     * @param layerManager the layer manager
94     * @since 11885 (signature)
95     */
96    public static void createInstance(MainLayerManager layerManager) {
97        if (instance != null)
98            throw new IllegalStateException("Dialog was already created");
99        instance = new LayerListDialog(layerManager);
100    }
101
102    /**
103     * Replies the instance of the dialog
104     *
105     * @return the instance of the dialog
106     * @throws IllegalStateException if the dialog is not created yet
107     * @see #createInstance(MainLayerManager)
108     */
109    public static LayerListDialog getInstance() {
110        if (instance == null)
111            throw new IllegalStateException("Dialog not created yet. Invoke createInstance() first");
112        return instance;
113    }
114
115    /** the model for the layer list */
116    private final LayerListModel model;
117
118    /** the list of layers (technically its a JTable, but appears like a list) */
119    private final LayerList layerList;
120
121    private final ActivateLayerAction activateLayerAction;
122    private final ShowHideLayerAction showHideLayerAction;
123
124    //TODO This duplicates ShowHide actions functionality
125    /** stores which layer index to toggle and executes the ShowHide action if the layer is present */
126    private final class ToggleLayerIndexVisibility extends AbstractAction {
127        private final int layerIndex;
128
129        ToggleLayerIndexVisibility(int layerIndex) {
130            this.layerIndex = layerIndex;
131        }
132
133        @Override
134        public void actionPerformed(ActionEvent e) {
135            final Layer l = model.getLayer(model.getRowCount() - layerIndex - 1);
136            if (l != null) {
137                l.toggleVisible();
138            }
139        }
140    }
141
142    private final transient Shortcut[] visibilityToggleShortcuts = new Shortcut[10];
143    private final ToggleLayerIndexVisibility[] visibilityToggleActions = new ToggleLayerIndexVisibility[10];
144
145    /**
146     * The {@link MainLayerManager} this list is for.
147     */
148    private final transient MainLayerManager layerManager;
149
150    /**
151     * registers (shortcut to toggle right hand side toggle dialogs)+(number keys) shortcuts
152     * to toggle the visibility of the first ten layers.
153     */
154    private void createVisibilityToggleShortcuts() {
155        for (int i = 0; i < 10; i++) {
156            final int i1 = i + 1;
157            /* POSSIBLE SHORTCUTS: 1,2,3,4,5,6,7,8,9,0=10 */
158            visibilityToggleShortcuts[i] = Shortcut.registerShortcut("subwindow:layers:toggleLayer" + i1,
159                    tr("Toggle visibility of layer: {0}", i1), KeyEvent.VK_0 + (i1 % 10), Shortcut.ALT);
160            visibilityToggleActions[i] = new ToggleLayerIndexVisibility(i);
161            Main.registerActionShortcut(visibilityToggleActions[i], visibilityToggleShortcuts[i]);
162        }
163    }
164
165    /**
166     * Creates a layer list and attach it to the given layer manager.
167     * @param layerManager The layer manager this list is for
168     * @since 10467
169     */
170    public LayerListDialog(MainLayerManager layerManager) {
171        super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."),
172                Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L,
173                        Shortcut.ALT_SHIFT), 100, true);
174        this.layerManager = layerManager;
175
176        // create the models
177        //
178        DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
179        selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
180        model = new LayerListModel(layerManager, selectionModel);
181
182        // create the list control
183        //
184        layerList = new LayerList(model);
185        layerList.setSelectionModel(selectionModel);
186        layerList.addMouseListener(new PopupMenuHandler());
187        layerList.setBackground(UIManager.getColor("Button.background"));
188        layerList.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
189        layerList.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
190        layerList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
191        layerList.setTableHeader(null);
192        layerList.setShowGrid(false);
193        layerList.setIntercellSpacing(new Dimension(0, 0));
194        layerList.getColumnModel().getColumn(0).setCellRenderer(new ActiveLayerCellRenderer());
195        layerList.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(new ActiveLayerCheckBox()));
196        layerList.getColumnModel().getColumn(0).setMaxWidth(12);
197        layerList.getColumnModel().getColumn(0).setPreferredWidth(12);
198        layerList.getColumnModel().getColumn(0).setResizable(false);
199
200        layerList.getColumnModel().getColumn(1).setCellRenderer(new NativeScaleLayerCellRenderer());
201        layerList.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(new NativeScaleLayerCheckBox()));
202        layerList.getColumnModel().getColumn(1).setMaxWidth(12);
203        layerList.getColumnModel().getColumn(1).setPreferredWidth(12);
204        layerList.getColumnModel().getColumn(1).setResizable(false);
205
206        layerList.getColumnModel().getColumn(2).setCellRenderer(new LayerVisibleCellRenderer());
207        layerList.getColumnModel().getColumn(2).setCellEditor(new LayerVisibleCellEditor(new LayerVisibleCheckBox()));
208        layerList.getColumnModel().getColumn(2).setMaxWidth(16);
209        layerList.getColumnModel().getColumn(2).setPreferredWidth(16);
210        layerList.getColumnModel().getColumn(2).setResizable(false);
211
212        layerList.getColumnModel().getColumn(3).setCellRenderer(new LayerNameCellRenderer());
213        layerList.getColumnModel().getColumn(3).setCellEditor(new LayerNameCellEditor(new DisableShortcutsOnFocusGainedTextField()));
214        // Disable some default JTable shortcuts to use JOSM ones (see #5678, #10458)
215        for (KeyStroke ks : new KeyStroke[] {
216                KeyStroke.getKeyStroke(KeyEvent.VK_C, GuiHelper.getMenuShortcutKeyMaskEx()),
217                KeyStroke.getKeyStroke(KeyEvent.VK_V, GuiHelper.getMenuShortcutKeyMaskEx()),
218                KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_DOWN_MASK),
219                KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_DOWN_MASK),
220                KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_DOWN_MASK),
221                KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_DOWN_MASK),
222                KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK),
223                KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK),
224                KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK),
225                KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK),
226                KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0),
227                KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0),
228                KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0),
229                KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0),
230        }) {
231            layerList.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ks, new Object());
232        }
233
234        // init the model
235        //
236        model.populate();
237        model.setSelectedLayer(layerManager.getActiveLayer());
238        model.addLayerListModelListener(
239                new LayerListModelListener() {
240                    @Override
241                    public void makeVisible(int row, Layer layer) {
242                        layerList.scrollToVisible(row, 0);
243                        layerList.repaint();
244                    }
245
246                    @Override
247                    public void refresh() {
248                        layerList.repaint();
249                    }
250                }
251                );
252
253        // -- move up action
254        MoveUpAction moveUpAction = new MoveUpAction(model);
255        adaptTo(moveUpAction, model);
256        adaptTo(moveUpAction, selectionModel);
257
258        // -- move down action
259        MoveDownAction moveDownAction = new MoveDownAction(model);
260        adaptTo(moveDownAction, model);
261        adaptTo(moveDownAction, selectionModel);
262
263        // -- activate action
264        activateLayerAction = new ActivateLayerAction(model);
265        activateLayerAction.updateEnabledState();
266        MultikeyActionsHandler.getInstance().addAction(activateLayerAction);
267        adaptTo(activateLayerAction, selectionModel);
268
269        JumpToMarkerActions.initialize();
270
271        // -- show hide action
272        showHideLayerAction = new ShowHideLayerAction(model);
273        MultikeyActionsHandler.getInstance().addAction(showHideLayerAction);
274        adaptTo(showHideLayerAction, selectionModel);
275
276        LayerVisibilityAction visibilityAction = new LayerVisibilityAction(model);
277        adaptTo(visibilityAction, selectionModel);
278        SideButton visibilityButton = new SideButton(visibilityAction, false);
279        visibilityAction.setCorrespondingSideButton(visibilityButton);
280
281        // -- delete layer action
282        DeleteLayerAction deleteLayerAction = new DeleteLayerAction(model);
283        layerList.getActionMap().put("deleteLayer", deleteLayerAction);
284        adaptTo(deleteLayerAction, selectionModel);
285        getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
286                KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"
287                );
288        getActionMap().put("delete", deleteLayerAction);
289
290        // Activate layer on Enter key press
291        InputMapUtils.addEnterAction(layerList, new AbstractAction() {
292            @Override
293            public void actionPerformed(ActionEvent e) {
294                activateLayerAction.actionPerformed(null);
295                layerList.requestFocus();
296            }
297        });
298
299        // Show/Activate layer on Enter key press
300        InputMapUtils.addSpacebarAction(layerList, showHideLayerAction);
301
302        createLayout(layerList, true, Arrays.asList(
303                new SideButton(moveUpAction, false),
304                new SideButton(moveDownAction, false),
305                new SideButton(activateLayerAction, false),
306                visibilityButton,
307                new SideButton(deleteLayerAction, false)
308        ));
309
310        createVisibilityToggleShortcuts();
311    }
312
313    /**
314     * Gets the layer manager this dialog is for.
315     * @return The layer manager.
316     * @since 10288
317     */
318    public MainLayerManager getLayerManager() {
319        return layerManager;
320    }
321
322    @Override
323    public void showNotify() {
324        layerManager.addActiveLayerChangeListener(activateLayerAction);
325        layerManager.addLayerChangeListener(model, true);
326        layerManager.addAndFireActiveLayerChangeListener(model);
327        model.populate();
328    }
329
330    @Override
331    public void hideNotify() {
332        layerManager.removeLayerChangeListener(model, true);
333        layerManager.removeActiveLayerChangeListener(model);
334        layerManager.removeActiveLayerChangeListener(activateLayerAction);
335    }
336
337    /**
338     * Returns the layer list model.
339     * @return the layer list model
340     */
341    public LayerListModel getModel() {
342        return model;
343    }
344
345    /**
346     * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that
347     * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()}
348     * on every {@link ListSelectionEvent}.
349     *
350     * @param listener  the listener
351     * @param listSelectionModel  the source emitting {@link ListSelectionEvent}s
352     */
353    protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) {
354        listSelectionModel.addListSelectionListener(e -> listener.updateEnabledState());
355    }
356
357    /**
358     * Wires <code>listener</code> to <code>listModel</code> in such a way, that
359     * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()}
360     * on every {@link ListDataEvent}.
361     *
362     * @param listener the listener
363     * @param listModel the source emitting {@link ListDataEvent}s
364     */
365    protected void adaptTo(final IEnabledStateUpdating listener, LayerListModel listModel) {
366        listModel.addTableModelListener(e -> listener.updateEnabledState());
367    }
368
369    @Override
370    public void destroy() {
371        for (int i = 0; i < 10; i++) {
372            Main.unregisterActionShortcut(visibilityToggleActions[i], visibilityToggleShortcuts[i]);
373        }
374        MultikeyActionsHandler.getInstance().removeAction(activateLayerAction);
375        MultikeyActionsHandler.getInstance().removeAction(showHideLayerAction);
376        JumpToMarkerActions.unregisterActions();
377        super.destroy();
378        instance = null;
379    }
380
381    private static class ActiveLayerCheckBox extends JCheckBox {
382        ActiveLayerCheckBox() {
383            setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
384            ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank");
385            ImageIcon active = ImageProvider.get("dialogs/layerlist", "active");
386            setIcon(blank);
387            setSelectedIcon(active);
388            setRolloverIcon(blank);
389            setRolloverSelectedIcon(active);
390            setPressedIcon(ImageProvider.get("dialogs/layerlist", "active-pressed"));
391        }
392    }
393
394    private static class LayerVisibleCheckBox extends JCheckBox {
395        private final ImageIcon iconEye;
396        private final ImageIcon iconEyeTranslucent;
397        private boolean isTranslucent;
398
399        /**
400         * Constructs a new {@code LayerVisibleCheckBox}.
401         */
402        LayerVisibleCheckBox() {
403            setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
404            iconEye = ImageProvider.get("dialogs/layerlist", "eye");
405            iconEyeTranslucent = ImageProvider.get("dialogs/layerlist", "eye-translucent");
406            setIcon(ImageProvider.get("dialogs/layerlist", "eye-off"));
407            setPressedIcon(ImageProvider.get("dialogs/layerlist", "eye-pressed"));
408            setSelectedIcon(iconEye);
409            isTranslucent = false;
410        }
411
412        public void setTranslucent(boolean isTranslucent) {
413            if (this.isTranslucent == isTranslucent) return;
414            if (isTranslucent) {
415                setSelectedIcon(iconEyeTranslucent);
416            } else {
417                setSelectedIcon(iconEye);
418            }
419            this.isTranslucent = isTranslucent;
420        }
421
422        public void updateStatus(Layer layer) {
423            boolean visible = layer.isVisible();
424            setSelected(visible);
425            setTranslucent(layer.getOpacity() < 1.0);
426            setToolTipText(visible ?
427                tr("layer is currently visible (click to hide layer)") :
428                tr("layer is currently hidden (click to show layer)"));
429        }
430    }
431
432    private static class NativeScaleLayerCheckBox extends JCheckBox {
433        NativeScaleLayerCheckBox() {
434            setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
435            ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank");
436            ImageIcon active = ImageProvider.get("dialogs/layerlist", "scale");
437            setIcon(blank);
438            setSelectedIcon(active);
439        }
440    }
441
442    private static class ActiveLayerCellRenderer implements TableCellRenderer {
443        private final JCheckBox cb;
444
445        /**
446         * Constructs a new {@code ActiveLayerCellRenderer}.
447         */
448        ActiveLayerCellRenderer() {
449            cb = new ActiveLayerCheckBox();
450        }
451
452        @Override
453        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
454            boolean active = value != null && (Boolean) value;
455            cb.setSelected(active);
456            cb.setToolTipText(active ? tr("this layer is the active layer") : tr("this layer is not currently active (click to activate)"));
457            return cb;
458        }
459    }
460
461    private static class LayerVisibleCellRenderer implements TableCellRenderer {
462        private final LayerVisibleCheckBox cb;
463
464        /**
465         * Constructs a new {@code LayerVisibleCellRenderer}.
466         */
467        LayerVisibleCellRenderer() {
468            this.cb = new LayerVisibleCheckBox();
469        }
470
471        @Override
472        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
473            if (value != null) {
474                cb.updateStatus((Layer) value);
475            }
476            return cb;
477        }
478    }
479
480    private static class LayerVisibleCellEditor extends DefaultCellEditor {
481        private final LayerVisibleCheckBox cb;
482
483        LayerVisibleCellEditor(LayerVisibleCheckBox cb) {
484            super(cb);
485            this.cb = cb;
486        }
487
488        @Override
489        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
490            cb.updateStatus((Layer) value);
491            return cb;
492        }
493    }
494
495    private static class NativeScaleLayerCellRenderer implements TableCellRenderer {
496        private final JCheckBox cb;
497
498        /**
499         * Constructs a new {@code ActiveLayerCellRenderer}.
500         */
501        NativeScaleLayerCellRenderer() {
502            cb = new NativeScaleLayerCheckBox();
503        }
504
505        @Override
506        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
507            Layer layer = (Layer) value;
508            if (layer instanceof NativeScaleLayer) {
509                boolean active = ((NativeScaleLayer) layer) == Main.map.mapView.getNativeScaleLayer();
510                cb.setSelected(active);
511                cb.setToolTipText(active
512                    ? tr("scale follows native resolution of this layer")
513                    : tr("scale follows native resolution of another layer (click to set this layer)")
514                );
515            } else {
516                cb.setSelected(false);
517                cb.setToolTipText(tr("this layer has no native resolution"));
518            }
519            return cb;
520        }
521    }
522
523    private class LayerNameCellRenderer extends DefaultTableCellRenderer {
524
525        protected boolean isActiveLayer(Layer layer) {
526            return getLayerManager().getActiveLayer() == layer;
527        }
528
529        @Override
530        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
531            if (value == null)
532                return this;
533            Layer layer = (Layer) value;
534            JLabel label = (JLabel) super.getTableCellRendererComponent(table,
535                    layer.getName(), isSelected, hasFocus, row, column);
536            if (isActiveLayer(layer)) {
537                label.setFont(label.getFont().deriveFont(Font.BOLD));
538            }
539            if (Main.pref.getBoolean("dialog.layer.colorname", true)) {
540                AbstractProperty<Color> prop = layer.getColorProperty();
541                Color c = prop == null ? null : prop.get();
542                if (c == null || !model.getLayers().stream()
543                        .map(Layer::getColorProperty)
544                        .filter(Objects::nonNull)
545                        .map(AbstractProperty::get)
546                        .anyMatch(oc -> oc != null && !oc.equals(c))) {
547                    /* not more than one color, don't use coloring */
548                    label.setForeground(UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground"));
549                } else {
550                    label.setForeground(c);
551                }
552            }
553            label.setIcon(layer.getIcon());
554            label.setToolTipText(layer.getToolTipText());
555            return label;
556        }
557    }
558
559    private static class LayerNameCellEditor extends DefaultCellEditor {
560        LayerNameCellEditor(DisableShortcutsOnFocusGainedTextField tf) {
561            super(tf);
562        }
563
564        @Override
565        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
566            JosmTextField tf = (JosmTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
567            tf.setText(value == null ? "" : ((Layer) value).getName());
568            return tf;
569        }
570    }
571
572    class PopupMenuHandler extends PopupMenuLauncher {
573        @Override
574        public void showMenu(MouseEvent evt) {
575            menu = new LayerListPopup(getModel().getSelectedLayers());
576            super.showMenu(evt);
577        }
578    }
579
580    /**
581     * Observer interface to be implemented by views using {@link LayerListModel}.
582     */
583    public interface LayerListModelListener {
584
585        /**
586         * Fired when a layer is made visible.
587         * @param index the layer index
588         * @param layer the layer
589         */
590        void makeVisible(int index, Layer layer);
591
592
593        /**
594         * Fired when something has changed in the layer list model.
595         */
596        void refresh();
597    }
598
599    /**
600     * The layer list model. The model manages a list of layers and provides methods for
601     * moving layers up and down, for toggling their visibility, and for activating a layer.
602     *
603     * The model is a {@link TableModel} and it provides a {@link ListSelectionModel}. It expects
604     * to be configured with a {@link DefaultListSelectionModel}. The selection model is used
605     * to update the selection state of views depending on messages sent to the model.
606     *
607     * The model manages a list of {@link LayerListModelListener} which are mainly notified if
608     * the model requires views to make a specific list entry visible.
609     *
610     * It also listens to {@link PropertyChangeEvent}s of every {@link Layer} it manages, in particular to
611     * the properties {@link Layer#VISIBLE_PROP} and {@link Layer#NAME_PROP}.
612     */
613    public static final class LayerListModel extends AbstractTableModel
614            implements LayerChangeListener, ActiveLayerChangeListener, PropertyChangeListener {
615        /** manages list selection state*/
616        private final DefaultListSelectionModel selectionModel;
617        private final CopyOnWriteArrayList<LayerListModelListener> listeners;
618        private LayerList layerList;
619        private final MainLayerManager layerManager;
620
621        /**
622         * constructor
623         * @param layerManager The layer manager to use for the list.
624         * @param selectionModel the list selection model
625         */
626        LayerListModel(MainLayerManager layerManager, DefaultListSelectionModel selectionModel) {
627            this.layerManager = layerManager;
628            this.selectionModel = selectionModel;
629            listeners = new CopyOnWriteArrayList<>();
630        }
631
632        void setLayerList(LayerList layerList) {
633            this.layerList = layerList;
634        }
635
636        /**
637         * The layer manager this model is for.
638         * @return The layer manager.
639         */
640        public MainLayerManager getLayerManager() {
641            return layerManager;
642        }
643
644        /**
645         * Adds a listener to this model
646         *
647         * @param listener the listener
648         */
649        public void addLayerListModelListener(LayerListModelListener listener) {
650            if (listener != null) {
651                listeners.addIfAbsent(listener);
652            }
653        }
654
655        /**
656         * removes a listener from  this model
657         * @param listener the listener
658         */
659        public void removeLayerListModelListener(LayerListModelListener listener) {
660            listeners.remove(listener);
661        }
662
663        /**
664         * Fires a make visible event to listeners
665         *
666         * @param index the index of the row to make visible
667         * @param layer the layer at this index
668         * @see LayerListModelListener#makeVisible(int, Layer)
669         */
670        private void fireMakeVisible(int index, Layer layer) {
671            for (LayerListModelListener listener : listeners) {
672                listener.makeVisible(index, layer);
673            }
674        }
675
676        /**
677         * Fires a refresh event to listeners of this model
678         *
679         * @see LayerListModelListener#refresh()
680         */
681        private void fireRefresh() {
682            for (LayerListModelListener listener : listeners) {
683                listener.refresh();
684            }
685        }
686
687        /**
688         * Populates the model with the current layers managed by {@link MapView}.
689         */
690        public void populate() {
691            for (Layer layer: getLayers()) {
692                // make sure the model is registered exactly once
693                layer.removePropertyChangeListener(this);
694                layer.addPropertyChangeListener(this);
695            }
696            fireTableDataChanged();
697        }
698
699        /**
700         * Marks <code>layer</code> as selected layer. Ignored, if layer is null.
701         *
702         * @param layer the layer.
703         */
704        public void setSelectedLayer(Layer layer) {
705            if (layer == null)
706                return;
707            int idx = getLayers().indexOf(layer);
708            if (idx >= 0) {
709                selectionModel.setSelectionInterval(idx, idx);
710            }
711            ensureSelectedIsVisible();
712        }
713
714        /**
715         * Replies the list of currently selected layers. Never null, but may be empty.
716         *
717         * @return the list of currently selected layers. Never null, but may be empty.
718         */
719        public List<Layer> getSelectedLayers() {
720            List<Layer> selected = new ArrayList<>();
721            List<Layer> layers = getLayers();
722            for (int i = 0; i < layers.size(); i++) {
723                if (selectionModel.isSelectedIndex(i)) {
724                    selected.add(layers.get(i));
725                }
726            }
727            return selected;
728        }
729
730        /**
731         * Replies a the list of indices of the selected rows. Never null, but may be empty.
732         *
733         * @return  the list of indices of the selected rows. Never null, but may be empty.
734         */
735        public List<Integer> getSelectedRows() {
736            List<Integer> selected = new ArrayList<>();
737            for (int i = 0; i < getLayers().size(); i++) {
738                if (selectionModel.isSelectedIndex(i)) {
739                    selected.add(i);
740                }
741            }
742            return selected;
743        }
744
745        /**
746         * Invoked if a layer managed by {@link MapView} is removed
747         *
748         * @param layer the layer which is removed
749         */
750        private void onRemoveLayer(Layer layer) {
751            if (layer == null)
752                return;
753            layer.removePropertyChangeListener(this);
754            final int size = getRowCount();
755            final List<Integer> rows = getSelectedRows();
756
757            if (rows.isEmpty() && size > 0) {
758                selectionModel.setSelectionInterval(size-1, size-1);
759            }
760            fireTableDataChanged();
761            fireRefresh();
762            ensureActiveSelected();
763        }
764
765        /**
766         * Invoked when a layer managed by {@link MapView} is added
767         *
768         * @param layer the layer
769         */
770        private void onAddLayer(Layer layer) {
771            if (layer == null)
772                return;
773            layer.addPropertyChangeListener(this);
774            fireTableDataChanged();
775            int idx = getLayers().indexOf(layer);
776            if (layerList != null) {
777                layerList.setRowHeight(idx, Math.max(16, layer.getIcon().getIconHeight()));
778            }
779            selectionModel.setSelectionInterval(idx, idx);
780            ensureSelectedIsVisible();
781        }
782
783        /**
784         * Replies the first layer. Null if no layers are present
785         *
786         * @return the first layer. Null if no layers are present
787         */
788        public Layer getFirstLayer() {
789            if (getRowCount() == 0)
790                return null;
791            return getLayers().get(0);
792        }
793
794        /**
795         * Replies the layer at position <code>index</code>
796         *
797         * @param index the index
798         * @return the layer at position <code>index</code>. Null,
799         * if index is out of range.
800         */
801        public Layer getLayer(int index) {
802            if (index < 0 || index >= getRowCount())
803                return null;
804            return getLayers().get(index);
805        }
806
807        /**
808         * Replies true if the currently selected layers can move up by one position
809         *
810         * @return true if the currently selected layers can move up by one position
811         */
812        public boolean canMoveUp() {
813            List<Integer> sel = getSelectedRows();
814            return !sel.isEmpty() && sel.get(0) > 0;
815        }
816
817        /**
818         * Move up the currently selected layers by one position
819         *
820         */
821        public void moveUp() {
822            if (!canMoveUp())
823                return;
824            List<Integer> sel = getSelectedRows();
825            List<Layer> layers = getLayers();
826            for (int row : sel) {
827                Layer l1 = layers.get(row);
828                Layer l2 = layers.get(row-1);
829                Main.map.mapView.moveLayer(l2, row);
830                Main.map.mapView.moveLayer(l1, row-1);
831            }
832            fireTableDataChanged();
833            selectionModel.setValueIsAdjusting(true);
834            selectionModel.clearSelection();
835            for (int row : sel) {
836                selectionModel.addSelectionInterval(row-1, row-1);
837            }
838            selectionModel.setValueIsAdjusting(false);
839            ensureSelectedIsVisible();
840        }
841
842        /**
843         * Replies true if the currently selected layers can move down by one position
844         *
845         * @return true if the currently selected layers can move down by one position
846         */
847        public boolean canMoveDown() {
848            List<Integer> sel = getSelectedRows();
849            return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1;
850        }
851
852        /**
853         * Move down the currently selected layers by one position
854         */
855        public void moveDown() {
856            if (!canMoveDown())
857                return;
858            List<Integer> sel = getSelectedRows();
859            Collections.reverse(sel);
860            List<Layer> layers = getLayers();
861            for (int row : sel) {
862                Layer l1 = layers.get(row);
863                Layer l2 = layers.get(row+1);
864                Main.map.mapView.moveLayer(l1, row+1);
865                Main.map.mapView.moveLayer(l2, row);
866            }
867            fireTableDataChanged();
868            selectionModel.setValueIsAdjusting(true);
869            selectionModel.clearSelection();
870            for (int row : sel) {
871                selectionModel.addSelectionInterval(row+1, row+1);
872            }
873            selectionModel.setValueIsAdjusting(false);
874            ensureSelectedIsVisible();
875        }
876
877        /**
878         * Make sure the first of the selected layers is visible in the views of this model.
879         */
880        private void ensureSelectedIsVisible() {
881            int index = selectionModel.getMinSelectionIndex();
882            if (index < 0)
883                return;
884            List<Layer> layers = getLayers();
885            if (index >= layers.size())
886                return;
887            Layer layer = layers.get(index);
888            fireMakeVisible(index, layer);
889        }
890
891        /**
892         * Replies a list of layers which are possible merge targets for <code>source</code>
893         *
894         * @param source the source layer
895         * @return a list of layers which are possible merge targets
896         * for <code>source</code>. Never null, but can be empty.
897         */
898        public List<Layer> getPossibleMergeTargets(Layer source) {
899            List<Layer> targets = new ArrayList<>();
900            if (source == null) {
901                return targets;
902            }
903            for (Layer target : getLayers()) {
904                if (source == target) {
905                    continue;
906                }
907                if (target.isMergable(source) && source.isMergable(target)) {
908                    targets.add(target);
909                }
910            }
911            return targets;
912        }
913
914        /**
915         * Replies the list of layers currently managed by {@link MapView}.
916         * Never null, but can be empty.
917         *
918         * @return the list of layers currently managed by {@link MapView}.
919         * Never null, but can be empty.
920         */
921        public List<Layer> getLayers() {
922            return getLayerManager().getLayers();
923        }
924
925        /**
926         * Ensures that at least one layer is selected in the layer dialog
927         *
928         */
929        private void ensureActiveSelected() {
930            List<Layer> layers = getLayers();
931            if (layers.isEmpty())
932                return;
933            final Layer activeLayer = getActiveLayer();
934            if (activeLayer != null) {
935                // there's an active layer - select it and make it visible
936                int idx = layers.indexOf(activeLayer);
937                selectionModel.setSelectionInterval(idx, idx);
938                ensureSelectedIsVisible();
939            } else {
940                // no active layer - select the first one and make it visible
941                selectionModel.setSelectionInterval(0, 0);
942                ensureSelectedIsVisible();
943            }
944        }
945
946        /**
947         * Replies the active layer. null, if no active layer is available
948         *
949         * @return the active layer. null, if no active layer is available
950         */
951        private Layer getActiveLayer() {
952            return getLayerManager().getActiveLayer();
953        }
954
955        /* ------------------------------------------------------------------------------ */
956        /* Interface TableModel                                                           */
957        /* ------------------------------------------------------------------------------ */
958
959        @Override
960        public int getRowCount() {
961            List<Layer> layers = getLayers();
962            return layers == null ? 0 : layers.size();
963        }
964
965        @Override
966        public int getColumnCount() {
967            return 4;
968        }
969
970        @Override
971        public Object getValueAt(int row, int col) {
972            List<Layer> layers = getLayers();
973            if (row >= 0 && row < layers.size()) {
974                switch (col) {
975                case 0: return layers.get(row) == getActiveLayer();
976                case 1:
977                case 2:
978                case 3: return layers.get(row);
979                default: // Do nothing
980                }
981            }
982            return null;
983        }
984
985        @Override
986        public boolean isCellEditable(int row, int col) {
987            return col != 0 || getActiveLayer() != getLayers().get(row);
988        }
989
990        @Override
991        public void setValueAt(Object value, int row, int col) {
992            List<Layer> layers = getLayers();
993            if (row < layers.size()) {
994                Layer l = layers.get(row);
995                switch (col) {
996                case 0:
997                    getLayerManager().setActiveLayer(l);
998                    l.setVisible(true);
999                    break;
1000                case 1:
1001                    NativeScaleLayer oldLayer = Main.map.mapView.getNativeScaleLayer();
1002                    if (oldLayer == l) {
1003                        Main.map.mapView.setNativeScaleLayer(null);
1004                    } else if (l instanceof NativeScaleLayer) {
1005                        Main.map.mapView.setNativeScaleLayer((NativeScaleLayer) l);
1006                        if (oldLayer != null) {
1007                            int idx = getLayers().indexOf(oldLayer);
1008                            if (idx >= 0) {
1009                                fireTableCellUpdated(idx, col);
1010                            }
1011                        }
1012                    }
1013                    break;
1014                case 2:
1015                    l.setVisible((Boolean) value);
1016                    break;
1017                case 3:
1018                    l.rename((String) value);
1019                    break;
1020                default:
1021                    throw new IllegalArgumentException("Wrong column: " + col);
1022                }
1023                fireTableCellUpdated(row, col);
1024            }
1025        }
1026
1027        /* ------------------------------------------------------------------------------ */
1028        /* Interface ActiveLayerChangeListener                                            */
1029        /* ------------------------------------------------------------------------------ */
1030        @Override
1031        public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
1032            Layer oldLayer = e.getPreviousActiveLayer();
1033            if (oldLayer != null) {
1034                int idx = getLayers().indexOf(oldLayer);
1035                if (idx >= 0) {
1036                    fireTableRowsUpdated(idx, idx);
1037                }
1038            }
1039
1040            Layer newLayer = getActiveLayer();
1041            if (newLayer != null) {
1042                int idx = getLayers().indexOf(newLayer);
1043                if (idx >= 0) {
1044                    fireTableRowsUpdated(idx, idx);
1045                }
1046            }
1047            ensureActiveSelected();
1048        }
1049
1050        /* ------------------------------------------------------------------------------ */
1051        /* Interface LayerChangeListener                                                  */
1052        /* ------------------------------------------------------------------------------ */
1053        @Override
1054        public void layerAdded(LayerAddEvent e) {
1055            onAddLayer(e.getAddedLayer());
1056        }
1057
1058        @Override
1059        public void layerRemoving(LayerRemoveEvent e) {
1060            onRemoveLayer(e.getRemovedLayer());
1061        }
1062
1063        @Override
1064        public void layerOrderChanged(LayerOrderChangeEvent e) {
1065            fireTableDataChanged();
1066        }
1067
1068        /* ------------------------------------------------------------------------------ */
1069        /* Interface PropertyChangeListener                                               */
1070        /* ------------------------------------------------------------------------------ */
1071        @Override
1072        public void propertyChange(PropertyChangeEvent evt) {
1073            if (evt.getSource() instanceof Layer) {
1074                Layer layer = (Layer) evt.getSource();
1075                final int idx = getLayers().indexOf(layer);
1076                if (idx < 0)
1077                    return;
1078                fireRefresh();
1079            }
1080        }
1081    }
1082
1083    /**
1084     * This component displays a list of layers and provides the methods needed by {@link LayerListModel}.
1085     */
1086    static class LayerList extends ScrollableTable {
1087
1088        LayerList(LayerListModel dataModel) {
1089            super(dataModel);
1090            dataModel.setLayerList(this);
1091            if (!GraphicsEnvironment.isHeadless()) {
1092                setDragEnabled(true);
1093            }
1094            setDropMode(DropMode.INSERT_ROWS);
1095            setTransferHandler(new LayerListTransferHandler());
1096        }
1097
1098        @Override
1099        public LayerListModel getModel() {
1100            return (LayerListModel) super.getModel();
1101        }
1102    }
1103
1104    /**
1105     * Creates a {@link ShowHideLayerAction} in the context of this {@link LayerListDialog}.
1106     *
1107     * @return the action
1108     */
1109    public ShowHideLayerAction createShowHideLayerAction() {
1110        return new ShowHideLayerAction(model);
1111    }
1112
1113    /**
1114     * Creates a {@link DeleteLayerAction} in the context of this {@link LayerListDialog}.
1115     *
1116     * @return the action
1117     */
1118    public DeleteLayerAction createDeleteLayerAction() {
1119        return new DeleteLayerAction(model);
1120    }
1121
1122    /**
1123     * Creates a {@link ActivateLayerAction} for <code>layer</code> in the context of this {@link LayerListDialog}.
1124     *
1125     * @param layer the layer
1126     * @return the action
1127     */
1128    public ActivateLayerAction createActivateLayerAction(Layer layer) {
1129        return new ActivateLayerAction(layer, model);
1130    }
1131
1132    /**
1133     * Creates a {@link MergeLayerAction} for <code>layer</code> in the context of this {@link LayerListDialog}.
1134     *
1135     * @param layer the layer
1136     * @return the action
1137     */
1138    public MergeAction createMergeLayerAction(Layer layer) {
1139        return new MergeAction(layer, model);
1140    }
1141
1142    /**
1143     * Creates a {@link DuplicateAction} for <code>layer</code> in the context of this {@link LayerListDialog}.
1144     *
1145     * @param layer the layer
1146     * @return the action
1147     */
1148    public DuplicateAction createDuplicateLayerAction(Layer layer) {
1149        return new DuplicateAction(layer, model);
1150    }
1151
1152    /**
1153     * Returns the layer at given index, or {@code null}.
1154     * @param index the index
1155     * @return the layer at given index, or {@code null} if index out of range
1156     */
1157    public static Layer getLayerForIndex(int index) {
1158        List<Layer> layers = Main.getLayerManager().getLayers();
1159
1160        if (index < layers.size() && index >= 0)
1161            return layers.get(index);
1162        else
1163            return null;
1164    }
1165
1166    /**
1167     * Returns a list of info on all layers of a given class.
1168     * @param layerClass The layer class. This is not {@code Class<? extends Layer>} on purpose,
1169     *                   to allow asking for layers implementing some interface
1170     * @return list of info on all layers assignable from {@code layerClass}
1171     */
1172    public static List<MultikeyInfo> getLayerInfoByClass(Class<?> layerClass) {
1173        List<MultikeyInfo> result = new ArrayList<>();
1174
1175        List<Layer> layers = Main.getLayerManager().getLayers();
1176
1177        int index = 0;
1178        for (Layer l: layers) {
1179            if (layerClass.isAssignableFrom(l.getClass())) {
1180                result.add(new MultikeyInfo(index, l.getName()));
1181            }
1182            index++;
1183        }
1184
1185        return result;
1186    }
1187
1188    /**
1189     * Determines if a layer is valid (contained in global layer list).
1190     * @param l the layer
1191     * @return {@code true} if layer {@code l} is contained in current layer list
1192     */
1193    public static boolean isLayerValid(Layer l) {
1194        if (l == null)
1195            return false;
1196
1197        return Main.getLayerManager().containsLayer(l);
1198    }
1199
1200    /**
1201     * Returns info about layer.
1202     * @param l the layer
1203     * @return info about layer {@code l}
1204     */
1205    public static MultikeyInfo getLayerInfo(Layer l) {
1206        if (l == null)
1207            return null;
1208
1209        int index = Main.getLayerManager().getLayers().indexOf(l);
1210        if (index < 0)
1211            return null;
1212
1213        return new MultikeyInfo(index, l.getName());
1214    }
1215}
Note: See TracBrowser for help on using the repository browser.