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

Last change on this file since 2621 was 2621, checked in by Gubaer, 14 years ago

Moved layer listener management from Layer to MapView
Made sure that listeners also unregister when they register for layer change events.

This will certainly break plugins. Plugin updates will follow later.

  • Property svn:eol-style set to native
File size: 37.8 KB
Line 
1package org.openstreetmap.josm.gui.dialogs;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4
5import java.awt.BorderLayout;
6import java.awt.Component;
7import java.awt.GridLayout;
8import java.awt.Point;
9import java.awt.Rectangle;
10import java.awt.event.ActionEvent;
11import java.awt.event.KeyEvent;
12import java.awt.event.MouseAdapter;
13import java.awt.event.MouseEvent;
14import java.beans.PropertyChangeEvent;
15import java.beans.PropertyChangeListener;
16import java.util.ArrayList;
17import java.util.Collections;
18import java.util.List;
19import java.util.concurrent.CopyOnWriteArrayList;
20
21import javax.swing.AbstractAction;
22import javax.swing.DefaultListCellRenderer;
23import javax.swing.DefaultListModel;
24import javax.swing.DefaultListSelectionModel;
25import javax.swing.Icon;
26import javax.swing.JComponent;
27import javax.swing.JLabel;
28import javax.swing.JList;
29import javax.swing.JPanel;
30import javax.swing.JScrollPane;
31import javax.swing.KeyStroke;
32import javax.swing.ListModel;
33import javax.swing.ListSelectionModel;
34import javax.swing.UIManager;
35import javax.swing.event.ListDataEvent;
36import javax.swing.event.ListDataListener;
37import javax.swing.event.ListSelectionEvent;
38import javax.swing.event.ListSelectionListener;
39
40import org.openstreetmap.josm.Main;
41import org.openstreetmap.josm.actions.MergeLayerAction;
42import org.openstreetmap.josm.gui.MapFrame;
43import org.openstreetmap.josm.gui.MapView;
44import org.openstreetmap.josm.gui.SideButton;
45import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
46import org.openstreetmap.josm.gui.io.SaveLayersDialog;
47import org.openstreetmap.josm.gui.layer.Layer;
48import org.openstreetmap.josm.gui.layer.OsmDataLayer;
49import org.openstreetmap.josm.tools.ImageProvider;
50import org.openstreetmap.josm.tools.Shortcut;
51import org.openstreetmap.josm.tools.ImageProvider.OverlayPosition;
52
53/**
54 * This is a toggle dialog which displays the list of layers. Actions allow to
55 * change the ordering of the layers, to hide/show layers, to activate layers,
56 * and to delete layers.
57 *
58 */
59public class LayerListDialog extends ToggleDialog {
60 //static private final Logger logger = Logger.getLogger(LayerListDialog.class.getName());
61
62 /** the unique instance of the dialog */
63 static private LayerListDialog instance;
64
65 /**
66 * Creates the instance of the dialog. It's connected to the map frame <code>mapFrame</code>
67 *
68 * @param mapFrame the map frame
69 */
70 static public void createInstance(MapFrame mapFrame) {
71 instance = new LayerListDialog(mapFrame);
72 }
73
74 /**
75 * Replies the instance of the dialog
76 *
77 * @return the instance of the dialog
78 * @throws IllegalStateException thrown, if the dialog is not created yet
79 * @see #createInstance(MapFrame)
80 */
81 static public LayerListDialog getInstance() throws IllegalStateException {
82 if (instance == null)
83 throw new IllegalStateException(tr("Dialog not created yet. Invoke createInstance() first"));
84 return instance;
85 }
86
87 /** the model for the layer list */
88 private LayerListModel model;
89
90 /** the selection model */
91 private DefaultListSelectionModel selectionModel;
92
93 /** the list of layers */
94 private LayerList layerList;
95
96 ActivateLayerAction activateLayerAction;
97
98 protected JPanel createButtonPanel() {
99 JPanel buttonPanel = new JPanel(new GridLayout(1, 5));
100
101 // -- move up action
102 MoveUpAction moveUpAction = new MoveUpAction();
103 adaptTo(moveUpAction, model);
104 adaptTo(moveUpAction,selectionModel);
105 buttonPanel.add(new SideButton(moveUpAction));
106
107 // -- move down action
108 MoveDownAction moveDownAction = new MoveDownAction();
109 adaptTo(moveDownAction, model);
110 adaptTo(moveDownAction,selectionModel);
111 buttonPanel.add(new SideButton(moveDownAction));
112
113 // -- activate action
114 activateLayerAction = new ActivateLayerAction();
115 adaptTo(activateLayerAction, selectionModel);
116 MapView.addLayerChangeListener(activateLayerAction);
117 buttonPanel.add(new SideButton(activateLayerAction, "activate"));
118
119 // -- show hide action
120 ShowHideLayerAction showHideLayerAction = new ShowHideLayerAction();
121 adaptTo(showHideLayerAction, selectionModel);
122 buttonPanel.add(new SideButton(showHideLayerAction, "showhide"));
123
124 // -- merge layer action
125 MergeAction mergeLayerAction = new MergeAction();
126 adaptTo(mergeLayerAction, model);
127 adaptTo(mergeLayerAction,selectionModel);
128 buttonPanel.add(new SideButton(mergeLayerAction));
129
130 //-- delete layer action
131 DeleteLayerAction deleteLayerAction = new DeleteLayerAction();
132 layerList.getInputMap(JComponent.WHEN_FOCUSED).put(
133 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"deleteLayer"
134 );
135 layerList.getActionMap().put("deleteLayer", deleteLayerAction);
136 adaptTo(deleteLayerAction, selectionModel);
137 buttonPanel.add(new SideButton(deleteLayerAction, "delete"));
138
139 return buttonPanel;
140 }
141
142 /**
143 * Create an layer list and attach it to the given mapView.
144 */
145 protected LayerListDialog(MapFrame mapFrame) {
146 super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."),
147 Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L, Shortcut.GROUP_LAYER), 100, true);
148
149 // create the models
150 //
151 selectionModel = new DefaultListSelectionModel();
152 selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
153 model = new LayerListModel(selectionModel);
154 MapView.addLayerChangeListener(model);
155
156 // create the list control
157 //
158 layerList = new LayerList(model);
159 layerList.setSelectionModel(selectionModel);
160 layerList.addMouseListener(new LayerListMouseAdapter());
161 layerList.setBackground(UIManager.getColor("Button.background"));
162 layerList.setCellRenderer(new LayerListCellRenderer());
163 add(new JScrollPane(layerList), BorderLayout.CENTER);
164
165 // init the model
166 //
167 final MapView mapView = mapFrame.mapView;
168 model.populate();
169 model.setSelectedLayer(mapView.getActiveLayer());
170 model.addLayerListModelListener(
171 new LayerListModelListener() {
172 public void makeVisible(int index, Layer layer) {
173 layerList.ensureIndexIsVisible(index);
174 }
175
176 public void refresh() {
177 layerList.repaint();
178 }
179 }
180 );
181
182 add(createButtonPanel(), BorderLayout.SOUTH);
183 }
184
185 @Override
186 public void tearDown() {
187 MapView.removeLayerChangeListener(model);
188 MapView.removeLayerChangeListener(activateLayerAction);
189 }
190
191 public LayerListModel getModel() {
192 return model;
193 }
194
195 private interface IEnabledStateUpdating {
196 void updateEnabledState();
197 }
198
199 /**
200 * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that
201 * <code>listener</code> receives a {@see IEnabledStateUpdating#updateEnabledState()}
202 * on every {@see ListSelectionEvent}.
203 *
204 * @param listener the listener
205 * @param listSelectionModel the source emitting {@see ListSelectionEvent}s
206 */
207 protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) {
208 listSelectionModel.addListSelectionListener(
209 new ListSelectionListener() {
210 public void valueChanged(ListSelectionEvent e) {
211 listener.updateEnabledState();
212 }
213 }
214 );
215 }
216
217 /**
218 * Wires <code>listener</code> to <code>listModel</code> in such a way, that
219 * <code>listener</code> receives a {@see IEnabledStateUpdating#updateEnabledState()}
220 * on every {@see ListDataEvent}.
221 *
222 * @param listener the listener
223 * @param listSelectionModel the source emitting {@see ListDataEvent}s
224 */
225 protected void adaptTo(final IEnabledStateUpdating listener, ListModel listModel) {
226 listModel.addListDataListener(
227 new ListDataListener() {
228 public void contentsChanged(ListDataEvent e) {
229 listener.updateEnabledState();
230 }
231
232 public void intervalAdded(ListDataEvent e) {
233 listener.updateEnabledState();
234 }
235
236 public void intervalRemoved(ListDataEvent e) {
237 listener.updateEnabledState();
238 }
239 }
240 );
241 }
242
243 /**
244 * The action to delete the currently selected layer
245 */
246 public final class DeleteLayerAction extends AbstractAction implements IEnabledStateUpdating {
247 private Layer layer;
248
249 /**
250 * Creates a {@see DeleteLayerAction} for a specific layer.
251 *
252 * @param layer the layer. Must not be null.
253 * @exception IllegalArgumentException thrown, if layer is null
254 */
255 public DeleteLayerAction(Layer layer) {
256 this();
257 if (layer == null)
258 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "layer"));
259 this.layer = layer;
260 putValue(NAME, tr("Delete"));
261 updateEnabledState();
262 }
263
264 /**
265 * Creates a {@see DeleteLayerAction} which will delete the currently
266 * selected layers in the layer dialog.
267 *
268 */
269 public DeleteLayerAction() {
270 putValue(SMALL_ICON,ImageProvider.get("dialogs", "delete"));
271 putValue(SHORT_DESCRIPTION, tr("Delete the selected layer."));
272 putValue("help", "Action/LayerDelete");
273 updateEnabledState();
274 }
275
276 protected boolean enforceUploadOrSaveModifiedData(List<Layer> selectedLayers) {
277 SaveLayersDialog dialog = new SaveLayersDialog(Main.parent);
278 List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>();
279 for (Layer l: selectedLayers) {
280 if (! (l instanceof OsmDataLayer)) {
281 continue;
282 }
283 OsmDataLayer odl = (OsmDataLayer)l;
284 if ((odl.requiresSaveToFile() || odl.requiresUploadToServer()) && odl.data.isModified()) {
285 layersWithUnmodifiedChanges.add(odl);
286 }
287 }
288 dialog.prepareForSavingAndUpdatingLayersBeforeDelete();
289 if (!layersWithUnmodifiedChanges.isEmpty()) {
290 dialog.getModel().populate(layersWithUnmodifiedChanges);
291 dialog.setVisible(true);
292 switch(dialog.getUserAction()) {
293 case CANCEL: return false;
294 case PROCEED: return true;
295 default: return false;
296 }
297 }
298 return true;
299 }
300
301 public void actionPerformed(ActionEvent e) {
302 List<Layer> selectedLayers;
303 if (this.layer == null) {
304 selectedLayers = getModel().getSelectedLayers();
305 } else {
306 selectedLayers = Collections.singletonList(this.layer);
307 }
308 if (selectedLayers.isEmpty())
309 return;
310 if (! enforceUploadOrSaveModifiedData(selectedLayers))
311 return;
312 for(Layer l: selectedLayers) {
313 Main.main.removeLayer(l);
314 }
315 }
316
317 public void updateEnabledState() {
318 if (layer == null) {
319 setEnabled(! getModel().getSelectedLayers().isEmpty());
320 } else {
321 setEnabled(true);
322 }
323 }
324 }
325
326 public final class ShowHideLayerAction extends AbstractAction implements IEnabledStateUpdating {
327 private Layer layer;
328
329 /**
330 * Creates a {@see ShowHideLayerAction} which toggle the visibility of
331 * a specific layer.
332 *
333 * @param layer the layer. Must not be null.
334 * @exception IllegalArgumentException thrown, if layer is null
335 */
336 public ShowHideLayerAction(Layer layer) throws IllegalArgumentException {
337 this();
338 if (layer == null)
339 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "layer"));
340 this.layer = layer;
341 putValue(NAME, tr("Show/Hide"));
342 updateEnabledState();
343 }
344
345 /**
346 * Creates a {@see ShowHideLayerAction} which will toggle the visibility of
347 * the currently selected layers
348 *
349 */
350 public ShowHideLayerAction() {
351 putValue(SMALL_ICON, ImageProvider.get("dialogs", "showhide"));
352 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the selected layer."));
353 putValue("help", "Action/LayerShowHide");
354 updateEnabledState();
355 }
356
357 public void actionPerformed(ActionEvent e) {
358 if (layer != null) {
359 layer.toggleVisible();
360 } else {
361 for(Layer layer: model.getSelectedLayers()) {
362 layer.toggleVisible();
363 }
364 }
365 }
366
367 public void updateEnabledState() {
368 if (layer == null) {
369 setEnabled(! getModel().getSelectedLayers().isEmpty());
370 } else {
371 setEnabled(true);
372 }
373 }
374 }
375
376 /**
377 * The action to activate the currently selected layer
378 */
379
380 public final class ActivateLayerAction extends AbstractAction implements IEnabledStateUpdating, MapView.LayerChangeListener{
381 private Layer layer;
382
383 public ActivateLayerAction(Layer layer) throws IllegalArgumentException {
384 this();
385 if (layer == null)
386 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "layer"));
387 this.layer = layer;
388 putValue(NAME, tr("Activate"));
389 updateEnabledState();
390 }
391
392 public ActivateLayerAction() {
393 putValue(SMALL_ICON, ImageProvider.get("dialogs", "activate"));
394 putValue(SHORT_DESCRIPTION, tr("Activate the selected layer"));
395 putValue("help", "Action/ActivateLayer");
396 updateEnabledState();
397 }
398
399 public void actionPerformed(ActionEvent e) {
400 Layer toActivate;
401 if (layer != null) {
402 toActivate = layer;
403 } else {
404 toActivate = model.getSelectedLayers().get(0);
405 }
406 // model is going to be updated via LayerChangeListener
407 // and PropertyChangeEvents
408 Main.map.mapView.setActiveLayer(toActivate);
409 toActivate.setVisible(true);
410 }
411
412 protected boolean isActiveLayer(Layer layer) {
413 if (Main.map == null) return false;
414 if (Main.map.mapView == null) return false;
415 return Main.map.mapView.getActiveLayer() == layer;
416 }
417
418 public void updateEnabledState() {
419 if (layer == null) {
420 if (getModel().getSelectedLayers().size() != 1) {
421 setEnabled(false);
422 return;
423 }
424 Layer selectedLayer = getModel().getSelectedLayers().get(0);
425 setEnabled(!isActiveLayer(selectedLayer));
426 } else {
427 setEnabled(!isActiveLayer(layer));
428 }
429 }
430
431 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
432 updateEnabledState();
433 }
434 public void layerAdded(Layer newLayer) {
435 updateEnabledState();
436 }
437 public void layerRemoved(Layer oldLayer) {
438 updateEnabledState();
439 }
440 }
441
442 /**
443 * The action to merge the currently selected layer into another layer.
444 */
445 public final class MergeAction extends AbstractAction implements IEnabledStateUpdating {
446 private Layer layer;
447
448 public MergeAction(Layer layer) throws IllegalArgumentException {
449 this();
450 if (layer == null)
451 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "layer"));
452 this.layer = layer;
453 putValue(NAME, tr("Merge"));
454 updateEnabledState();
455 }
456
457 public MergeAction() {
458 putValue(SMALL_ICON, ImageProvider.get("dialogs", "mergedown"));
459 putValue(SHORT_DESCRIPTION, tr("Merge this layer into another layer"));
460 putValue("help", "Action/MergeLayer");
461 updateEnabledState();
462 }
463
464 public void actionPerformed(ActionEvent e) {
465 if (layer != null) {
466 new MergeLayerAction().merge(layer);
467 } else {
468 Layer selectedLayer = getModel().getSelectedLayers().get(0);
469 new MergeLayerAction().merge(selectedLayer);
470 }
471 }
472
473 protected boolean isActiveLayer(Layer layer) {
474 if (Main.map == null) return false;
475 if (Main.map.mapView == null) return false;
476 return Main.map.mapView.getActiveLayer() == layer;
477 }
478
479 public void updateEnabledState() {
480 if (layer == null) {
481 if (getModel().getSelectedLayers().size() != 1) {
482 setEnabled(false);
483 return;
484 }
485 Layer selectedLayer = getModel().getSelectedLayers().get(0);
486 List<Layer> targets = getModel().getPossibleMergeTargets(selectedLayer);
487 setEnabled(!targets.isEmpty());
488 } else {
489 List<Layer> targets = getModel().getPossibleMergeTargets(layer);
490 setEnabled(!targets.isEmpty());
491 }
492 }
493 }
494
495 /**
496 * the list cell renderer used to render layer list entries
497 *
498 */
499 class LayerListCellRenderer extends DefaultListCellRenderer {
500
501 protected boolean isActiveLayer(Layer layer) {
502 if (Main.map == null) return false;
503 if (Main.map.mapView == null) return false;
504 return Main.map.mapView.getActiveLayer() == layer;
505 }
506
507 @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
508 Layer layer = (Layer)value;
509 JLabel label = (JLabel)super.getListCellRendererComponent(list,
510 layer.getName(), index, isSelected, cellHasFocus);
511 Icon icon = layer.getIcon();
512 if (isActiveLayer(layer)) {
513 icon = ImageProvider.overlay(icon, "overlay/active", OverlayPosition.SOUTHWEST);
514 }
515 if (!layer.isVisible()) {
516 icon = ImageProvider.overlay(icon, "overlay/invisiblenew", OverlayPosition.SOUTHEAST);
517 }
518 label.setIcon(icon);
519 label.setToolTipText(layer.getToolTipText());
520 return label;
521 }
522 }
523
524 class LayerListMouseAdapter extends MouseAdapter {
525
526 private void openPopup(MouseEvent e) {
527 Point p = e.getPoint();
528 int index = layerList.locationToIndex(p);
529 if (index < 0) return;
530 if (!layerList.getCellBounds(index, index).contains(e.getPoint()))
531 return;
532 if (!layerList.isSelectedIndex(index)) {
533 layerList.setSelectedIndex(index);
534 }
535 Layer layer = model.getLayer(index);
536 LayerListPopup menu = new LayerListPopup(layerList, layer);
537 menu.show(LayerListDialog.this, p.x, p.y-3);
538 }
539 @Override public void mousePressed(MouseEvent e) {
540 if (e.isPopupTrigger()) {
541 openPopup(e);
542 }
543 }
544 @Override public void mouseReleased(MouseEvent e) {
545 if (e.isPopupTrigger()) {
546 openPopup(e);
547 }
548 }
549 @Override public void mouseClicked(MouseEvent e) {
550 if (e.getClickCount() == 2) {
551 int index = layerList.locationToIndex(e.getPoint());
552 if (!layerList.getCellBounds(index, index).contains(e.getPoint()))
553 return;
554 Layer layer = model.getLayer(index);
555 String current = Main.pref.get("marker.show "+layer.getName(),"show");
556 Main.pref.put("marker.show "+layer.getName(), current.equalsIgnoreCase("show") ? "hide" : "show");
557 layer.toggleVisible();
558 }
559 }
560 }
561
562 /**
563 * The action to move up the currently selected entries in the list.
564 */
565 class MoveUpAction extends AbstractAction implements IEnabledStateUpdating{
566 public MoveUpAction() {
567 putValue(SMALL_ICON, ImageProvider.get("dialogs", "up"));
568 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row up."));
569 updateEnabledState();
570 }
571
572 public void updateEnabledState() {
573 setEnabled(model.canMoveUp());
574 }
575
576 public void actionPerformed(ActionEvent e) {
577 model.moveUp();
578 }
579 }
580
581 /**
582 * The action to move down the currently selected entries in the list.
583 */
584 class MoveDownAction extends AbstractAction implements IEnabledStateUpdating {
585 public MoveDownAction() {
586 putValue(SMALL_ICON, ImageProvider.get("dialogs", "down"));
587 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row down."));
588 updateEnabledState();
589 }
590
591 public void updateEnabledState() {
592 setEnabled(model.canMoveDown());
593 }
594
595 public void actionPerformed(ActionEvent e) {
596 model.moveDown();
597 }
598 }
599
600 /**
601 * Observer interface to be implemented by views using {@see LayerListModel}
602 *
603 */
604 public interface LayerListModelListener {
605 public void makeVisible(int index, Layer layer);
606 public void refresh();
607 }
608
609 /**
610 * The layer list model. The model manages a list of layers and provides methods for
611 * moving layers up and down, for toggling their visibility, and for activating a layer.
612 *
613 * The model is a {@see ListModel} and it provides a {@see ListSelectionModel}. It expectes
614 * to be configured with a {@see DefaultListSelectionModel}. The selection model is used
615 * to update the selection state of views depending on messages sent to the model.
616 *
617 * The model manages a list of {@see LayerListModelListener} which are mainly notified if
618 * the model requires views to make a specific list entry visible.
619 *
620 * It also listens to {@see PropertyChangeEvent}s of every {@see Layer} it manages, in particular to
621 * the properties {@see Layer#VISIBLE_PROP} and {@see Layer#NAME_PROP}.
622 */
623 public class LayerListModel extends DefaultListModel implements MapView.LayerChangeListener, PropertyChangeListener{
624
625 /** manages list selection state*/
626 private DefaultListSelectionModel selectionModel;
627 private CopyOnWriteArrayList<LayerListModelListener> listeners;
628
629 /**
630 * constructor
631 *
632 * @param selectionModel the list selection model
633 */
634 private LayerListModel(DefaultListSelectionModel selectionModel) {
635 this.selectionModel = selectionModel;
636 listeners = new CopyOnWriteArrayList<LayerListModelListener>();
637 }
638
639 /**
640 * Adds a listener to this model
641 *
642 * @param listener the listener
643 */
644 public void addLayerListModelListener(LayerListModelListener listener) {
645 synchronized(listeners) {
646 if (listener != null && !listeners.contains(listener)) {
647 listeners.add(listener);
648 }
649 }
650 }
651
652 /**
653 * removes a listener from this model
654 * @param listener the listener
655 *
656 */
657 public void removeLayerListModelListener(LayerListModelListener listener) {
658 synchronized(listeners) {
659 if (listener != null && listeners.contains(listener)) {
660 listeners.remove(listener);
661 }
662 }
663 }
664
665 /**
666 * Fires a make visible event to listeners
667 *
668 * @param index the index of the row to make visible
669 * @param layer the layer at this index
670 * @see LayerListModelListener#makeVisible(int, Layer)
671 */
672 protected void fireMakeVisible(int index, Layer layer) {
673 for (LayerListModelListener listener : listeners) {
674 listener.makeVisible(index, layer);
675 }
676 }
677
678 /**
679 * Fires a refresh event to listeners of this model
680 *
681 * @see LayerListModelListener#refresh()
682 */
683 protected void fireRefresh() {
684 for (LayerListModelListener listener : listeners) {
685 listener.refresh();
686 }
687 }
688
689 /**
690 * Populates the model with the current layers managed by
691 * {@see MapView}.
692 *
693 */
694 public void populate() {
695 for (Layer layer: getLayers()) {
696 // make sure the model is registered exactly once
697 //
698 layer.removePropertyChangeListener(this);
699 layer.addPropertyChangeListener(this);
700 }
701 fireContentsChanged(this, 0, getSize());
702 }
703
704 /**
705 * Marks <code>layer</code> as selected layer. Ignored, if
706 * layer is null.
707 *
708 * @param layer the layer.
709 */
710 public void setSelectedLayer(Layer layer) {
711 if (layer == null)
712 return;
713 int idx = getLayers().indexOf(layer);
714 if (idx >= 0) {
715 selectionModel.setSelectionInterval(idx, idx);
716 }
717 fireContentsChanged(this, 0, getSize());
718 ensureSelectedIsVisible();
719 }
720
721 /**
722 * Replies the list of currently selected layers. Never null, but may
723 * be empty.
724 *
725 * @return the list of currently selected layers. Never null, but may
726 * be empty.
727 */
728 public List<Layer> getSelectedLayers() {
729 ArrayList<Layer> selected = new ArrayList<Layer>();
730 for (int i=0; i<getLayers().size(); i++) {
731 if (selectionModel.isSelectedIndex(i)) {
732 selected.add(getLayers().get(i));
733 }
734 }
735 return selected;
736 }
737
738 /**
739 * Replies a the list of indices of the selected rows. Never null,
740 * but may be empty.
741 *
742 * @return the list of indices of the selected rows. Never null,
743 * but may be empty.
744 */
745 public List<Integer> getSelectedRows() {
746 ArrayList<Integer> selected = new ArrayList<Integer>();
747 for (int i=0; i<getLayers().size();i++) {
748 if (selectionModel.isSelectedIndex(i)) {
749 selected.add(i);
750 }
751 }
752 return selected;
753 }
754
755 /**
756 * Invoked if a layer managed by {@see MapView} is removed
757 *
758 * @param layer the layer which is removed
759 */
760 protected void onRemoveLayer(Layer layer) {
761 if (layer == null)
762 return;
763 layer.removePropertyChangeListener(this);
764 int size = getSize();
765 List<Integer> rows = getSelectedRows();
766 if (rows.isEmpty() && size > 0) {
767 selectionModel.setSelectionInterval(size-1, size-1);
768 }
769 fireRefresh();
770 ensureSelectedIsVisible();
771 }
772
773 /**
774 * Invoked when a layer managed by {@see MapView} is added
775 *
776 * @param layer the layer
777 */
778 protected void onAddLayer(Layer layer) {
779 if (layer == null) return;
780 layer.addPropertyChangeListener(this);
781 fireContentsChanged(this, 0, getSize());
782 int idx = getLayers().indexOf(layer);
783 selectionModel.setSelectionInterval(idx, idx);
784 ensureSelectedIsVisible();
785 }
786
787 /**
788 * Replies the first layer. Null if no layers are present
789 *
790 * @return the first layer. Null if no layers are present
791 */
792 public Layer getFirstLayer() {
793 if (getSize() == 0) return null;
794 return getLayers().get(0);
795 }
796
797 /**
798 * Replies the layer at position <code>index</code>
799 *
800 * @param index the index
801 * @return the layer at position <code>index</code>. Null,
802 * if index is out of range.
803 */
804 public Layer getLayer(int index) {
805 if (index < 0 || index >= getSize())
806 return null;
807 return getLayers().get(index);
808 }
809
810 /**
811 * Replies true if the the currently selected layers can move up
812 * by one position
813 *
814 * @return true if the the currently selected layers can move up
815 * by one position
816 */
817 public boolean canMoveUp() {
818 List<Integer> sel = getSelectedRows();
819 return !sel.isEmpty() && sel.get(0) > 0;
820 }
821
822 /**
823 * Move up the currently selected layers by one position
824 *
825 */
826 public void moveUp() {
827 if (!canMoveUp()) return;
828 List<Integer> sel = getSelectedRows();
829 for (int row: sel) {
830 Layer l1 = getLayers().get(row);
831 Layer l2 = getLayers().get(row-1);
832 Main.map.mapView.moveLayer(l2,row);
833 Main.map.mapView.moveLayer(l1, row-1);
834 }
835 fireContentsChanged(this, 0, getSize());
836 selectionModel.clearSelection();
837 for(int row: sel) {
838 selectionModel.addSelectionInterval(row-1, row-1);
839 }
840 ensureSelectedIsVisible();
841 }
842
843 /**
844 * Replies true if the currently selected layers can move down
845 * by one position
846 *
847 * @return true if the currently selected layers can move down
848 * by one position
849 */
850 public boolean canMoveDown() {
851 List<Integer> sel = getSelectedRows();
852 return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1;
853 }
854
855 /**
856 * Move down the currently selected layers by one position
857 *
858 */
859 public void moveDown() {
860 if (!canMoveDown()) return;
861 List<Integer> sel = getSelectedRows();
862 Collections.reverse(sel);
863 for (int row: sel) {
864 Layer l1 = getLayers().get(row);
865 Layer l2 = getLayers().get(row+1);
866 Main.map.mapView.moveLayer(l1, row+1);
867 Main.map.mapView.moveLayer(l2, row);
868 }
869 fireContentsChanged(this, 0, getSize());
870 selectionModel.clearSelection();
871 for(int row: sel) {
872 selectionModel.addSelectionInterval(row+1, row+1);
873 }
874 ensureSelectedIsVisible();
875 }
876
877 /**
878 * Make sure the first of the selected layers is visible in the
879 * views of this model.
880 *
881 */
882 protected void ensureSelectedIsVisible() {
883 int index = selectionModel.getMinSelectionIndex();
884 if (index <0 )return;
885 if (index >= getLayers().size()) return;
886 Layer layer = getLayers().get(index);
887 fireMakeVisible(index, layer);
888 }
889
890 /**
891 * Replies a list of layers which are possible merge targets
892 * for <code>source</code>
893 *
894 * @param source the source layer
895 * @return a list of layers which are possible merge targets
896 * for <code>source</code>. Never null, but can be empty.
897 */
898 public List<Layer> getPossibleMergeTargets(Layer source) {
899 ArrayList<Layer> targets = new ArrayList<Layer>();
900 if (source == null)
901 return targets;
902 for(Layer target: getLayers()) {
903 if (source == target) {
904 continue;
905 }
906 if (target.isMergable(source)) {
907 targets.add(target);
908 }
909 }
910 return targets;
911 }
912
913 /**
914 * Replies the list of layers currently managed by {@see MapView}.
915 * Never null, but can be empty.
916 *
917 * @return the list of layers currently managed by {@see MapView}.
918 * Never null, but can be empty.
919 */
920 protected List<Layer> getLayers() {
921 if (Main.map == null || Main.map.mapView == null)
922 return Collections.<Layer>emptyList();
923 return Main.map.mapView.getAllLayersAsList();
924 }
925
926 /**
927 * Ensures that at least one layer is selected in the layer dialog
928 *
929 */
930 protected void ensureActiveSelected() {
931 if (getLayers().size() == 0) return;
932 if (getActiveLayer() != null) {
933 // there's an active layer - select it and make it
934 // visible
935 int idx = getLayers().indexOf(getActiveLayer());
936 selectionModel.setSelectionInterval(idx, idx);
937 ensureSelectedIsVisible();
938 } else {
939 // no active layer - select the first one and make
940 // it visible
941 selectionModel.setSelectionInterval(0, 0);
942 ensureSelectedIsVisible();
943 }
944 }
945
946 /**
947 * Replies the active layer. null, if no active layer is available
948 *
949 * @return the active layer. null, if no active layer is available
950 */
951 protected Layer getActiveLayer() {
952 if (Main.map == null || Main.map.mapView == null) return null;
953 return Main.map.mapView.getActiveLayer();
954 }
955
956 /* ------------------------------------------------------------------------------ */
957 /* Interface ListModel */
958 /* ------------------------------------------------------------------------------ */
959 @Override
960 public Object getElementAt(int index) {
961 return getLayers().get(index);
962 }
963
964 @Override
965 public int getSize() {
966 List<Layer> layers = getLayers();
967 if (layers == null) return 0;
968 return layers.size();
969 }
970
971 /* ------------------------------------------------------------------------------ */
972 /* Interface LayerChangeListener */
973 /* ------------------------------------------------------------------------------ */
974 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
975 if (oldLayer != null) {
976 int idx = getLayers().indexOf(oldLayer);
977 if (idx >= 0) {
978 fireContentsChanged(this, idx,idx);
979 }
980 }
981
982 if (newLayer != null) {
983 int idx = getLayers().indexOf(newLayer);
984 if (idx >= 0) {
985 fireContentsChanged(this, idx,idx);
986 }
987 }
988 ensureActiveSelected();
989 }
990
991 public void layerAdded(Layer newLayer) {
992 onAddLayer(newLayer);
993 }
994
995 public void layerRemoved(final Layer oldLayer) {
996 onRemoveLayer(oldLayer);
997 }
998
999 /* ------------------------------------------------------------------------------ */
1000 /* Interface PropertyChangeListener */
1001 /* ------------------------------------------------------------------------------ */
1002 public void propertyChange(PropertyChangeEvent evt) {
1003 if (evt.getSource() instanceof Layer) {
1004 Layer layer = (Layer)evt.getSource();
1005 final int idx = getLayers().indexOf(layer);
1006 if (idx < 0) return;
1007 fireRefresh();
1008 }
1009 }
1010 }
1011
1012 class LayerList extends JList {
1013 public LayerList(ListModel dataModel) {
1014 super(dataModel);
1015 }
1016
1017 @Override
1018 protected void processMouseEvent(MouseEvent e) {
1019 // if the layer list is embedded in a detached dialog, the last row is
1020 // selected if a user clicks in the empty space *below* the last row.
1021 // This mouse event filter prevents this.
1022 //
1023 int idx = locationToIndex(e.getPoint());
1024 // sometimes bounds can be null, see #3539
1025 Rectangle bounds = getCellBounds(idx,idx);
1026 if (bounds != null && bounds.contains(e.getPoint())) {
1027 super.processMouseEvent(e);
1028 }
1029 }
1030 }
1031
1032 /**
1033 * Creates a {@see ShowHideLayerAction} for <code>layer</code> in the
1034 * context of this {@see LayerListDialog}.
1035 *
1036 * @param layer the layer
1037 * @return the action
1038 */
1039 public ShowHideLayerAction createShowHideLayerAction(Layer layer) {
1040 return new ShowHideLayerAction(layer);
1041 }
1042
1043 /**
1044 * Creates a {@see DeleteLayerAction} for <code>layer</code> in the
1045 * context of this {@see LayerListDialog}.
1046 *
1047 * @param layer the layer
1048 * @return the action
1049 */
1050 public DeleteLayerAction createDeleteLayerAction(Layer layer) {
1051 return new DeleteLayerAction(layer);
1052 }
1053
1054 /**
1055 * Creates a {@see ActivateLayerAction} for <code>layer</code> in the
1056 * context of this {@see LayerListDialog}.
1057 *
1058 * @param layer the layer
1059 * @return the action
1060 */
1061 public ActivateLayerAction createActivateLayerAction(Layer layer) {
1062 return new ActivateLayerAction(layer);
1063 }
1064
1065 /**
1066 * Creates a {@see MergeLayerAction} for <code>layer</code> in the
1067 * context of this {@see LayerListDialog}.
1068 *
1069 * @param layer the layer
1070 * @return the action
1071 */
1072 public MergeAction createMergeLayerAction(Layer layer) {
1073 return new MergeAction(layer);
1074 }
1075}
Note: See TracBrowser for help on using the repository browser.