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

Last change on this file since 5275 was 5275, checked in by bastiK, 12 years ago

doc improvements

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