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

Last change on this file was 19050, checked in by taylor.smock, 15 months ago

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

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