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

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

findbugs - BC_UNCONFIRMED_CAST_OF_RETURN

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