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

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

see #8465 - use diamond operator where applicable

  • Property svn:eol-style set to native
File size: 58.8 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.lang.ref.WeakReference;
19import java.util.ArrayList;
20import java.util.Arrays;
21import java.util.Collections;
22import java.util.List;
23import java.util.concurrent.CopyOnWriteArrayList;
24
25import javax.swing.AbstractAction;
26import javax.swing.Action;
27import javax.swing.DefaultCellEditor;
28import javax.swing.DefaultListSelectionModel;
29import javax.swing.ImageIcon;
30import javax.swing.JCheckBox;
31import javax.swing.JComponent;
32import javax.swing.JLabel;
33import javax.swing.JMenuItem;
34import javax.swing.JPopupMenu;
35import javax.swing.JSlider;
36import javax.swing.JTable;
37import javax.swing.JViewport;
38import javax.swing.KeyStroke;
39import javax.swing.ListSelectionModel;
40import javax.swing.UIManager;
41import javax.swing.event.ChangeEvent;
42import javax.swing.event.ChangeListener;
43import javax.swing.event.ListDataEvent;
44import javax.swing.event.ListSelectionEvent;
45import javax.swing.event.ListSelectionListener;
46import javax.swing.event.TableModelEvent;
47import javax.swing.event.TableModelListener;
48import javax.swing.table.AbstractTableModel;
49import javax.swing.table.DefaultTableCellRenderer;
50import javax.swing.table.TableCellRenderer;
51import javax.swing.table.TableModel;
52
53import org.openstreetmap.josm.Main;
54import org.openstreetmap.josm.actions.MergeLayerAction;
55import org.openstreetmap.josm.gui.MapFrame;
56import org.openstreetmap.josm.gui.MapView;
57import org.openstreetmap.josm.gui.SideButton;
58import org.openstreetmap.josm.gui.help.HelpUtil;
59import org.openstreetmap.josm.gui.layer.JumpToMarkerActions;
60import org.openstreetmap.josm.gui.layer.Layer;
61import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
62import org.openstreetmap.josm.gui.layer.OsmDataLayer;
63import org.openstreetmap.josm.gui.util.GuiHelper;
64import org.openstreetmap.josm.gui.widgets.JosmTextField;
65import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
66import org.openstreetmap.josm.tools.CheckParameterUtil;
67import org.openstreetmap.josm.tools.ImageProvider;
68import org.openstreetmap.josm.tools.InputMapUtils;
69import org.openstreetmap.josm.tools.MultikeyActionsHandler;
70import org.openstreetmap.josm.tools.MultikeyShortcutAction;
71import org.openstreetmap.josm.tools.MultikeyShortcutAction.MultikeyInfo;
72import org.openstreetmap.josm.tools.Shortcut;
73
74/**
75 * This is a toggle dialog which displays the list of layers. Actions allow to
76 * change the ordering of the layers, to hide/show layers, to activate layers,
77 * and to delete layers.
78 *
79 */
80public class LayerListDialog extends ToggleDialog {
81 /** the unique instance of the dialog */
82 private static LayerListDialog instance;
83
84 /**
85 * Creates the instance of the dialog. It's connected to the map frame <code>mapFrame</code>
86 *
87 * @param mapFrame the map frame
88 */
89 public static void createInstance(MapFrame mapFrame) {
90 if (instance != null)
91 throw new IllegalStateException("Dialog was already created");
92 instance = new LayerListDialog(mapFrame);
93 }
94
95 /**
96 * Replies the instance of the dialog
97 *
98 * @return the instance of the dialog
99 * @throws IllegalStateException thrown, if the dialog is not created yet
100 * @see #createInstance(MapFrame)
101 */
102 public static LayerListDialog getInstance() throws IllegalStateException {
103 if (instance == null)
104 throw new IllegalStateException("Dialog not created yet. Invoke createInstance() first");
105 return instance;
106 }
107
108 /** the model for the layer list */
109 private LayerListModel model;
110
111 /** the list of layers (technically its a JTable, but appears like a list) */
112 private LayerList layerList;
113
114 private SideButton opacityButton;
115
116 ActivateLayerAction activateLayerAction;
117 ShowHideLayerAction showHideLayerAction;
118
119 //TODO This duplicates ShowHide actions functionality
120 /** stores which layer index to toggle and executes the ShowHide action if the layer is present */
121 private final class ToggleLayerIndexVisibility extends AbstractAction {
122 int layerIndex = -1;
123 public ToggleLayerIndexVisibility(int layerIndex) {
124 this.layerIndex = layerIndex;
125 }
126 @Override
127 public void actionPerformed(ActionEvent e) {
128 final Layer l = model.getLayer(model.getRowCount() - layerIndex - 1);
129 if(l != null) {
130 l.toggleVisible();
131 }
132 }
133 }
134
135 private final Shortcut[] visibilityToggleShortcuts = new Shortcut[10];
136 private final ToggleLayerIndexVisibility[] visibilityToggleActions = new ToggleLayerIndexVisibility[10];
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 final void createVisibilityToggleShortcuts() {
142 final int[] k = { KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_3, KeyEvent.VK_4,
143 KeyEvent.VK_5, KeyEvent.VK_6, KeyEvent.VK_7, KeyEvent.VK_8,
144 KeyEvent.VK_9, KeyEvent.VK_0 };
145
146 for(int i=0; i < 10; i++) {
147 visibilityToggleShortcuts[i] = Shortcut.registerShortcut("subwindow:layers:toggleLayer" + (i+1),
148 tr("Toggle visibility of layer: {0}", (i+1)), k[i], Shortcut.ALT);
149 visibilityToggleActions[i] = new ToggleLayerIndexVisibility(i);
150 Main.registerActionShortcut(visibilityToggleActions[i], visibilityToggleShortcuts[i]);
151 }
152 }
153
154 /**
155 * Create an layer list and attach it to the given mapView.
156 */
157 protected LayerListDialog(MapFrame mapFrame) {
158 super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."),
159 Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L,
160 Shortcut.ALT_SHIFT), 100, true);
161
162 // create the models
163 //
164 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
165 selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
166 model = new LayerListModel(selectionModel);
167
168 // create the list control
169 //
170 layerList = new LayerList(model);
171 layerList.setSelectionModel(selectionModel);
172 layerList.addMouseListener(new PopupMenuHandler());
173 layerList.setBackground(UIManager.getColor("Button.background"));
174 layerList.putClientProperty("terminateEditOnFocusLost", true);
175 layerList.putClientProperty("JTable.autoStartsEdit", false);
176 layerList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
177 layerList.setTableHeader(null);
178 layerList.setShowGrid(false);
179 layerList.setIntercellSpacing(new Dimension(0, 0));
180 layerList.getColumnModel().getColumn(0).setCellRenderer(new ActiveLayerCellRenderer());
181 layerList.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(new ActiveLayerCheckBox()));
182 layerList.getColumnModel().getColumn(0).setMaxWidth(12);
183 layerList.getColumnModel().getColumn(0).setPreferredWidth(12);
184 layerList.getColumnModel().getColumn(0).setResizable(false);
185 layerList.getColumnModel().getColumn(1).setCellRenderer(new LayerVisibleCellRenderer());
186 layerList.getColumnModel().getColumn(1).setCellEditor(new LayerVisibleCellEditor(new LayerVisibleCheckBox()));
187 layerList.getColumnModel().getColumn(1).setMaxWidth(16);
188 layerList.getColumnModel().getColumn(1).setPreferredWidth(16);
189 layerList.getColumnModel().getColumn(1).setResizable(false);
190 layerList.getColumnModel().getColumn(2).setCellRenderer(new LayerNameCellRenderer());
191 layerList.getColumnModel().getColumn(2).setCellEditor(new LayerNameCellEditor(new JosmTextField()));
192 for (KeyStroke ks : new KeyStroke[] {
193 KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK),
194 KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK),
195 KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK),
196 KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_MASK),
197 KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK),
198 KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK),
199 KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0),
200 KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0),
201 })
202 {
203 layerList.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ks, new Object());
204 }
205
206 // init the model
207 //
208 final MapView mapView = mapFrame.mapView;
209 model.populate();
210 model.setSelectedLayer(mapView.getActiveLayer());
211 model.addLayerListModelListener(
212 new LayerListModelListener() {
213 @Override
214 public void makeVisible(int row, Layer layer) {
215 layerList.scrollToVisible(row, 0);
216 layerList.repaint();
217 }
218 @Override
219 public void refresh() {
220 layerList.repaint();
221 }
222 }
223 );
224
225 // -- move up action
226 MoveUpAction moveUpAction = new MoveUpAction();
227 adaptTo(moveUpAction, model);
228 adaptTo(moveUpAction,selectionModel);
229
230 // -- move down action
231 MoveDownAction moveDownAction = new MoveDownAction();
232 adaptTo(moveDownAction, model);
233 adaptTo(moveDownAction,selectionModel);
234
235 // -- activate action
236 activateLayerAction = new ActivateLayerAction();
237 activateLayerAction.updateEnabledState();
238 MultikeyActionsHandler.getInstance().addAction(activateLayerAction);
239 adaptTo(activateLayerAction, selectionModel);
240
241 JumpToMarkerActions.initialize();
242
243 // -- show hide action
244 showHideLayerAction = new ShowHideLayerAction();
245 MultikeyActionsHandler.getInstance().addAction(showHideLayerAction);
246 adaptTo(showHideLayerAction, selectionModel);
247
248 //-- layer opacity action
249 LayerOpacityAction layerOpacityAction = new LayerOpacityAction();
250 adaptTo(layerOpacityAction, selectionModel);
251 opacityButton = new SideButton(layerOpacityAction, false);
252
253 // -- merge layer action
254 MergeAction mergeLayerAction = new MergeAction();
255 adaptTo(mergeLayerAction, model);
256 adaptTo(mergeLayerAction,selectionModel);
257
258 // -- duplicate layer action
259 DuplicateAction duplicateLayerAction = new DuplicateAction();
260 adaptTo(duplicateLayerAction, model);
261 adaptTo(duplicateLayerAction, selectionModel);
262
263 //-- delete layer action
264 DeleteLayerAction deleteLayerAction = new DeleteLayerAction();
265 layerList.getActionMap().put("deleteLayer", deleteLayerAction);
266 adaptTo(deleteLayerAction, selectionModel);
267 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
268 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"delete"
269 );
270 getActionMap().put("delete", deleteLayerAction);
271
272 // Activate layer on Enter key press
273 InputMapUtils.addEnterAction(layerList, new AbstractAction() {
274 @Override
275 public void actionPerformed(ActionEvent e) {
276 activateLayerAction.actionPerformed(null);
277 layerList.requestFocus();
278 }
279 });
280
281 // Show/Activate layer on Enter key press
282 InputMapUtils.addSpacebarAction(layerList, showHideLayerAction);
283
284 createLayout(layerList, true, Arrays.asList(new SideButton[] {
285 new SideButton(moveUpAction, false),
286 new SideButton(moveDownAction, false),
287 new SideButton(activateLayerAction, false),
288 new SideButton(showHideLayerAction, false),
289 opacityButton,
290 new SideButton(mergeLayerAction, false),
291 new SideButton(duplicateLayerAction, false),
292 new SideButton(deleteLayerAction, false)
293 }));
294
295 createVisibilityToggleShortcuts();
296 }
297
298 @Override
299 public void showNotify() {
300 MapView.addLayerChangeListener(activateLayerAction);
301 MapView.addLayerChangeListener(model);
302 model.populate();
303 }
304
305 @Override
306 public void hideNotify() {
307 MapView.removeLayerChangeListener(model);
308 MapView.removeLayerChangeListener(activateLayerAction);
309 }
310
311 public LayerListModel getModel() {
312 return model;
313 }
314
315 protected interface IEnabledStateUpdating {
316 void updateEnabledState();
317 }
318
319 /**
320 * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that
321 * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()}
322 * on every {@link ListSelectionEvent}.
323 *
324 * @param listener the listener
325 * @param listSelectionModel the source emitting {@link ListSelectionEvent}s
326 */
327 protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) {
328 listSelectionModel.addListSelectionListener(
329 new ListSelectionListener() {
330 @Override
331 public void valueChanged(ListSelectionEvent e) {
332 listener.updateEnabledState();
333 }
334 }
335 );
336 }
337
338 /**
339 * Wires <code>listener</code> to <code>listModel</code> in such a way, that
340 * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()}
341 * on every {@link ListDataEvent}.
342 *
343 * @param listener the listener
344 * @param listModel the source emitting {@link ListDataEvent}s
345 */
346 protected void adaptTo(final IEnabledStateUpdating listener, LayerListModel listModel) {
347 listModel.addTableModelListener(
348 new TableModelListener() {
349
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 /**
371 * The action to delete the currently selected layer
372 */
373 public final class DeleteLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
374 /**
375 * Creates a {@link DeleteLayerAction} which will delete the currently
376 * selected layers in the layer dialog.
377 *
378 */
379 public DeleteLayerAction() {
380 putValue(SMALL_ICON,ImageProvider.get("dialogs", "delete"));
381 putValue(SHORT_DESCRIPTION, tr("Delete the selected layers."));
382 putValue(NAME, tr("Delete"));
383 putValue("help", HelpUtil.ht("/Dialog/LayerList#DeleteLayer"));
384 updateEnabledState();
385 }
386
387 @Override
388 public void actionPerformed(ActionEvent e) {
389 List<Layer> selectedLayers = getModel().getSelectedLayers();
390 if (selectedLayers.isEmpty())
391 return;
392 if (!Main.saveUnsavedModifications(selectedLayers, false))
393 return;
394 for (Layer l: selectedLayers) {
395 Main.main.removeLayer(l);
396 }
397 }
398
399 @Override
400 public void updateEnabledState() {
401 setEnabled(! getModel().getSelectedLayers().isEmpty());
402 }
403
404 @Override
405 public Component createMenuComponent() {
406 return new JMenuItem(this);
407 }
408
409 @Override
410 public boolean supportLayers(List<Layer> layers) {
411 return true;
412 }
413
414 @Override
415 public boolean equals(Object obj) {
416 return obj instanceof DeleteLayerAction;
417 }
418
419 @Override
420 public int hashCode() {
421 return getClass().hashCode();
422 }
423 }
424
425 public final class ShowHideLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction, MultikeyShortcutAction {
426
427 private WeakReference<Layer> lastLayer;
428 private Shortcut multikeyShortcut;
429
430 /**
431 * Creates a {@link ShowHideLayerAction} which will toggle the visibility of
432 * the currently selected layers
433 *
434 */
435 public ShowHideLayerAction(boolean init) {
436 putValue(NAME, tr("Show/hide"));
437 putValue(SMALL_ICON, ImageProvider.get("dialogs", "showhide"));
438 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the selected layer."));
439 putValue("help", HelpUtil.ht("/Dialog/LayerList#ShowHideLayer"));
440 multikeyShortcut = Shortcut.registerShortcut("core_multikey:showHideLayer", tr("Multikey: {0}",
441 tr("Show/hide layer")), KeyEvent.VK_S, Shortcut.SHIFT);
442 multikeyShortcut.setAccelerator(this);
443 if (init) {
444 updateEnabledState();
445 }
446 }
447
448 /**
449 * Constructs a new {@code ShowHideLayerAction}.
450 */
451 public ShowHideLayerAction() {
452 this(true);
453 }
454
455 @Override
456 public Shortcut getMultikeyShortcut() {
457 return multikeyShortcut;
458 }
459
460 @Override
461 public void actionPerformed(ActionEvent e) {
462 for(Layer l : model.getSelectedLayers()) {
463 l.toggleVisible();
464 }
465 }
466
467 @Override
468 public void executeMultikeyAction(int index, boolean repeat) {
469 Layer l = LayerListDialog.getLayerForIndex(index);
470 if (l != null) {
471 l.toggleVisible();
472 lastLayer = new WeakReference<>(l);
473 } else if (repeat && lastLayer != null) {
474 l = lastLayer.get();
475 if (LayerListDialog.isLayerValid(l)) {
476 l.toggleVisible();
477 }
478 }
479 }
480
481 @Override
482 public void updateEnabledState() {
483 setEnabled(!model.getSelectedLayers().isEmpty());
484 }
485
486 @Override
487 public Component createMenuComponent() {
488 return new JMenuItem(this);
489 }
490
491 @Override
492 public boolean supportLayers(List<Layer> layers) {
493 return true;
494 }
495
496 @Override
497 public boolean equals(Object obj) {
498 return obj instanceof ShowHideLayerAction;
499 }
500
501 @Override
502 public int hashCode() {
503 return getClass().hashCode();
504 }
505
506 @Override
507 public List<MultikeyInfo> getMultikeyCombinations() {
508 return LayerListDialog.getLayerInfoByClass(Layer.class);
509 }
510
511 @Override
512 public MultikeyInfo getLastMultikeyAction() {
513 if (lastLayer != null)
514 return LayerListDialog.getLayerInfo(lastLayer.get());
515 return null;
516 }
517 }
518
519 public final class LayerOpacityAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
520 private Layer layer;
521 private JPopupMenu popup;
522 private JSlider slider = new JSlider(JSlider.VERTICAL);
523
524 /**
525 * Creates a {@link LayerOpacityAction} which allows to chenge the
526 * opacity of one or more layers.
527 *
528 * @param layer the layer. Must not be null.
529 * @exception IllegalArgumentException thrown, if layer is null
530 */
531 public LayerOpacityAction(Layer layer) throws IllegalArgumentException {
532 this();
533 putValue(NAME, tr("Opacity"));
534 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
535 this.layer = layer;
536 updateEnabledState();
537 }
538
539 /**
540 * Creates a {@link ShowHideLayerAction} which will toggle the visibility of
541 * the currently selected layers
542 *
543 */
544 public LayerOpacityAction() {
545 putValue(NAME, tr("Opacity"));
546 putValue(SHORT_DESCRIPTION, tr("Adjust opacity of the layer."));
547 putValue(SMALL_ICON, ImageProvider.get("dialogs/layerlist", "transparency"));
548 updateEnabledState();
549
550 popup = new JPopupMenu();
551 slider.addChangeListener(new ChangeListener() {
552 @Override
553 public void stateChanged(ChangeEvent e) {
554 setOpacity((double)slider.getValue()/100);
555 }
556 });
557 popup.add(slider);
558 }
559
560 private void setOpacity(double value) {
561 if (!isEnabled()) return;
562 if (layer != null) {
563 layer.setOpacity(value);
564 } else {
565 for(Layer layer: model.getSelectedLayers()) {
566 layer.setOpacity(value);
567 }
568 }
569 }
570
571 private double getOpacity() {
572 if (layer != null)
573 return layer.getOpacity();
574 else {
575 double opacity = 0;
576 List<Layer> layers = model.getSelectedLayers();
577 for(Layer layer: layers) {
578 opacity += layer.getOpacity();
579 }
580 return opacity / layers.size();
581 }
582 }
583
584 @Override
585 public void actionPerformed(ActionEvent e) {
586 slider.setValue((int)Math.round(getOpacity()*100));
587 if (e.getSource() == opacityButton) {
588 popup.show(opacityButton, 0, opacityButton.getHeight());
589 } else {
590 // Action can be trigger either by opacity button or by popup menu (in case toggle buttons are hidden).
591 // In that case, show it in the middle of screen (because opacityButton is not visible)
592 popup.show(Main.parent, Main.parent.getWidth() / 2, (Main.parent.getHeight() - popup.getHeight()) / 2);
593 }
594 }
595
596 @Override
597 public void updateEnabledState() {
598 if (layer == null) {
599 setEnabled(! getModel().getSelectedLayers().isEmpty());
600 } else {
601 setEnabled(true);
602 }
603 }
604
605 @Override
606 public Component createMenuComponent() {
607 return new JMenuItem(this);
608 }
609
610 @Override
611 public boolean supportLayers(List<Layer> layers) {
612 return true;
613 }
614
615 @Override
616 public boolean equals(Object obj) {
617 return obj instanceof LayerOpacityAction;
618 }
619
620 @Override
621 public int hashCode() {
622 return getClass().hashCode();
623 }
624 }
625
626 /**
627 * The action to activate the currently selected layer
628 */
629
630 public final class ActivateLayerAction extends AbstractAction implements IEnabledStateUpdating, MapView.LayerChangeListener, MultikeyShortcutAction{
631 private Layer layer;
632 private Shortcut multikeyShortcut;
633
634 public ActivateLayerAction(Layer layer) {
635 this();
636 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
637 this.layer = layer;
638 putValue(NAME, tr("Activate"));
639 updateEnabledState();
640 }
641
642 /**
643 * Constructs a new {@code ActivateLayerAction}.
644 */
645 public ActivateLayerAction() {
646 putValue(NAME, tr("Activate"));
647 putValue(SMALL_ICON, ImageProvider.get("dialogs", "activate"));
648 putValue(SHORT_DESCRIPTION, tr("Activate the selected layer"));
649 multikeyShortcut = Shortcut.registerShortcut("core_multikey:activateLayer", tr("Multikey: {0}",
650 tr("Activate layer")), KeyEvent.VK_A, Shortcut.SHIFT);
651 multikeyShortcut.setAccelerator(this);
652 putValue("help", HelpUtil.ht("/Dialog/LayerList#ActivateLayer"));
653 }
654
655 @Override
656 public Shortcut getMultikeyShortcut() {
657 return multikeyShortcut;
658 }
659
660 @Override
661 public void actionPerformed(ActionEvent e) {
662 Layer toActivate;
663 if (layer != null) {
664 toActivate = layer;
665 } else {
666 toActivate = model.getSelectedLayers().get(0);
667 }
668 execute(toActivate);
669 }
670
671 private void execute(Layer layer) {
672 // model is going to be updated via LayerChangeListener
673 // and PropertyChangeEvents
674 Main.map.mapView.setActiveLayer(layer);
675 layer.setVisible(true);
676 }
677
678 protected boolean isActiveLayer(Layer layer) {
679 if (!Main.isDisplayingMapView()) return false;
680 return Main.map.mapView.getActiveLayer() == layer;
681 }
682
683 @Override
684 public void updateEnabledState() {
685 GuiHelper.runInEDTAndWait(new Runnable() {
686 @Override
687 public void run() {
688 if (layer == null) {
689 if (getModel().getSelectedLayers().size() != 1) {
690 setEnabled(false);
691 return;
692 }
693 Layer selectedLayer = getModel().getSelectedLayers().get(0);
694 setEnabled(!isActiveLayer(selectedLayer));
695 } else {
696 setEnabled(!isActiveLayer(layer));
697 }
698 }
699 });
700 }
701
702 @Override
703 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
704 updateEnabledState();
705 }
706 @Override
707 public void layerAdded(Layer newLayer) {
708 updateEnabledState();
709 }
710 @Override
711 public void layerRemoved(Layer oldLayer) {
712 updateEnabledState();
713 }
714
715 @Override
716 public void executeMultikeyAction(int index, boolean repeat) {
717 Layer l = LayerListDialog.getLayerForIndex(index);
718 if (l != null) {
719 execute(l);
720 }
721 }
722
723 @Override
724 public List<MultikeyInfo> getMultikeyCombinations() {
725 return LayerListDialog.getLayerInfoByClass(Layer.class);
726 }
727
728 @Override
729 public MultikeyInfo getLastMultikeyAction() {
730 return null; // Repeating action doesn't make much sense for activating
731 }
732 }
733
734 /**
735 * The action to merge the currently selected layer into another layer.
736 */
737 public final class MergeAction extends AbstractAction implements IEnabledStateUpdating {
738 private Layer layer;
739
740 public MergeAction(Layer layer) throws IllegalArgumentException {
741 this();
742 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
743 this.layer = layer;
744 putValue(NAME, tr("Merge"));
745 updateEnabledState();
746 }
747
748 /**
749 * Constructs a new {@code MergeAction}.
750 */
751 public MergeAction() {
752 putValue(NAME, tr("Merge"));
753 putValue(SMALL_ICON, ImageProvider.get("dialogs", "mergedown"));
754 putValue(SHORT_DESCRIPTION, tr("Merge this layer into another layer"));
755 putValue("help", HelpUtil.ht("/Dialog/LayerList#MergeLayer"));
756 updateEnabledState();
757 }
758
759 @Override
760 public void actionPerformed(ActionEvent e) {
761 if (layer != null) {
762 Main.main.menu.merge.merge(layer);
763 } else {
764 if (getModel().getSelectedLayers().size() == 1) {
765 Layer selectedLayer = getModel().getSelectedLayers().get(0);
766 Main.main.menu.merge.merge(selectedLayer);
767 } else {
768 Main.main.menu.merge.merge(getModel().getSelectedLayers());
769 }
770 }
771 }
772
773 protected boolean isActiveLayer(Layer layer) {
774 if (!Main.isDisplayingMapView()) return false;
775 return Main.map.mapView.getActiveLayer() == layer;
776 }
777
778 @Override
779 public void updateEnabledState() {
780 if (layer == null) {
781 if (getModel().getSelectedLayers().isEmpty()) {
782 setEnabled(false);
783 } else if (getModel().getSelectedLayers().size() > 1) {
784 Layer firstLayer = getModel().getSelectedLayers().get(0);
785 for (Layer l: getModel().getSelectedLayers()) {
786 if (l != firstLayer && (!l.isMergable(firstLayer) || !firstLayer.isMergable(l))) {
787 setEnabled(false);
788 return;
789 }
790 }
791 setEnabled(true);
792 } else {
793 Layer selectedLayer = getModel().getSelectedLayers().get(0);
794 List<Layer> targets = getModel().getPossibleMergeTargets(selectedLayer);
795 setEnabled(!targets.isEmpty());
796 }
797 } else {
798 List<Layer> targets = getModel().getPossibleMergeTargets(layer);
799 setEnabled(!targets.isEmpty());
800 }
801 }
802 }
803
804 /**
805 * The action to merge the currently selected layer into another layer.
806 */
807 public final class DuplicateAction extends AbstractAction implements IEnabledStateUpdating {
808 private Layer layer;
809
810 public DuplicateAction(Layer layer) throws IllegalArgumentException {
811 this();
812 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
813 this.layer = layer;
814 updateEnabledState();
815 }
816
817 /**
818 * Constructs a new {@code DuplicateAction}.
819 */
820 public DuplicateAction() {
821 putValue(NAME, tr("Duplicate"));
822 putValue(SMALL_ICON, ImageProvider.get("dialogs", "duplicatelayer"));
823 putValue(SHORT_DESCRIPTION, tr("Duplicate this layer"));
824 putValue("help", HelpUtil.ht("/Dialog/LayerList#DuplicateLayer"));
825 updateEnabledState();
826 }
827
828 private void duplicate(Layer layer) {
829 if (!Main.isDisplayingMapView())
830 return;
831
832 List<String> layerNames = new ArrayList<>();
833 for (Layer l: Main.map.mapView.getAllLayers()) {
834 layerNames.add(l.getName());
835 }
836 if (layer instanceof OsmDataLayer) {
837 OsmDataLayer oldLayer = (OsmDataLayer)layer;
838 // Translators: "Copy of {layer name}"
839 String newName = tr("Copy of {0}", oldLayer.getName());
840 int i = 2;
841 while (layerNames.contains(newName)) {
842 // Translators: "Copy {number} of {layer name}"
843 newName = tr("Copy {1} of {0}", oldLayer.getName(), i);
844 i++;
845 }
846 Main.main.addLayer(new OsmDataLayer(oldLayer.data.clone(), newName, null));
847 }
848 }
849
850 @Override
851 public void actionPerformed(ActionEvent e) {
852 if (layer != null) {
853 duplicate(layer);
854 } else {
855 duplicate(getModel().getSelectedLayers().get(0));
856 }
857 }
858
859 protected boolean isActiveLayer(Layer layer) {
860 if (!Main.isDisplayingMapView())
861 return false;
862 return Main.map.mapView.getActiveLayer() == layer;
863 }
864
865 @Override
866 public void updateEnabledState() {
867 if (layer == null) {
868 if (getModel().getSelectedLayers().size() == 1) {
869 setEnabled(getModel().getSelectedLayers().get(0) instanceof OsmDataLayer);
870 } else {
871 setEnabled(false);
872 }
873 } else {
874 setEnabled(layer instanceof OsmDataLayer);
875 }
876 }
877 }
878
879 private static class ActiveLayerCheckBox extends JCheckBox {
880 public ActiveLayerCheckBox() {
881 setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
882 ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank");
883 ImageIcon active = ImageProvider.get("dialogs/layerlist", "active");
884 setIcon(blank);
885 setSelectedIcon(active);
886 setRolloverIcon(blank);
887 setRolloverSelectedIcon(active);
888 setPressedIcon(ImageProvider.get("dialogs/layerlist", "active-pressed"));
889 }
890 }
891
892 private static class LayerVisibleCheckBox extends JCheckBox {
893 private final ImageIcon icon_eye;
894 private final ImageIcon icon_eye_translucent;
895 private boolean isTranslucent;
896 public LayerVisibleCheckBox() {
897 setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
898 icon_eye = ImageProvider.get("dialogs/layerlist", "eye");
899 icon_eye_translucent = ImageProvider.get("dialogs/layerlist", "eye-translucent");
900 setIcon(ImageProvider.get("dialogs/layerlist", "eye-off"));
901 setPressedIcon(ImageProvider.get("dialogs/layerlist", "eye-pressed"));
902 setSelectedIcon(icon_eye);
903 isTranslucent = false;
904 }
905
906 public void setTranslucent(boolean isTranslucent) {
907 if (this.isTranslucent == isTranslucent) return;
908 if (isTranslucent) {
909 setSelectedIcon(icon_eye_translucent);
910 } else {
911 setSelectedIcon(icon_eye);
912 }
913 this.isTranslucent = isTranslucent;
914 }
915
916 public void updateStatus(Layer layer) {
917 boolean visible = layer.isVisible();
918 setSelected(visible);
919 setTranslucent(layer.getOpacity()<1.0);
920 setToolTipText(visible ? tr("layer is currently visible (click to hide layer)") : tr("layer is currently hidden (click to show layer)"));
921 }
922 }
923
924 private static class ActiveLayerCellRenderer implements TableCellRenderer {
925 JCheckBox cb;
926 public ActiveLayerCellRenderer() {
927 cb = new ActiveLayerCheckBox();
928 }
929
930 @Override
931 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
932 boolean active = value != null && (Boolean) value;
933 cb.setSelected(active);
934 cb.setToolTipText(active ? tr("this layer is the active layer") : tr("this layer is not currently active (click to activate)"));
935 return cb;
936 }
937 }
938
939 private static class LayerVisibleCellRenderer implements TableCellRenderer {
940 LayerVisibleCheckBox cb;
941 public LayerVisibleCellRenderer() {
942 this.cb = new LayerVisibleCheckBox();
943 }
944
945 @Override
946 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
947 if (value != null) {
948 cb.updateStatus((Layer)value);
949 }
950 return cb;
951 }
952 }
953
954 private static class LayerVisibleCellEditor extends DefaultCellEditor {
955 LayerVisibleCheckBox cb;
956 public LayerVisibleCellEditor(LayerVisibleCheckBox cb) {
957 super(cb);
958 this.cb = cb;
959 }
960
961 @Override
962 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
963 cb.updateStatus((Layer)value);
964 return cb;
965 }
966 }
967
968 private class LayerNameCellRenderer extends DefaultTableCellRenderer {
969
970 protected boolean isActiveLayer(Layer layer) {
971 if (!Main.isDisplayingMapView()) return false;
972 return Main.map.mapView.getActiveLayer() == layer;
973 }
974
975 @Override
976 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
977 if (value == null)
978 return this;
979 Layer layer = (Layer)value;
980 JLabel label = (JLabel)super.getTableCellRendererComponent(table,
981 layer.getName(), isSelected, hasFocus, row, column);
982 if (isActiveLayer(layer)) {
983 label.setFont(label.getFont().deriveFont(Font.BOLD));
984 }
985 if(Main.pref.getBoolean("dialog.layer.colorname", true)) {
986 Color c = layer.getColor(false);
987 if(c != null) {
988 Color oc = null;
989 for(Layer l : model.getLayers()) {
990 oc = l.getColor(false);
991 if(oc != null) {
992 if(oc.equals(c)) {
993 oc = null;
994 } else {
995 break;
996 }
997 }
998 }
999 /* not more than one color, don't use coloring */
1000 if(oc == null) {
1001 c = null;
1002 }
1003 }
1004 if(c == null) {
1005 c = Main.pref.getUIColor(isSelected ? "Table.selectionForeground" : "Table.foreground");
1006 }
1007 label.setForeground(c);
1008 }
1009 label.setIcon(layer.getIcon());
1010 label.setToolTipText(layer.getToolTipText());
1011 return label;
1012 }
1013 }
1014
1015 private static class LayerNameCellEditor extends DefaultCellEditor {
1016 public LayerNameCellEditor(JosmTextField tf) {
1017 super(tf);
1018 }
1019
1020 @Override
1021 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
1022 JosmTextField tf = (JosmTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
1023 tf.setText(value == null ? "" : ((Layer) value).getName());
1024 return tf;
1025 }
1026 }
1027
1028 class PopupMenuHandler extends PopupMenuLauncher {
1029 @Override public void showMenu(MouseEvent evt) {
1030 Layer layer = getModel().getLayer(layerList.getSelectedRow());
1031 menu = new LayerListPopup(getModel().getSelectedLayers(), layer);
1032 super.showMenu(evt);
1033 }
1034 }
1035
1036 /**
1037 * The action to move up the currently selected entries in the list.
1038 */
1039 class MoveUpAction extends AbstractAction implements IEnabledStateUpdating{
1040 public MoveUpAction() {
1041 putValue(NAME, tr("Move up"));
1042 putValue(SMALL_ICON, ImageProvider.get("dialogs", "up"));
1043 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row up."));
1044 updateEnabledState();
1045 }
1046
1047 @Override
1048 public void updateEnabledState() {
1049 setEnabled(model.canMoveUp());
1050 }
1051
1052 @Override
1053 public void actionPerformed(ActionEvent e) {
1054 model.moveUp();
1055 }
1056 }
1057
1058 /**
1059 * The action to move down the currently selected entries in the list.
1060 */
1061 class MoveDownAction extends AbstractAction implements IEnabledStateUpdating {
1062 public MoveDownAction() {
1063 putValue(NAME, tr("Move down"));
1064 putValue(SMALL_ICON, ImageProvider.get("dialogs", "down"));
1065 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row down."));
1066 updateEnabledState();
1067 }
1068
1069 @Override
1070 public void updateEnabledState() {
1071 setEnabled(model.canMoveDown());
1072 }
1073
1074 @Override
1075 public void actionPerformed(ActionEvent e) {
1076 model.moveDown();
1077 }
1078 }
1079
1080 /**
1081 * Observer interface to be implemented by views using {@link LayerListModel}
1082 *
1083 */
1084 public interface LayerListModelListener {
1085 public void makeVisible(int index, Layer layer);
1086 public void refresh();
1087 }
1088
1089 /**
1090 * The layer list model. The model manages a list of layers and provides methods for
1091 * moving layers up and down, for toggling their visibility, and for activating a layer.
1092 *
1093 * The model is a {@link TableModel} and it provides a {@link ListSelectionModel}. It expects
1094 * to be configured with a {@link DefaultListSelectionModel}. The selection model is used
1095 * to update the selection state of views depending on messages sent to the model.
1096 *
1097 * The model manages a list of {@link LayerListModelListener} which are mainly notified if
1098 * the model requires views to make a specific list entry visible.
1099 *
1100 * It also listens to {@link PropertyChangeEvent}s of every {@link Layer} it manages, in particular to
1101 * the properties {@link Layer#VISIBLE_PROP} and {@link Layer#NAME_PROP}.
1102 */
1103 public final class LayerListModel extends AbstractTableModel implements MapView.LayerChangeListener, PropertyChangeListener {
1104 /** manages list selection state*/
1105 private DefaultListSelectionModel selectionModel;
1106 private CopyOnWriteArrayList<LayerListModelListener> listeners;
1107
1108 /**
1109 * constructor
1110 *
1111 * @param selectionModel the list selection model
1112 */
1113 private LayerListModel(DefaultListSelectionModel selectionModel) {
1114 this.selectionModel = selectionModel;
1115 listeners = new CopyOnWriteArrayList<>();
1116 }
1117
1118 /**
1119 * Adds a listener to this model
1120 *
1121 * @param listener the listener
1122 */
1123 public void addLayerListModelListener(LayerListModelListener listener) {
1124 if (listener != null) {
1125 listeners.addIfAbsent(listener);
1126 }
1127 }
1128
1129 /**
1130 * removes a listener from this model
1131 * @param listener the listener
1132 *
1133 */
1134 public void removeLayerListModelListener(LayerListModelListener listener) {
1135 listeners.remove(listener);
1136 }
1137
1138 /**
1139 * Fires a make visible event to listeners
1140 *
1141 * @param index the index of the row to make visible
1142 * @param layer the layer at this index
1143 * @see LayerListModelListener#makeVisible(int, Layer)
1144 */
1145 protected void fireMakeVisible(int index, Layer layer) {
1146 for (LayerListModelListener listener : listeners) {
1147 listener.makeVisible(index, layer);
1148 }
1149 }
1150
1151 /**
1152 * Fires a refresh event to listeners of this model
1153 *
1154 * @see LayerListModelListener#refresh()
1155 */
1156 protected void fireRefresh() {
1157 for (LayerListModelListener listener : listeners) {
1158 listener.refresh();
1159 }
1160 }
1161
1162 /**
1163 * Populates the model with the current layers managed by
1164 * {@link MapView}.
1165 *
1166 */
1167 public void populate() {
1168 for (Layer layer: getLayers()) {
1169 // make sure the model is registered exactly once
1170 //
1171 layer.removePropertyChangeListener(this);
1172 layer.addPropertyChangeListener(this);
1173 }
1174 fireTableDataChanged();
1175 }
1176
1177 /**
1178 * Marks <code>layer</code> as selected layer. Ignored, if
1179 * layer is null.
1180 *
1181 * @param layer the layer.
1182 */
1183 public void setSelectedLayer(Layer layer) {
1184 if (layer == null)
1185 return;
1186 int idx = getLayers().indexOf(layer);
1187 if (idx >= 0) {
1188 selectionModel.setSelectionInterval(idx, idx);
1189 }
1190 ensureSelectedIsVisible();
1191 }
1192
1193 /**
1194 * Replies the list of currently selected layers. Never null, but may
1195 * be empty.
1196 *
1197 * @return the list of currently selected layers. Never null, but may
1198 * be empty.
1199 */
1200 public List<Layer> getSelectedLayers() {
1201 List<Layer> selected = new ArrayList<>();
1202 for (int i=0; i<getLayers().size(); i++) {
1203 if (selectionModel.isSelectedIndex(i)) {
1204 selected.add(getLayers().get(i));
1205 }
1206 }
1207 return selected;
1208 }
1209
1210 /**
1211 * Replies a the list of indices of the selected rows. Never null,
1212 * but may be empty.
1213 *
1214 * @return the list of indices of the selected rows. Never null,
1215 * but may be empty.
1216 */
1217 public List<Integer> getSelectedRows() {
1218 List<Integer> selected = new ArrayList<>();
1219 for (int i=0; i<getLayers().size();i++) {
1220 if (selectionModel.isSelectedIndex(i)) {
1221 selected.add(i);
1222 }
1223 }
1224 return selected;
1225 }
1226
1227 /**
1228 * Invoked if a layer managed by {@link MapView} is removed
1229 *
1230 * @param layer the layer which is removed
1231 */
1232 protected void onRemoveLayer(Layer layer) {
1233 if (layer == null)
1234 return;
1235 layer.removePropertyChangeListener(this);
1236 final int size = getRowCount();
1237 final List<Integer> rows = getSelectedRows();
1238 GuiHelper.runInEDTAndWait(new Runnable() {
1239 @Override
1240 public void run() {
1241 if (rows.isEmpty() && size > 0) {
1242 selectionModel.setSelectionInterval(size-1, size-1);
1243 }
1244 fireTableDataChanged();
1245 fireRefresh();
1246 ensureActiveSelected();
1247 }
1248 });
1249 }
1250
1251 /**
1252 * Invoked when a layer managed by {@link MapView} is added
1253 *
1254 * @param layer the layer
1255 */
1256 protected void onAddLayer(Layer layer) {
1257 if (layer == null) return;
1258 layer.addPropertyChangeListener(this);
1259 fireTableDataChanged();
1260 int idx = getLayers().indexOf(layer);
1261 layerList.setRowHeight(idx, Math.max(16, layer.getIcon().getIconHeight()));
1262 selectionModel.setSelectionInterval(idx, idx);
1263 ensureSelectedIsVisible();
1264 }
1265
1266 /**
1267 * Replies the first layer. Null if no layers are present
1268 *
1269 * @return the first layer. Null if no layers are present
1270 */
1271 public Layer getFirstLayer() {
1272 if (getRowCount() == 0) return null;
1273 return getLayers().get(0);
1274 }
1275
1276 /**
1277 * Replies the layer at position <code>index</code>
1278 *
1279 * @param index the index
1280 * @return the layer at position <code>index</code>. Null,
1281 * if index is out of range.
1282 */
1283 public Layer getLayer(int index) {
1284 if (index < 0 || index >= getRowCount())
1285 return null;
1286 return getLayers().get(index);
1287 }
1288
1289 /**
1290 * Replies true if the currently selected layers can move up
1291 * by one position
1292 *
1293 * @return true if the currently selected layers can move up
1294 * by one position
1295 */
1296 public boolean canMoveUp() {
1297 List<Integer> sel = getSelectedRows();
1298 return !sel.isEmpty() && sel.get(0) > 0;
1299 }
1300
1301 /**
1302 * Move up the currently selected layers by one position
1303 *
1304 */
1305 public void moveUp() {
1306 if (!canMoveUp()) return;
1307 List<Integer> sel = getSelectedRows();
1308 for (int row : sel) {
1309 Layer l1 = getLayers().get(row);
1310 Layer l2 = getLayers().get(row-1);
1311 Main.map.mapView.moveLayer(l2,row);
1312 Main.map.mapView.moveLayer(l1, row-1);
1313 }
1314 fireTableDataChanged();
1315 selectionModel.clearSelection();
1316 for (int row : sel) {
1317 selectionModel.addSelectionInterval(row-1, row-1);
1318 }
1319 ensureSelectedIsVisible();
1320 }
1321
1322 /**
1323 * Replies true if the currently selected layers can move down
1324 * by one position
1325 *
1326 * @return true if the currently selected layers can move down
1327 * by one position
1328 */
1329 public boolean canMoveDown() {
1330 List<Integer> sel = getSelectedRows();
1331 return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1;
1332 }
1333
1334 /**
1335 * Move down the currently selected layers by one position
1336 *
1337 */
1338 public void moveDown() {
1339 if (!canMoveDown()) return;
1340 List<Integer> sel = getSelectedRows();
1341 Collections.reverse(sel);
1342 for (int row : sel) {
1343 Layer l1 = getLayers().get(row);
1344 Layer l2 = getLayers().get(row+1);
1345 Main.map.mapView.moveLayer(l1, row+1);
1346 Main.map.mapView.moveLayer(l2, row);
1347 }
1348 fireTableDataChanged();
1349 selectionModel.clearSelection();
1350 for (int row : sel) {
1351 selectionModel.addSelectionInterval(row+1, row+1);
1352 }
1353 ensureSelectedIsVisible();
1354 }
1355
1356 /**
1357 * Make sure the first of the selected layers is visible in the
1358 * views of this model.
1359 *
1360 */
1361 protected void ensureSelectedIsVisible() {
1362 int index = selectionModel.getMinSelectionIndex();
1363 if (index < 0) return;
1364 if (index >= getLayers().size()) return;
1365 Layer layer = getLayers().get(index);
1366 fireMakeVisible(index, layer);
1367 }
1368
1369 /**
1370 * Replies a list of layers which are possible merge targets
1371 * for <code>source</code>
1372 *
1373 * @param source the source layer
1374 * @return a list of layers which are possible merge targets
1375 * for <code>source</code>. Never null, but can be empty.
1376 */
1377 public List<Layer> getPossibleMergeTargets(Layer source) {
1378 List<Layer> targets = new ArrayList<>();
1379 if (source == null)
1380 return targets;
1381 for (Layer target : getLayers()) {
1382 if (source == target) {
1383 continue;
1384 }
1385 if (target.isMergable(source) && source.isMergable(target)) {
1386 targets.add(target);
1387 }
1388 }
1389 return targets;
1390 }
1391
1392 /**
1393 * Replies the list of layers currently managed by {@link MapView}.
1394 * Never null, but can be empty.
1395 *
1396 * @return the list of layers currently managed by {@link MapView}.
1397 * Never null, but can be empty.
1398 */
1399 public List<Layer> getLayers() {
1400 if (!Main.isDisplayingMapView())
1401 return Collections.<Layer>emptyList();
1402 return Main.map.mapView.getAllLayersAsList();
1403 }
1404
1405 /**
1406 * Ensures that at least one layer is selected in the layer dialog
1407 *
1408 */
1409 protected void ensureActiveSelected() {
1410 if (getLayers().isEmpty())
1411 return;
1412 final Layer activeLayer = getActiveLayer();
1413 if (activeLayer != null) {
1414 // there's an active layer - select it and make it
1415 // visible
1416 int idx = getLayers().indexOf(activeLayer);
1417 selectionModel.setSelectionInterval(idx, idx);
1418 ensureSelectedIsVisible();
1419 } else {
1420 // no active layer - select the first one and make
1421 // it visible
1422 selectionModel.setSelectionInterval(0, 0);
1423 ensureSelectedIsVisible();
1424 }
1425 }
1426
1427 /**
1428 * Replies the active layer. null, if no active layer is available
1429 *
1430 * @return the active layer. null, if no active layer is available
1431 */
1432 protected Layer getActiveLayer() {
1433 if (!Main.isDisplayingMapView()) return null;
1434 return Main.map.mapView.getActiveLayer();
1435 }
1436
1437 /* ------------------------------------------------------------------------------ */
1438 /* Interface TableModel */
1439 /* ------------------------------------------------------------------------------ */
1440
1441 @Override
1442 public int getRowCount() {
1443 List<Layer> layers = getLayers();
1444 if (layers == null) return 0;
1445 return layers.size();
1446 }
1447
1448 @Override
1449 public int getColumnCount() {
1450 return 3;
1451 }
1452
1453 @Override
1454 public Object getValueAt(int row, int col) {
1455 if (row >= 0 && row < getLayers().size()) {
1456 switch (col) {
1457 case 0: return getLayers().get(row) == getActiveLayer();
1458 case 1: return getLayers().get(row);
1459 case 2: return getLayers().get(row);
1460 default: throw new RuntimeException();
1461 }
1462 }
1463 return null;
1464 }
1465
1466 @Override
1467 public boolean isCellEditable(int row, int col) {
1468 if (col == 0 && getActiveLayer() == getLayers().get(row))
1469 return false;
1470 return true;
1471 }
1472
1473 @Override
1474 public void setValueAt(Object value, int row, int col) {
1475 Layer l = getLayers().get(row);
1476 switch (col) {
1477 case 0:
1478 Main.map.mapView.setActiveLayer(l);
1479 l.setVisible(true);
1480 break;
1481 case 1:
1482 l.setVisible((Boolean) value);
1483 break;
1484 case 2:
1485 l.setName((String) value);
1486 break;
1487 default: throw new RuntimeException();
1488 }
1489 fireTableCellUpdated(row, col);
1490 }
1491
1492 /* ------------------------------------------------------------------------------ */
1493 /* Interface LayerChangeListener */
1494 /* ------------------------------------------------------------------------------ */
1495 @Override
1496 public void activeLayerChange(final Layer oldLayer, final Layer newLayer) {
1497 GuiHelper.runInEDTAndWait(new Runnable() {
1498 @Override
1499 public void run() {
1500 if (oldLayer != null) {
1501 int idx = getLayers().indexOf(oldLayer);
1502 if (idx >= 0) {
1503 fireTableRowsUpdated(idx,idx);
1504 }
1505 }
1506
1507 if (newLayer != null) {
1508 int idx = getLayers().indexOf(newLayer);
1509 if (idx >= 0) {
1510 fireTableRowsUpdated(idx,idx);
1511 }
1512 }
1513 ensureActiveSelected();
1514 }
1515 });
1516 }
1517
1518 @Override
1519 public void layerAdded(Layer newLayer) {
1520 onAddLayer(newLayer);
1521 }
1522
1523 @Override
1524 public void layerRemoved(final Layer oldLayer) {
1525 onRemoveLayer(oldLayer);
1526 }
1527
1528 /* ------------------------------------------------------------------------------ */
1529 /* Interface PropertyChangeListener */
1530 /* ------------------------------------------------------------------------------ */
1531 @Override
1532 public void propertyChange(PropertyChangeEvent evt) {
1533 if (evt.getSource() instanceof Layer) {
1534 Layer layer = (Layer)evt.getSource();
1535 final int idx = getLayers().indexOf(layer);
1536 if (idx < 0) return;
1537 fireRefresh();
1538 }
1539 }
1540 }
1541
1542 static class LayerList extends JTable {
1543 public LayerList(TableModel dataModel) {
1544 super(dataModel);
1545 }
1546
1547 public void scrollToVisible(int row, int col) {
1548 if (!(getParent() instanceof JViewport))
1549 return;
1550 JViewport viewport = (JViewport) getParent();
1551 Rectangle rect = getCellRect(row, col, true);
1552 Point pt = viewport.getViewPosition();
1553 rect.setLocation(rect.x - pt.x, rect.y - pt.y);
1554 viewport.scrollRectToVisible(rect);
1555 }
1556 }
1557
1558 /**
1559 * Creates a {@link ShowHideLayerAction} for <code>layer</code> in the
1560 * context of this {@link LayerListDialog}.
1561 *
1562 * @return the action
1563 */
1564 public ShowHideLayerAction createShowHideLayerAction() {
1565 ShowHideLayerAction act = new ShowHideLayerAction(true);
1566 act.putValue(Action.NAME, tr("Show/Hide"));
1567 return act;
1568 }
1569
1570 /**
1571 * Creates a {@link DeleteLayerAction} for <code>layer</code> in the
1572 * context of this {@link LayerListDialog}.
1573 *
1574 * @return the action
1575 */
1576 public DeleteLayerAction createDeleteLayerAction() {
1577 // the delete layer action doesn't depend on the current layer
1578 return new DeleteLayerAction();
1579 }
1580
1581 /**
1582 * Creates a {@link ActivateLayerAction} for <code>layer</code> in the
1583 * context of this {@link LayerListDialog}.
1584 *
1585 * @param layer the layer
1586 * @return the action
1587 */
1588 public ActivateLayerAction createActivateLayerAction(Layer layer) {
1589 return new ActivateLayerAction(layer);
1590 }
1591
1592 /**
1593 * Creates a {@link MergeLayerAction} for <code>layer</code> in the
1594 * context of this {@link LayerListDialog}.
1595 *
1596 * @param layer the layer
1597 * @return the action
1598 */
1599 public MergeAction createMergeLayerAction(Layer layer) {
1600 return new MergeAction(layer);
1601 }
1602
1603 public static Layer getLayerForIndex(int index) {
1604
1605 if (!Main.isDisplayingMapView())
1606 return null;
1607
1608 List<Layer> layers = Main.map.mapView.getAllLayersAsList();
1609
1610 if (index < layers.size() && index >= 0)
1611 return layers.get(index);
1612 else
1613 return null;
1614 }
1615
1616 // This is not Class<? extends Layer> on purpose, to allow asking for layers implementing some interface
1617 public static List<MultikeyInfo> getLayerInfoByClass(Class<?> layerClass) {
1618
1619 List<MultikeyInfo> result = new ArrayList<>();
1620
1621 if (!Main.isDisplayingMapView())
1622 return result;
1623
1624 List<Layer> layers = Main.map.mapView.getAllLayersAsList();
1625
1626 int index = 0;
1627 for (Layer l: layers) {
1628 if (layerClass.isAssignableFrom(l.getClass())) {
1629 result.add(new MultikeyInfo(index, l.getName()));
1630 }
1631 index++;
1632 }
1633
1634 return result;
1635 }
1636
1637 public static boolean isLayerValid(Layer l) {
1638 if (l == null)
1639 return false;
1640
1641 if (!Main.isDisplayingMapView())
1642 return false;
1643
1644 return Main.map.mapView.getAllLayersAsList().contains(l);
1645 }
1646
1647 public static MultikeyInfo getLayerInfo(Layer l) {
1648
1649 if (l == null)
1650 return null;
1651
1652 if (!Main.isDisplayingMapView())
1653 return null;
1654
1655 int index = Main.map.mapView.getAllLayersAsList().indexOf(l);
1656 if (index < 0)
1657 return null;
1658
1659 return new MultikeyInfo(index, l.getName());
1660 }
1661}
Note: See TracBrowser for help on using the repository browser.