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

Last change on this file since 2869 was 2869, checked in by jttt, 14 years ago

Removed ToggleDialog.tearDown(). All listeners should be unregistered in hideNotify()

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