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

Last change on this file since 15048 was 14993, checked in by GerdP, 5 years ago

fix #17591: Add new preference zoom.scale-follow-native-resolution-at-load

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