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

Last change on this file since 6340 was 6336, checked in by Don-vip, 11 years ago

code cleanup / robustness in edit layer handling

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