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

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

fix #13037 - Small fixes for unit tests (patch by michael2402) - gsoc-core

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