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

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

remove deprecated code

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