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

Last change on this file since 2025 was 2025, checked in by Gubaer, 15 years ago

new: improved dialog for uploading/saving modified layers on exit
new: improved dialog for uploading/saving modified layers if layers are deleted
new: new progress monitor which can delegate rendering to any Swing component
more setters/getters for properties in OSM data classes (fields are @deprecated); started to update references in the code base

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