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

Last change on this file since 13434 was 13285, checked in by Don-vip, 6 years ago

fix #15740 - Layers don't move up/down correctly if multiple layers are selected

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