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

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

findbugs - DB_DUPLICATE_SWITCH_CLAUSES + REC_CATCH_EXCEPTION + UC_USELESS_CONDITION + OS_OPEN_STREAM_EXCEPTION_PATH + ICAST_INTEGER_MULTIPLY_CAST_TO_LONG

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