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

Last change on this file since 2711 was 2711, checked in by stoecker, 14 years ago

fix bad line endings

  • Property svn:eol-style set to native
File size: 36.7 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.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 = getButtonPanel(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));
118
119 // -- show hide action
120 ShowHideLayerAction showHideLayerAction = new ShowHideLayerAction();
121 adaptTo(showHideLayerAction, selectionModel);
122 buttonPanel.add(new SideButton(showHideLayerAction));
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, false));
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 DblClickAdapter());
161 layerList.addMouseListener(new PopupMenuHandler());
162 layerList.setBackground(UIManager.getColor("Button.background"));
163 layerList.setCellRenderer(new LayerListCellRenderer());
164 add(new JScrollPane(layerList), BorderLayout.CENTER);
165
166 // init the model
167 //
168 final MapView mapView = mapFrame.mapView;
169 model.populate();
170 model.setSelectedLayer(mapView.getActiveLayer());
171 model.addLayerListModelListener(
172 new LayerListModelListener() {
173 public void makeVisible(int index, Layer layer) {
174 layerList.ensureIndexIsVisible(index);
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 /**
248 * Creates a {@see DeleteLayerAction} which will delete the currently
249 * selected layers in the layer dialog.
250 *
251 */
252 public DeleteLayerAction() {
253 putValue(SMALL_ICON,ImageProvider.get("dialogs", "delete"));
254 putValue(SHORT_DESCRIPTION, tr("Delete the selected layers."));
255 putValue(NAME, tr("Delete"));
256 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#DeleteLayer"));
257 updateEnabledState();
258 }
259
260 protected boolean enforceUploadOrSaveModifiedData(List<Layer> selectedLayers) {
261 SaveLayersDialog dialog = new SaveLayersDialog(Main.parent);
262 List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>();
263 for (Layer l: selectedLayers) {
264 if (! (l instanceof OsmDataLayer)) {
265 continue;
266 }
267 OsmDataLayer odl = (OsmDataLayer)l;
268 if ((odl.requiresSaveToFile() || odl.requiresUploadToServer()) && odl.data.isModified()) {
269 layersWithUnmodifiedChanges.add(odl);
270 }
271 }
272 dialog.prepareForSavingAndUpdatingLayersBeforeDelete();
273 if (!layersWithUnmodifiedChanges.isEmpty()) {
274 dialog.getModel().populate(layersWithUnmodifiedChanges);
275 dialog.setVisible(true);
276 switch(dialog.getUserAction()) {
277 case CANCEL: return false;
278 case PROCEED: return true;
279 default: return false;
280 }
281 }
282 return true;
283 }
284
285 public void actionPerformed(ActionEvent e) {
286 List<Layer> selectedLayers = getModel().getSelectedLayers();
287 if (selectedLayers.isEmpty())
288 return;
289 if (! enforceUploadOrSaveModifiedData(selectedLayers))
290 return;
291 for(Layer l: selectedLayers) {
292 Main.main.removeLayer(l);
293 }
294 }
295
296 public void updateEnabledState() {
297 setEnabled(! getModel().getSelectedLayers().isEmpty());
298 }
299 }
300
301 public final class ShowHideLayerAction extends AbstractAction implements IEnabledStateUpdating {
302 private Layer layer;
303
304 /**
305 * Creates a {@see ShowHideLayerAction} which toggle the visibility of
306 * a specific layer.
307 *
308 * @param layer the layer. Must not be null.
309 * @exception IllegalArgumentException thrown, if layer is null
310 */
311 public ShowHideLayerAction(Layer layer) throws IllegalArgumentException {
312 this();
313 if (layer == null)
314 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "layer"));
315 this.layer = layer;
316 putValue(NAME, tr("Show/Hide"));
317 updateEnabledState();
318 }
319
320 /**
321 * Creates a {@see ShowHideLayerAction} which will toggle the visibility of
322 * the currently selected layers
323 *
324 */
325 public ShowHideLayerAction() {
326 putValue(SMALL_ICON, ImageProvider.get("dialogs", "showhide"));
327 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the selected layer."));
328 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#ShowHideLayer"));
329 updateEnabledState();
330 }
331
332 public void actionPerformed(ActionEvent e) {
333 if (layer != null) {
334 layer.toggleVisible();
335 } else {
336 for(Layer layer: model.getSelectedLayers()) {
337 layer.toggleVisible();
338 }
339 }
340 }
341
342 public void updateEnabledState() {
343 if (layer == null) {
344 setEnabled(! getModel().getSelectedLayers().isEmpty());
345 } else {
346 setEnabled(true);
347 }
348 }
349 }
350
351 /**
352 * The action to activate the currently selected layer
353 */
354
355 public final class ActivateLayerAction extends AbstractAction implements IEnabledStateUpdating, MapView.LayerChangeListener{
356 private Layer layer;
357
358 public ActivateLayerAction(Layer layer) throws IllegalArgumentException {
359 this();
360 if (layer == null)
361 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "layer"));
362 this.layer = layer;
363 putValue(NAME, tr("Activate"));
364 updateEnabledState();
365 }
366
367 public ActivateLayerAction() {
368 putValue(SMALL_ICON, ImageProvider.get("dialogs", "activate"));
369 putValue(SHORT_DESCRIPTION, tr("Activate the selected layer"));
370 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#ActivateLayer"));
371 updateEnabledState();
372 }
373
374 public void actionPerformed(ActionEvent e) {
375 Layer toActivate;
376 if (layer != null) {
377 toActivate = layer;
378 } else {
379 toActivate = model.getSelectedLayers().get(0);
380 }
381 // model is going to be updated via LayerChangeListener
382 // and PropertyChangeEvents
383 Main.map.mapView.setActiveLayer(toActivate);
384 toActivate.setVisible(true);
385 }
386
387 protected boolean isActiveLayer(Layer layer) {
388 if (Main.map == null) return false;
389 if (Main.map.mapView == null) return false;
390 return Main.map.mapView.getActiveLayer() == layer;
391 }
392
393 public void updateEnabledState() {
394 if (layer == null) {
395 if (getModel().getSelectedLayers().size() != 1) {
396 setEnabled(false);
397 return;
398 }
399 Layer selectedLayer = getModel().getSelectedLayers().get(0);
400 setEnabled(!isActiveLayer(selectedLayer));
401 } else {
402 setEnabled(!isActiveLayer(layer));
403 }
404 }
405
406 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
407 updateEnabledState();
408 }
409 public void layerAdded(Layer newLayer) {
410 updateEnabledState();
411 }
412 public void layerRemoved(Layer oldLayer) {
413 updateEnabledState();
414 }
415 }
416
417 /**
418 * The action to merge the currently selected layer into another layer.
419 */
420 public final class MergeAction extends AbstractAction implements IEnabledStateUpdating {
421 private Layer layer;
422
423 public MergeAction(Layer layer) throws IllegalArgumentException {
424 this();
425 if (layer == null)
426 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "layer"));
427 this.layer = layer;
428 putValue(NAME, tr("Merge"));
429 updateEnabledState();
430 }
431
432 public MergeAction() {
433 putValue(SMALL_ICON, ImageProvider.get("dialogs", "mergedown"));
434 putValue(SHORT_DESCRIPTION, tr("Merge this layer into another layer"));
435 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#MergeLayer"));
436 updateEnabledState();
437 }
438
439 public void actionPerformed(ActionEvent e) {
440 if (layer != null) {
441 new MergeLayerAction().merge(layer);
442 } else {
443 Layer selectedLayer = getModel().getSelectedLayers().get(0);
444 new MergeLayerAction().merge(selectedLayer);
445 }
446 }
447
448 protected boolean isActiveLayer(Layer layer) {
449 if (Main.map == null) return false;
450 if (Main.map.mapView == null) return false;
451 return Main.map.mapView.getActiveLayer() == layer;
452 }
453
454 public void updateEnabledState() {
455 if (layer == null) {
456 if (getModel().getSelectedLayers().size() != 1) {
457 setEnabled(false);
458 return;
459 }
460 Layer selectedLayer = getModel().getSelectedLayers().get(0);
461 List<Layer> targets = getModel().getPossibleMergeTargets(selectedLayer);
462 setEnabled(!targets.isEmpty());
463 } else {
464 List<Layer> targets = getModel().getPossibleMergeTargets(layer);
465 setEnabled(!targets.isEmpty());
466 }
467 }
468 }
469
470 /**
471 * the list cell renderer used to render layer list entries
472 *
473 */
474 static class LayerListCellRenderer extends DefaultListCellRenderer {
475
476 protected boolean isActiveLayer(Layer layer) {
477 if (Main.map == null) return false;
478 if (Main.map.mapView == null) return false;
479 return Main.map.mapView.getActiveLayer() == layer;
480 }
481
482 @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
483 Layer layer = (Layer)value;
484 JLabel label = (JLabel)super.getListCellRendererComponent(list,
485 layer.getName(), index, isSelected, cellHasFocus);
486 Icon icon = layer.getIcon();
487 if (isActiveLayer(layer)) {
488 icon = ImageProvider.overlay(icon, "overlay/active", OverlayPosition.SOUTHWEST);
489 }
490 if (!layer.isVisible()) {
491 icon = ImageProvider.overlay(icon, "overlay/invisiblenew", OverlayPosition.SOUTHEAST);
492 }
493 label.setIcon(icon);
494 label.setToolTipText(layer.getToolTipText());
495 return label;
496 }
497 }
498
499 class PopupMenuHandler extends PopupMenuLauncher {
500 @Override
501 public void launch(MouseEvent evt) {
502 Point p = evt.getPoint();
503 int index = layerList.locationToIndex(p);
504 if (index < 0) return;
505 if (!layerList.getCellBounds(index, index).contains(evt.getPoint()))
506 return;
507 if (!layerList.isSelectedIndex(index)) {
508 layerList.setSelectedIndex(index);
509 }
510 Layer layer = model.getLayer(index);
511 LayerListPopup menu = new LayerListPopup(layerList, layer);
512 menu.show(LayerListDialog.this, p.x, p.y-3);
513 }
514 }
515
516 class DblClickAdapter extends MouseAdapter {
517 @Override public void mouseClicked(MouseEvent e) {
518 if (e.getClickCount() == 2) {
519 int index = layerList.locationToIndex(e.getPoint());
520 if (!layerList.getCellBounds(index, index).contains(e.getPoint()))
521 return;
522 Layer layer = model.getLayer(index);
523 String current = Main.pref.get("marker.show "+layer.getName(),"show");
524 Main.pref.put("marker.show "+layer.getName(), current.equalsIgnoreCase("show") ? "hide" : "show");
525 layer.toggleVisible();
526 }
527 }
528 }
529
530 /**
531 * The action to move up the currently selected entries in the list.
532 */
533 class MoveUpAction extends AbstractAction implements IEnabledStateUpdating{
534 public MoveUpAction() {
535 putValue(SMALL_ICON, ImageProvider.get("dialogs", "up"));
536 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row up."));
537 updateEnabledState();
538 }
539
540 public void updateEnabledState() {
541 setEnabled(model.canMoveUp());
542 }
543
544 public void actionPerformed(ActionEvent e) {
545 model.moveUp();
546 }
547 }
548
549 /**
550 * The action to move down the currently selected entries in the list.
551 */
552 class MoveDownAction extends AbstractAction implements IEnabledStateUpdating {
553 public MoveDownAction() {
554 putValue(SMALL_ICON, ImageProvider.get("dialogs", "down"));
555 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row down."));
556 updateEnabledState();
557 }
558
559 public void updateEnabledState() {
560 setEnabled(model.canMoveDown());
561 }
562
563 public void actionPerformed(ActionEvent e) {
564 model.moveDown();
565 }
566 }
567
568 /**
569 * Observer interface to be implemented by views using {@see LayerListModel}
570 *
571 */
572 public interface LayerListModelListener {
573 public void makeVisible(int index, Layer layer);
574 public void refresh();
575 }
576
577 /**
578 * The layer list model. The model manages a list of layers and provides methods for
579 * moving layers up and down, for toggling their visibility, and for activating a layer.
580 *
581 * The model is a {@see ListModel} and it provides a {@see ListSelectionModel}. It expectes
582 * to be configured with a {@see DefaultListSelectionModel}. The selection model is used
583 * to update the selection state of views depending on messages sent to the model.
584 *
585 * The model manages a list of {@see LayerListModelListener} which are mainly notified if
586 * the model requires views to make a specific list entry visible.
587 *
588 * It also listens to {@see PropertyChangeEvent}s of every {@see Layer} it manages, in particular to
589 * the properties {@see Layer#VISIBLE_PROP} and {@see Layer#NAME_PROP}.
590 */
591 public static class LayerListModel extends DefaultListModel implements MapView.LayerChangeListener, PropertyChangeListener{
592
593 /** manages list selection state*/
594 private DefaultListSelectionModel selectionModel;
595 private CopyOnWriteArrayList<LayerListModelListener> listeners;
596
597 /**
598 * constructor
599 *
600 * @param selectionModel the list selection model
601 */
602 private LayerListModel(DefaultListSelectionModel selectionModel) {
603 this.selectionModel = selectionModel;
604 listeners = new CopyOnWriteArrayList<LayerListModelListener>();
605 }
606
607 /**
608 * Adds a listener to this model
609 *
610 * @param listener the listener
611 */
612 public void addLayerListModelListener(LayerListModelListener listener) {
613 if (listener != null) {
614 listeners.addIfAbsent(listener);
615 }
616 }
617
618 /**
619 * removes a listener from this model
620 * @param listener the listener
621 *
622 */
623 public void removeLayerListModelListener(LayerListModelListener listener) {
624 listeners.remove(listener);
625 }
626
627 /**
628 * Fires a make visible event to listeners
629 *
630 * @param index the index of the row to make visible
631 * @param layer the layer at this index
632 * @see LayerListModelListener#makeVisible(int, Layer)
633 */
634 protected void fireMakeVisible(int index, Layer layer) {
635 for (LayerListModelListener listener : listeners) {
636 listener.makeVisible(index, layer);
637 }
638 }
639
640 /**
641 * Fires a refresh event to listeners of this model
642 *
643 * @see LayerListModelListener#refresh()
644 */
645 protected void fireRefresh() {
646 for (LayerListModelListener listener : listeners) {
647 listener.refresh();
648 }
649 }
650
651 /**
652 * Populates the model with the current layers managed by
653 * {@see MapView}.
654 *
655 */
656 public void populate() {
657 for (Layer layer: getLayers()) {
658 // make sure the model is registered exactly once
659 //
660 layer.removePropertyChangeListener(this);
661 layer.addPropertyChangeListener(this);
662 }
663 fireContentsChanged(this, 0, getSize());
664 }
665
666 /**
667 * Marks <code>layer</code> as selected layer. Ignored, if
668 * layer is null.
669 *
670 * @param layer the layer.
671 */
672 public void setSelectedLayer(Layer layer) {
673 if (layer == null)
674 return;
675 int idx = getLayers().indexOf(layer);
676 if (idx >= 0) {
677 selectionModel.setSelectionInterval(idx, idx);
678 }
679 fireContentsChanged(this, 0, getSize());
680 ensureSelectedIsVisible();
681 }
682
683 /**
684 * Replies the list of currently selected layers. Never null, but may
685 * be empty.
686 *
687 * @return the list of currently selected layers. Never null, but may
688 * be empty.
689 */
690 public List<Layer> getSelectedLayers() {
691 ArrayList<Layer> selected = new ArrayList<Layer>();
692 for (int i=0; i<getLayers().size(); i++) {
693 if (selectionModel.isSelectedIndex(i)) {
694 selected.add(getLayers().get(i));
695 }
696 }
697 return selected;
698 }
699
700 /**
701 * Replies a the list of indices of the selected rows. Never null,
702 * but may be empty.
703 *
704 * @return the list of indices of the selected rows. Never null,
705 * but may be empty.
706 */
707 public List<Integer> getSelectedRows() {
708 ArrayList<Integer> selected = new ArrayList<Integer>();
709 for (int i=0; i<getLayers().size();i++) {
710 if (selectionModel.isSelectedIndex(i)) {
711 selected.add(i);
712 }
713 }
714 return selected;
715 }
716
717 /**
718 * Invoked if a layer managed by {@see MapView} is removed
719 *
720 * @param layer the layer which is removed
721 */
722 protected void onRemoveLayer(Layer layer) {
723 if (layer == null)
724 return;
725 layer.removePropertyChangeListener(this);
726 int size = getSize();
727 List<Integer> rows = getSelectedRows();
728 if (rows.isEmpty() && size > 0) {
729 selectionModel.setSelectionInterval(size-1, size-1);
730 }
731 fireRefresh();
732 ensureActiveSelected();
733 }
734
735 /**
736 * Invoked when a layer managed by {@see MapView} is added
737 *
738 * @param layer the layer
739 */
740 protected void onAddLayer(Layer layer) {
741 if (layer == null) return;
742 layer.addPropertyChangeListener(this);
743 fireContentsChanged(this, 0, getSize());
744 int idx = getLayers().indexOf(layer);
745 selectionModel.setSelectionInterval(idx, idx);
746 ensureSelectedIsVisible();
747 }
748
749 /**
750 * Replies the first layer. Null if no layers are present
751 *
752 * @return the first layer. Null if no layers are present
753 */
754 public Layer getFirstLayer() {
755 if (getSize() == 0) return null;
756 return getLayers().get(0);
757 }
758
759 /**
760 * Replies the layer at position <code>index</code>
761 *
762 * @param index the index
763 * @return the layer at position <code>index</code>. Null,
764 * if index is out of range.
765 */
766 public Layer getLayer(int index) {
767 if (index < 0 || index >= getSize())
768 return null;
769 return getLayers().get(index);
770 }
771
772 /**
773 * Replies true if the currently selected layers can move up
774 * by one position
775 *
776 * @return true if the currently selected layers can move up
777 * by one position
778 */
779 public boolean canMoveUp() {
780 List<Integer> sel = getSelectedRows();
781 return !sel.isEmpty() && sel.get(0) > 0;
782 }
783
784 /**
785 * Move up the currently selected layers by one position
786 *
787 */
788 public void moveUp() {
789 if (!canMoveUp()) return;
790 List<Integer> sel = getSelectedRows();
791 for (int row: sel) {
792 Layer l1 = getLayers().get(row);
793 Layer l2 = getLayers().get(row-1);
794 Main.map.mapView.moveLayer(l2,row);
795 Main.map.mapView.moveLayer(l1, row-1);
796 }
797 fireContentsChanged(this, 0, getSize());
798 selectionModel.clearSelection();
799 for(int row: sel) {
800 selectionModel.addSelectionInterval(row-1, row-1);
801 }
802 ensureSelectedIsVisible();
803 }
804
805 /**
806 * Replies true if the currently selected layers can move down
807 * by one position
808 *
809 * @return true if the currently selected layers can move down
810 * by one position
811 */
812 public boolean canMoveDown() {
813 List<Integer> sel = getSelectedRows();
814 return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1;
815 }
816
817 /**
818 * Move down the currently selected layers by one position
819 *
820 */
821 public void moveDown() {
822 if (!canMoveDown()) return;
823 List<Integer> sel = getSelectedRows();
824 Collections.reverse(sel);
825 for (int row: sel) {
826 Layer l1 = getLayers().get(row);
827 Layer l2 = getLayers().get(row+1);
828 Main.map.mapView.moveLayer(l1, row+1);
829 Main.map.mapView.moveLayer(l2, row);
830 }
831 fireContentsChanged(this, 0, getSize());
832 selectionModel.clearSelection();
833 for(int row: sel) {
834 selectionModel.addSelectionInterval(row+1, row+1);
835 }
836 ensureSelectedIsVisible();
837 }
838
839 /**
840 * Make sure the first of the selected layers is visible in the
841 * views of this model.
842 *
843 */
844 protected void ensureSelectedIsVisible() {
845 int index = selectionModel.getMinSelectionIndex();
846 if (index <0 )return;
847 if (index >= getLayers().size()) return;
848 Layer layer = getLayers().get(index);
849 fireMakeVisible(index, layer);
850 }
851
852 /**
853 * Replies a list of layers which are possible merge targets
854 * for <code>source</code>
855 *
856 * @param source the source layer
857 * @return a list of layers which are possible merge targets
858 * for <code>source</code>. Never null, but can be empty.
859 */
860 public List<Layer> getPossibleMergeTargets(Layer source) {
861 ArrayList<Layer> targets = new ArrayList<Layer>();
862 if (source == null)
863 return targets;
864 for(Layer target: getLayers()) {
865 if (source == target) {
866 continue;
867 }
868 if (target.isMergable(source)) {
869 targets.add(target);
870 }
871 }
872 return targets;
873 }
874
875 /**
876 * Replies the list of layers currently managed by {@see MapView}.
877 * Never null, but can be empty.
878 *
879 * @return the list of layers currently managed by {@see MapView}.
880 * Never null, but can be empty.
881 */
882 protected List<Layer> getLayers() {
883 if (Main.map == null || Main.map.mapView == null)
884 return Collections.<Layer>emptyList();
885 return Main.map.mapView.getAllLayersAsList();
886 }
887
888 /**
889 * Ensures that at least one layer is selected in the layer dialog
890 *
891 */
892 protected void ensureActiveSelected() {
893 if (getLayers().size() == 0) return;
894 if (getActiveLayer() != null) {
895 // there's an active layer - select it and make it
896 // visible
897 int idx = getLayers().indexOf(getActiveLayer());
898 selectionModel.setSelectionInterval(idx, idx);
899 ensureSelectedIsVisible();
900 } else {
901 // no active layer - select the first one and make
902 // it visible
903 selectionModel.setSelectionInterval(0, 0);
904 ensureSelectedIsVisible();
905 }
906 }
907
908 /**
909 * Replies the active layer. null, if no active layer is available
910 *
911 * @return the active layer. null, if no active layer is available
912 */
913 protected Layer getActiveLayer() {
914 if (Main.map == null || Main.map.mapView == null) return null;
915 return Main.map.mapView.getActiveLayer();
916 }
917
918 /* ------------------------------------------------------------------------------ */
919 /* Interface ListModel */
920 /* ------------------------------------------------------------------------------ */
921 @Override
922 public Object getElementAt(int index) {
923 return getLayers().get(index);
924 }
925
926 @Override
927 public int getSize() {
928 List<Layer> layers = getLayers();
929 if (layers == null) return 0;
930 return layers.size();
931 }
932
933 /* ------------------------------------------------------------------------------ */
934 /* Interface LayerChangeListener */
935 /* ------------------------------------------------------------------------------ */
936 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
937 if (oldLayer != null) {
938 int idx = getLayers().indexOf(oldLayer);
939 if (idx >= 0) {
940 fireContentsChanged(this, idx,idx);
941 }
942 }
943
944 if (newLayer != null) {
945 int idx = getLayers().indexOf(newLayer);
946 if (idx >= 0) {
947 fireContentsChanged(this, idx,idx);
948 }
949 }
950 ensureActiveSelected();
951 }
952
953 public void layerAdded(Layer newLayer) {
954 onAddLayer(newLayer);
955 }
956
957 public void layerRemoved(final Layer oldLayer) {
958 onRemoveLayer(oldLayer);
959 }
960
961 /* ------------------------------------------------------------------------------ */
962 /* Interface PropertyChangeListener */
963 /* ------------------------------------------------------------------------------ */
964 public void propertyChange(PropertyChangeEvent evt) {
965 if (evt.getSource() instanceof Layer) {
966 Layer layer = (Layer)evt.getSource();
967 final int idx = getLayers().indexOf(layer);
968 if (idx < 0) return;
969 fireRefresh();
970 }
971 }
972 }
973
974 static class LayerList extends JList {
975 public LayerList(ListModel dataModel) {
976 super(dataModel);
977 }
978
979 @Override
980 protected void processMouseEvent(MouseEvent e) {
981 // if the layer list is embedded in a detached dialog, the last row is
982 // selected if a user clicks in the empty space *below* the last row.
983 // This mouse event filter prevents this.
984 //
985 int idx = locationToIndex(e.getPoint());
986 // sometimes bounds can be null, see #3539
987 Rectangle bounds = getCellBounds(idx,idx);
988 if (bounds != null && bounds.contains(e.getPoint())) {
989 super.processMouseEvent(e);
990 }
991 }
992 }
993
994 /**
995 * Creates a {@see ShowHideLayerAction} for <code>layer</code> in the
996 * context of this {@see LayerListDialog}.
997 *
998 * @param layer the layer
999 * @return the action
1000 */
1001 public ShowHideLayerAction createShowHideLayerAction(Layer layer) {
1002 return new ShowHideLayerAction(layer);
1003 }
1004
1005 /**
1006 * Creates a {@see DeleteLayerAction} for <code>layer</code> in the
1007 * context of this {@see LayerListDialog}.
1008 *
1009 * @param layer the layer
1010 * @return the action
1011 */
1012 public DeleteLayerAction createDeleteLayerAction(Layer layer) {
1013 // the delete layer action doesn't depend on the current layer
1014 return new DeleteLayerAction();
1015 }
1016
1017 /**
1018 * Creates a {@see ActivateLayerAction} for <code>layer</code> in the
1019 * context of this {@see LayerListDialog}.
1020 *
1021 * @param layer the layer
1022 * @return the action
1023 */
1024 public ActivateLayerAction createActivateLayerAction(Layer layer) {
1025 return new ActivateLayerAction(layer);
1026 }
1027
1028 /**
1029 * Creates a {@see MergeLayerAction} for <code>layer</code> in the
1030 * context of this {@see LayerListDialog}.
1031 *
1032 * @param layer the layer
1033 * @return the action
1034 */
1035 public MergeAction createMergeLayerAction(Layer layer) {
1036 return new MergeAction(layer);
1037 }
1038}
Note: See TracBrowser for help on using the repository browser.