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

Last change on this file since 17458 was 17458, checked in by GerdP, 3 years ago

see #17184: Memory leaks

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