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

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

see #15229 - see #15182 - deprecate GuiHelper.getMenuShortcutKeyMaskEx() - replaced by PlatformHook.getMenuShortcutKeyMaskEx()

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