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

Last change on this file since 5891 was 5891, checked in by stoecker, 11 years ago

fix javadoc

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