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

Last change on this file was 17773, checked in by simon04, 3 years ago

see #20745 - Introduce TableHelper.selectedIndices

  • 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 class LayerNameCellRenderer extends DefaultTableCellRenderer {
698
699 protected 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.