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

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

fix #12882 - Use LayerManager for LayerListDialog (patch by michael2402)

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