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

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

sonar - squid:S2301 - Public methods should not contain selector arguments

  • 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.addAndFireLayerChangeListener(model);
326 layerManager.addAndFireActiveLayerChangeListener(model);
327 model.populate();
328 }
329
330 @Override
331 public void hideNotify() {
332 layerManager.removeAndFireLayerChangeListener(model);
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.