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

Last change on this file since 3941 was 3941, checked in by bastiK, 13 years ago

#5958 - Renaming layers erratic

  • Property svn:eol-style set to native
File size: 50.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.Dimension;
9import java.awt.Font;
10import java.awt.Point;
11import java.awt.Rectangle;
12import java.awt.event.ActionEvent;
13import java.awt.event.InputEvent;
14import java.awt.event.KeyEvent;
15import java.awt.event.MouseEvent;
16import java.beans.PropertyChangeEvent;
17import java.beans.PropertyChangeListener;
18import java.util.ArrayList;
19import java.util.Collections;
20import java.util.List;
21import java.util.concurrent.CopyOnWriteArrayList;
22
23import javax.swing.AbstractAction;
24import javax.swing.Action;
25import javax.swing.DefaultCellEditor;
26import javax.swing.DefaultListSelectionModel;
27import javax.swing.ImageIcon;
28import javax.swing.JCheckBox;
29import javax.swing.JComponent;
30import javax.swing.JLabel;
31import javax.swing.JMenuItem;
32import javax.swing.JPanel;
33import javax.swing.JPopupMenu;
34import javax.swing.JScrollPane;
35import javax.swing.JSlider;
36import javax.swing.JTable;
37import javax.swing.JTextField;
38import javax.swing.JViewport;
39import javax.swing.KeyStroke;
40import javax.swing.ListSelectionModel;
41import javax.swing.UIManager;
42import javax.swing.event.ChangeEvent;
43import javax.swing.event.ChangeListener;
44import javax.swing.event.ListSelectionEvent;
45import javax.swing.event.ListSelectionListener;
46import javax.swing.event.TableModelEvent;
47import javax.swing.event.TableModelListener;
48import javax.swing.table.AbstractTableModel;
49import javax.swing.table.DefaultTableCellRenderer;
50import javax.swing.table.TableCellRenderer;
51import javax.swing.table.TableModel;
52
53import org.openstreetmap.josm.Main;
54import org.openstreetmap.josm.actions.DuplicateLayerAction;
55import org.openstreetmap.josm.actions.MergeLayerAction;
56import org.openstreetmap.josm.gui.MapFrame;
57import org.openstreetmap.josm.gui.MapView;
58import org.openstreetmap.josm.gui.SideButton;
59import org.openstreetmap.josm.gui.help.HelpUtil;
60import org.openstreetmap.josm.gui.io.SaveLayersDialog;
61import org.openstreetmap.josm.gui.layer.Layer;
62import org.openstreetmap.josm.gui.layer.OsmDataLayer;
63import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
64import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
65import org.openstreetmap.josm.tools.CheckParameterUtil;
66import org.openstreetmap.josm.tools.ImageProvider;
67import org.openstreetmap.josm.tools.Shortcut;
68
69/**
70 * This is a toggle dialog which displays the list of layers. Actions allow to
71 * change the ordering of the layers, to hide/show layers, to activate layers,
72 * and to delete layers.
73 *
74 */
75public class LayerListDialog extends ToggleDialog {
76 //static private final Logger logger = Logger.getLogger(LayerListDialog.class.getName());
77
78 /** the unique instance of the dialog */
79 static private LayerListDialog instance;
80
81 /**
82 * Creates the instance of the dialog. It's connected to the map frame <code>mapFrame</code>
83 *
84 * @param mapFrame the map frame
85 */
86 static public void createInstance(MapFrame mapFrame) {
87 if (instance != null)
88 throw new IllegalStateException("Dialog was already created");
89 instance = new LayerListDialog(mapFrame);
90 }
91
92 /**
93 * Replies the instance of the dialog
94 *
95 * @return the instance of the dialog
96 * @throws IllegalStateException thrown, if the dialog is not created yet
97 * @see #createInstance(MapFrame)
98 */
99 static public LayerListDialog getInstance() throws IllegalStateException {
100 if (instance == null)
101 throw new IllegalStateException("Dialog not created yet. Invoke createInstance() first");
102 return instance;
103 }
104
105 /** the model for the layer list */
106 private LayerListModel model;
107
108 /** the selection model */
109 private DefaultListSelectionModel selectionModel;
110
111 /** the list of layers (technically its a JTable, but appears like a list) */
112 private LayerList layerList;
113
114 private SideButton opacityButton;
115
116 ActivateLayerAction activateLayerAction;
117
118 protected JPanel createButtonPanel() {
119 JPanel buttonPanel = getButtonPanel(5);
120
121 // -- move up action
122 MoveUpAction moveUpAction = new MoveUpAction();
123 adaptTo(moveUpAction, model);
124 adaptTo(moveUpAction,selectionModel);
125 buttonPanel.add(new SideButton(moveUpAction));
126
127 // -- move down action
128 MoveDownAction moveDownAction = new MoveDownAction();
129 adaptTo(moveDownAction, model);
130 adaptTo(moveDownAction,selectionModel);
131 buttonPanel.add(new SideButton(moveDownAction));
132
133 // -- activate action
134 activateLayerAction = new ActivateLayerAction();
135 adaptTo(activateLayerAction, selectionModel);
136 buttonPanel.add(new SideButton(activateLayerAction));
137
138 // -- show hide action
139 ShowHideLayerAction showHideLayerAction = new ShowHideLayerAction();
140 adaptTo(showHideLayerAction, selectionModel);
141 buttonPanel.add(new SideButton(showHideLayerAction));
142
143 //-- layer opacity action
144 LayerOpacityAction layerOpacityAction = new LayerOpacityAction();
145 adaptTo(layerOpacityAction, selectionModel);
146 opacityButton = new SideButton(layerOpacityAction);
147 buttonPanel.add(opacityButton);
148
149 // -- merge layer action
150 MergeAction mergeLayerAction = new MergeAction();
151 adaptTo(mergeLayerAction, model);
152 adaptTo(mergeLayerAction,selectionModel);
153 buttonPanel.add(new SideButton(mergeLayerAction));
154
155 // -- duplicate layer action
156 DuplicateAction duplicateLayerAction = new DuplicateAction();
157 adaptTo(duplicateLayerAction, model);
158 adaptTo(duplicateLayerAction, selectionModel);
159 buttonPanel.add(new SideButton(duplicateLayerAction));
160
161 //-- delete layer action
162 DeleteLayerAction deleteLayerAction = new DeleteLayerAction();
163 layerList.getActionMap().put("deleteLayer", deleteLayerAction);
164 adaptTo(deleteLayerAction, selectionModel);
165 buttonPanel.add(new SideButton(deleteLayerAction, false));
166
167 return buttonPanel;
168 }
169
170 /**
171 * Create an layer list and attach it to the given mapView.
172 */
173 protected LayerListDialog(MapFrame mapFrame) {
174 super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."),
175 Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L, Shortcut.GROUP_LAYER), 100, true);
176
177 // create the models
178 //
179 selectionModel = new DefaultListSelectionModel();
180 selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
181 model = new LayerListModel(selectionModel);
182
183 // create the list control
184 //
185 layerList = new LayerList(model);
186 layerList.setSelectionModel(selectionModel);
187 layerList.addMouseListener(new PopupMenuHandler());
188 layerList.setBackground(UIManager.getColor("Button.background"));
189 layerList.putClientProperty("terminateEditOnFocusLost", true);
190 layerList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
191 layerList.setTableHeader(null);
192 layerList.setShowGrid(false);
193 layerList.setIntercellSpacing(new Dimension(0, 0));
194 layerList.getColumnModel().getColumn(0).setCellRenderer(new ActiveLayerCellRenderer());
195 layerList.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(new ActiveLayerCheckBox()));
196 layerList.getColumnModel().getColumn(0).setMaxWidth(12);
197 layerList.getColumnModel().getColumn(0).setPreferredWidth(12);
198 layerList.getColumnModel().getColumn(0).setResizable(false);
199 layerList.getColumnModel().getColumn(1).setCellRenderer(new LayerVisibleCellRenderer());
200 layerList.getColumnModel().getColumn(1).setCellEditor(new LayerVisibleCellEditor(new LayerVisibleCheckBox()));
201 layerList.getColumnModel().getColumn(1).setMaxWidth(16);
202 layerList.getColumnModel().getColumn(1).setPreferredWidth(16);
203 layerList.getColumnModel().getColumn(1).setResizable(false);
204 layerList.getColumnModel().getColumn(2).setCellRenderer(new LayerNameCellRenderer());
205 layerList.getColumnModel().getColumn(2).setCellEditor(new LayerNameCellEditor(new JTextField()));
206 for (KeyStroke ks : new KeyStroke[] {
207 KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK),
208 KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK),
209 KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK),
210 KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_MASK),
211 KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK),
212 KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK),
213 KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0),
214 KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0),
215 })
216 {
217 layerList.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ks, new Object());
218 }
219
220 add(new JScrollPane(layerList), BorderLayout.CENTER);
221
222 // init the model
223 //
224 final MapView mapView = mapFrame.mapView;
225 model.populate();
226 model.setSelectedLayer(mapView.getActiveLayer());
227 model.addLayerListModelListener(
228 new LayerListModelListener() {
229 @Override
230 public void makeVisible(int row, Layer layer) {
231 layerList.scrollToVisible(row, 0);
232 layerList.repaint();
233 }
234 @Override
235 public void refresh() {
236 layerList.repaint();
237 }
238 }
239 );
240
241 add(createButtonPanel(), BorderLayout.SOUTH);
242 }
243
244 @Override
245 public void showNotify() {
246 MapView.addLayerChangeListener(activateLayerAction);
247 MapView.addLayerChangeListener(model);
248 model.populate();
249 }
250
251 @Override
252 public void hideNotify() {
253 MapView.removeLayerChangeListener(model);
254 MapView.removeLayerChangeListener(activateLayerAction);
255 }
256
257 public LayerListModel getModel() {
258 return model;
259 }
260
261 protected interface IEnabledStateUpdating {
262 void updateEnabledState();
263 }
264
265 /**
266 * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that
267 * <code>listener</code> receives a {@see IEnabledStateUpdating#updateEnabledState()}
268 * on every {@see ListSelectionEvent}.
269 *
270 * @param listener the listener
271 * @param listSelectionModel the source emitting {@see ListSelectionEvent}s
272 */
273 protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) {
274 listSelectionModel.addListSelectionListener(
275 new ListSelectionListener() {
276 @Override
277 public void valueChanged(ListSelectionEvent e) {
278 listener.updateEnabledState();
279 }
280 }
281 );
282 }
283
284 /**
285 * Wires <code>listener</code> to <code>listModel</code> in such a way, that
286 * <code>listener</code> receives a {@see IEnabledStateUpdating#updateEnabledState()}
287 * on every {@see ListDataEvent}.
288 *
289 * @param listener the listener
290 * @param listSelectionModel the source emitting {@see ListDataEvent}s
291 */
292 protected void adaptTo(final IEnabledStateUpdating listener, LayerListModel listModel) {
293 listModel.addTableModelListener(
294 new TableModelListener() {
295
296 @Override
297 public void tableChanged(TableModelEvent e) {
298 listener.updateEnabledState();
299 }
300 }
301 );
302 }
303
304 @Override
305 public void destroy() {
306 super.destroy();
307 instance = null;
308 }
309
310 /**
311 * The action to delete the currently selected layer
312 */
313 public final class DeleteLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
314 /**
315 * Creates a {@see DeleteLayerAction} which will delete the currently
316 * selected layers in the layer dialog.
317 *
318 */
319 public DeleteLayerAction() {
320 putValue(SMALL_ICON,ImageProvider.get("dialogs", "delete"));
321 putValue(SHORT_DESCRIPTION, tr("Delete the selected layers."));
322 putValue(NAME, tr("Delete"));
323 putValue("help", HelpUtil.ht("/Dialog/LayerList#DeleteLayer"));
324 updateEnabledState();
325 }
326
327 protected boolean enforceUploadOrSaveModifiedData(List<Layer> selectedLayers) {
328 SaveLayersDialog dialog = new SaveLayersDialog(Main.parent);
329 List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>();
330 for (Layer l: selectedLayers) {
331 if (! (l instanceof OsmDataLayer)) {
332 continue;
333 }
334 OsmDataLayer odl = (OsmDataLayer)l;
335 if ((odl.requiresSaveToFile() || odl.requiresUploadToServer()) && odl.data.isModified()) {
336 layersWithUnmodifiedChanges.add(odl);
337 }
338 }
339 dialog.prepareForSavingAndUpdatingLayersBeforeDelete();
340 if (!layersWithUnmodifiedChanges.isEmpty()) {
341 dialog.getModel().populate(layersWithUnmodifiedChanges);
342 dialog.setVisible(true);
343 switch(dialog.getUserAction()) {
344 case CANCEL: return false;
345 case PROCEED: return true;
346 default: return false;
347 }
348 }
349 return true;
350 }
351
352 @Override
353 public void actionPerformed(ActionEvent e) {
354 List<Layer> selectedLayers = getModel().getSelectedLayers();
355 if (selectedLayers.isEmpty())
356 return;
357 if (! enforceUploadOrSaveModifiedData(selectedLayers))
358 return;
359 for(Layer l: selectedLayers) {
360 Main.main.removeLayer(l);
361 }
362 }
363
364 @Override
365 public void updateEnabledState() {
366 setEnabled(! getModel().getSelectedLayers().isEmpty());
367 }
368
369 @Override
370 public Component createMenuComponent() {
371 return new JMenuItem(this);
372 }
373
374 @Override
375 public boolean supportLayers(List<Layer> layers) {
376 return true;
377 }
378
379 @Override
380 public boolean equals(Object obj) {
381 return obj instanceof DeleteLayerAction;
382 }
383
384 @Override
385 public int hashCode() {
386 return getClass().hashCode();
387 }
388 }
389
390 public final class ShowHideLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
391 private Layer layer;
392
393 /**
394 * Creates a {@see ShowHideLayerAction} which toggle the visibility of
395 * a specific layer.
396 *
397 * @param layer the layer. Must not be null.
398 * @exception IllegalArgumentException thrown, if layer is null
399 */
400 public ShowHideLayerAction(Layer layer) throws IllegalArgumentException {
401 this();
402 putValue(NAME, tr("Show/Hide"));
403 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
404 this.layer = layer;
405 updateEnabledState();
406 }
407
408 /**
409 * Creates a {@see ShowHideLayerAction} which will toggle the visibility of
410 * the currently selected layers
411 *
412 */
413 public ShowHideLayerAction() {
414 putValue(SMALL_ICON, ImageProvider.get("dialogs", "showhide"));
415 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the selected layer."));
416 putValue("help", HelpUtil.ht("/Dialog/LayerList#ShowHideLayer"));
417 updateEnabledState();
418 }
419
420 @Override
421 public void actionPerformed(ActionEvent e) {
422 if (layer != null) {
423 layer.toggleVisible();
424 } else {
425 for(Layer l : model.getSelectedLayers()) {
426 l.toggleVisible();
427 }
428 }
429 }
430
431 @Override
432 public void updateEnabledState() {
433 if (layer == null) {
434 setEnabled(! getModel().getSelectedLayers().isEmpty());
435 } else {
436 setEnabled(true);
437 }
438 }
439
440 @Override
441 public Component createMenuComponent() {
442 return new JMenuItem(this);
443 }
444
445 @Override
446 public boolean supportLayers(List<Layer> layers) {
447 return true;
448 }
449
450 @Override
451 public boolean equals(Object obj) {
452 return obj instanceof ShowHideLayerAction;
453 }
454
455 @Override
456 public int hashCode() {
457 return getClass().hashCode();
458 }
459 }
460
461 public final class LayerOpacityAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
462 private Layer layer;
463 private JPopupMenu popup;
464 private JSlider slider = new JSlider(JSlider.VERTICAL);
465
466 /**
467 * Creates a {@see LayerOpacityAction} which allows to chenge the
468 * opacity of one or more layers.
469 *
470 * @param layer the layer. Must not be null.
471 * @exception IllegalArgumentException thrown, if layer is null
472 */
473 public LayerOpacityAction(Layer layer) throws IllegalArgumentException {
474 this();
475 putValue(NAME, tr("Opacity"));
476 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
477 this.layer = layer;
478 updateEnabledState();
479 }
480
481 /**
482 * Creates a {@see ShowHideLayerAction} which will toggle the visibility of
483 * the currently selected layers
484 *
485 */
486 public LayerOpacityAction() {
487 putValue(SHORT_DESCRIPTION, tr("Adjust opacity of the layer."));
488 putValue(SMALL_ICON, ImageProvider.get("dialogs/layerlist", "transparency"));
489 updateEnabledState();
490
491 popup = new JPopupMenu();
492 slider.addChangeListener(new ChangeListener() {
493 @Override
494 public void stateChanged(ChangeEvent e) {
495 setOpacity((double)slider.getValue()/100);
496 }
497 });
498 popup.add(slider);
499 }
500
501 private void setOpacity(double value) {
502 if (!isEnabled()) return;
503 if (layer != null) {
504 layer.setOpacity(value);
505 } else {
506 for(Layer layer: model.getSelectedLayers()) {
507 layer.setOpacity(value);
508 }
509 }
510 }
511
512 private double getOpacity() {
513 if (layer != null)
514 return layer.getOpacity();
515 else {
516 double opacity = 0;
517 List<Layer> layers = model.getSelectedLayers();
518 for(Layer layer: layers) {
519 opacity += layer.getOpacity();
520 }
521 return opacity / layers.size();
522 }
523 }
524
525 @Override
526 public void actionPerformed(ActionEvent e) {
527 slider.setValue((int)Math.round(getOpacity()*100));
528 popup.show(opacityButton, 0, opacityButton.getHeight());
529 }
530
531 @Override
532 public void updateEnabledState() {
533 if (layer == null) {
534 setEnabled(! getModel().getSelectedLayers().isEmpty());
535 } else {
536 setEnabled(true);
537 }
538 }
539
540 @Override
541 public Component createMenuComponent() {
542 return new JMenuItem(this);
543 }
544
545 @Override
546 public boolean supportLayers(List<Layer> layers) {
547 return true;
548 }
549
550 @Override
551 public boolean equals(Object obj) {
552 return obj instanceof LayerOpacityAction;
553 }
554
555 @Override
556 public int hashCode() {
557 return getClass().hashCode();
558 }
559 }
560
561 /**
562 * The action to activate the currently selected layer
563 */
564
565 public final class ActivateLayerAction extends AbstractAction implements IEnabledStateUpdating, MapView.LayerChangeListener{
566 private Layer layer;
567
568 public ActivateLayerAction(Layer layer) {
569 this();
570 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
571 this.layer = layer;
572 putValue(NAME, tr("Activate"));
573 updateEnabledState();
574 }
575
576 public ActivateLayerAction() {
577 putValue(SMALL_ICON, ImageProvider.get("dialogs", "activate"));
578 putValue(SHORT_DESCRIPTION, tr("Activate the selected layer"));
579 putValue("help", HelpUtil.ht("/Dialog/LayerList#ActivateLayer"));
580 updateEnabledState();
581 }
582
583 @Override
584 public void actionPerformed(ActionEvent e) {
585 Layer toActivate;
586 if (layer != null) {
587 toActivate = layer;
588 } else {
589 toActivate = model.getSelectedLayers().get(0);
590 }
591 // model is going to be updated via LayerChangeListener
592 // and PropertyChangeEvents
593 Main.map.mapView.setActiveLayer(toActivate);
594 toActivate.setVisible(true);
595 }
596
597 protected boolean isActiveLayer(Layer layer) {
598 if (Main.map == null) return false;
599 if (Main.map.mapView == null) return false;
600 return Main.map.mapView.getActiveLayer() == layer;
601 }
602
603 @Override
604 public void updateEnabledState() {
605 if (layer == null) {
606 if (getModel().getSelectedLayers().size() != 1) {
607 setEnabled(false);
608 return;
609 }
610 Layer selectedLayer = getModel().getSelectedLayers().get(0);
611 setEnabled(!isActiveLayer(selectedLayer));
612 } else {
613 setEnabled(!isActiveLayer(layer));
614 }
615 }
616
617 @Override
618 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
619 updateEnabledState();
620 }
621 @Override
622 public void layerAdded(Layer newLayer) {
623 updateEnabledState();
624 }
625 @Override
626 public void layerRemoved(Layer oldLayer) {
627 updateEnabledState();
628 }
629 }
630
631 /**
632 * The action to merge the currently selected layer into another layer.
633 */
634 public final class MergeAction extends AbstractAction implements IEnabledStateUpdating {
635 private Layer layer;
636
637 public MergeAction(Layer layer) throws IllegalArgumentException {
638 this();
639 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
640 this.layer = layer;
641 putValue(NAME, tr("Merge"));
642 updateEnabledState();
643 }
644
645 public MergeAction() {
646 putValue(SMALL_ICON, ImageProvider.get("dialogs", "mergedown"));
647 putValue(SHORT_DESCRIPTION, tr("Merge this layer into another layer"));
648 putValue("help", HelpUtil.ht("/Dialog/LayerList#MergeLayer"));
649 updateEnabledState();
650 }
651
652 @Override
653 public void actionPerformed(ActionEvent e) {
654 if (layer != null) {
655 new MergeLayerAction().merge(layer);
656 } else {
657 Layer selectedLayer = getModel().getSelectedLayers().get(0);
658 new MergeLayerAction().merge(selectedLayer);
659 }
660 }
661
662 protected boolean isActiveLayer(Layer layer) {
663 if (Main.map == null) return false;
664 if (Main.map.mapView == null) return false;
665 return Main.map.mapView.getActiveLayer() == layer;
666 }
667
668 @Override
669 public void updateEnabledState() {
670 if (layer == null) {
671 if (getModel().getSelectedLayers().size() != 1) {
672 setEnabled(false);
673 return;
674 }
675 Layer selectedLayer = getModel().getSelectedLayers().get(0);
676 List<Layer> targets = getModel().getPossibleMergeTargets(selectedLayer);
677 setEnabled(!targets.isEmpty());
678 } else {
679 List<Layer> targets = getModel().getPossibleMergeTargets(layer);
680 setEnabled(!targets.isEmpty());
681 }
682 }
683 }
684
685 /**
686 * The action to merge the currently selected layer into another layer.
687 */
688 public final class DuplicateAction extends AbstractAction implements IEnabledStateUpdating {
689 private Layer layer;
690
691 public DuplicateAction(Layer layer) throws IllegalArgumentException {
692 this();
693 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
694 this.layer = layer;
695 putValue(NAME, tr("Duplicate"));
696 updateEnabledState();
697 }
698
699 public DuplicateAction() {
700 putValue(SMALL_ICON, ImageProvider.get("dialogs", "duplicatelayer"));
701 putValue(SHORT_DESCRIPTION, tr("Duplicate this layer"));
702 putValue("help", HelpUtil.ht("/Dialog/LayerList#DuplicateLayer"));
703 updateEnabledState();
704 }
705
706 @Override
707 public void actionPerformed(ActionEvent e) {
708 if (layer != null) {
709 new DuplicateLayerAction().duplicate(layer);
710 } else {
711 Layer selectedLayer = getModel().getSelectedLayers().get(0);
712 new DuplicateLayerAction().duplicate(selectedLayer);
713 }
714 }
715
716 protected boolean isActiveLayer(Layer layer) {
717 if (Main.map == null) return false;
718 if (Main.map.mapView == null) return false;
719 return Main.map.mapView.getActiveLayer() == layer;
720 }
721
722 @Override
723 public void updateEnabledState() {
724 if (layer == null) {
725 if (getModel().getSelectedLayers().size() == 1) {
726 setEnabled(DuplicateLayerAction.canDuplicate(getModel().getSelectedLayers().get(0)));
727 } else {
728 setEnabled(false);
729 }
730 } else {
731 setEnabled(DuplicateLayerAction.canDuplicate(layer));
732 }
733 }
734 }
735
736 private static class ActiveLayerCheckBox extends JCheckBox {
737 public ActiveLayerCheckBox() {
738 setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
739 ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank");
740 ImageIcon active = ImageProvider.get("dialogs/layerlist", "active");
741 setIcon(blank);
742 setSelectedIcon(active);
743 setRolloverIcon(blank);
744 setRolloverSelectedIcon(active);
745 setPressedIcon(ImageProvider.get("dialogs/layerlist", "active-pressed"));
746 }
747 }
748
749 private static class LayerVisibleCheckBox extends JCheckBox {
750 private final ImageIcon icon_eye;
751 private final ImageIcon icon_eye_translucent;
752 private boolean isTranslucent;
753 public LayerVisibleCheckBox() {
754 setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
755 icon_eye = ImageProvider.get("dialogs/layerlist", "eye");
756 icon_eye_translucent = ImageProvider.get("dialogs/layerlist", "eye-translucent");
757 setIcon(ImageProvider.get("dialogs/layerlist", "eye-off"));
758 setPressedIcon(ImageProvider.get("dialogs/layerlist", "eye-pressed"));
759 setSelectedIcon(icon_eye);
760 isTranslucent = false;
761 }
762
763 public void setTranslucent(boolean isTranslucent) {
764 if (this.isTranslucent == isTranslucent) return;
765 if (isTranslucent) {
766 setSelectedIcon(icon_eye_translucent);
767 } else {
768 setSelectedIcon(icon_eye);
769 }
770 this.isTranslucent = isTranslucent;
771 }
772
773 public void updateStatus(Layer layer) {
774 boolean visible = layer.isVisible();
775 setSelected(visible);
776 setTranslucent(layer.getOpacity()<1.0);
777 setToolTipText(visible ? tr("layer is currently visible (click to hide layer)") : tr("layer is currently hidden (click to show layer)"));
778 }
779 }
780
781 private static class ActiveLayerCellRenderer implements TableCellRenderer {
782 JCheckBox cb;
783 public ActiveLayerCellRenderer() {
784 cb = new ActiveLayerCheckBox();
785 }
786
787 @Override
788 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
789 boolean active = (Boolean) value;
790 cb.setSelected(active);
791 cb.setToolTipText(active ? tr("this layer is the active layer") : tr("this layer is not currently active (click to activate)"));
792 return cb;
793 }
794 }
795
796 private static class LayerVisibleCellRenderer implements TableCellRenderer {
797 LayerVisibleCheckBox cb;
798 public LayerVisibleCellRenderer() {
799 this.cb = new LayerVisibleCheckBox();
800 }
801
802 @Override
803 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
804 cb.updateStatus((Layer)value);
805 return cb;
806 }
807 }
808
809 private static class LayerVisibleCellEditor extends DefaultCellEditor {
810 LayerVisibleCheckBox cb;
811 public LayerVisibleCellEditor(LayerVisibleCheckBox cb) {
812 super(cb);
813 this.cb = cb;
814 }
815
816 @Override
817 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
818 cb.updateStatus((Layer)value);
819 return cb;
820 }
821 }
822
823 private static class LayerNameCellRenderer extends DefaultTableCellRenderer {
824
825 protected boolean isActiveLayer(Layer layer) {
826 if (Main.map == null) return false;
827 if (Main.map.mapView == null) return false;
828 return Main.map.mapView.getActiveLayer() == layer;
829 }
830
831 @Override
832 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
833 Layer layer = (Layer)value;
834 JLabel label = (JLabel)super.getTableCellRendererComponent(table,
835 layer.getName(), isSelected, hasFocus, row, column);
836 if (isActiveLayer(layer)) {
837 label.setFont(label.getFont().deriveFont(Font.BOLD));
838 }
839 label.setIcon(layer.getIcon());
840 label.setToolTipText(layer.getToolTipText());
841 return label;
842 }
843 }
844
845 private static class LayerNameCellEditor extends DefaultCellEditor {
846 public LayerNameCellEditor(JTextField tf) {
847 super(tf);
848 }
849
850 @Override
851 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
852 JTextField tf = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
853 tf.setText(value == null ? "" : ((Layer) value).getName());
854 return tf;
855 }
856 }
857
858 class PopupMenuHandler extends PopupMenuLauncher {
859 @Override
860 public void launch(MouseEvent evt) {
861 Point p = evt.getPoint();
862 int index = layerList.rowAtPoint(p);
863 if (index < 0) return;
864 if (!layerList.getCellRect(index, 2, false).contains(evt.getPoint()))
865 return;
866 if (!layerList.isRowSelected(index)) {
867 layerList.setRowSelectionInterval(index, index);
868 }
869 Layer layer = model.getLayer(index);
870 LayerListPopup menu = new LayerListPopup(getModel().getSelectedLayers(), layer);
871 menu.show(layerList, p.x, p.y-3);
872 }
873 }
874
875 /**
876 * The action to move up the currently selected entries in the list.
877 */
878 class MoveUpAction extends AbstractAction implements IEnabledStateUpdating{
879 public MoveUpAction() {
880 putValue(SMALL_ICON, ImageProvider.get("dialogs", "up"));
881 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row up."));
882 updateEnabledState();
883 }
884
885 @Override
886 public void updateEnabledState() {
887 setEnabled(model.canMoveUp());
888 }
889
890 @Override
891 public void actionPerformed(ActionEvent e) {
892 model.moveUp();
893 }
894 }
895
896 /**
897 * The action to move down the currently selected entries in the list.
898 */
899 class MoveDownAction extends AbstractAction implements IEnabledStateUpdating {
900 public MoveDownAction() {
901 putValue(SMALL_ICON, ImageProvider.get("dialogs", "down"));
902 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row down."));
903 updateEnabledState();
904 }
905
906 @Override
907 public void updateEnabledState() {
908 setEnabled(model.canMoveDown());
909 }
910
911 @Override
912 public void actionPerformed(ActionEvent e) {
913 model.moveDown();
914 }
915 }
916
917 /**
918 * Observer interface to be implemented by views using {@see LayerListModel}
919 *
920 */
921 public interface LayerListModelListener {
922 public void makeVisible(int index, Layer layer);
923 public void refresh();
924 }
925
926 /**
927 * The layer list model. The model manages a list of layers and provides methods for
928 * moving layers up and down, for toggling their visibility, and for activating a layer.
929 *
930 * The model is a {@see TableModel} and it provides a {@see ListSelectionModel}. It expects
931 * to be configured with a {@see DefaultListSelectionModel}. The selection model is used
932 * to update the selection state of views depending on messages sent to the model.
933 *
934 * The model manages a list of {@see LayerListModelListener} which are mainly notified if
935 * the model requires views to make a specific list entry visible.
936 *
937 * It also listens to {@see PropertyChangeEvent}s of every {@see Layer} it manages, in particular to
938 * the properties {@see Layer#VISIBLE_PROP} and {@see Layer#NAME_PROP}.
939 */
940 public class LayerListModel extends AbstractTableModel implements MapView.LayerChangeListener, PropertyChangeListener {
941 /** manages list selection state*/
942 private DefaultListSelectionModel selectionModel;
943 private CopyOnWriteArrayList<LayerListModelListener> listeners;
944
945 /**
946 * constructor
947 *
948 * @param selectionModel the list selection model
949 */
950 private LayerListModel(DefaultListSelectionModel selectionModel) {
951 this.selectionModel = selectionModel;
952 listeners = new CopyOnWriteArrayList<LayerListModelListener>();
953 }
954
955 /**
956 * Adds a listener to this model
957 *
958 * @param listener the listener
959 */
960 public void addLayerListModelListener(LayerListModelListener listener) {
961 if (listener != null) {
962 listeners.addIfAbsent(listener);
963 }
964 }
965
966 /**
967 * removes a listener from this model
968 * @param listener the listener
969 *
970 */
971 public void removeLayerListModelListener(LayerListModelListener listener) {
972 listeners.remove(listener);
973 }
974
975 /**
976 * Fires a make visible event to listeners
977 *
978 * @param index the index of the row to make visible
979 * @param layer the layer at this index
980 * @see LayerListModelListener#makeVisible(int, Layer)
981 */
982 protected void fireMakeVisible(int index, Layer layer) {
983 for (LayerListModelListener listener : listeners) {
984 listener.makeVisible(index, layer);
985 }
986 }
987
988 /**
989 * Fires a refresh event to listeners of this model
990 *
991 * @see LayerListModelListener#refresh()
992 */
993 protected void fireRefresh() {
994 for (LayerListModelListener listener : listeners) {
995 listener.refresh();
996 }
997 }
998
999 /**
1000 * Populates the model with the current layers managed by
1001 * {@see MapView}.
1002 *
1003 */
1004 public void populate() {
1005 for (Layer layer: getLayers()) {
1006 // make sure the model is registered exactly once
1007 //
1008 layer.removePropertyChangeListener(this);
1009 layer.addPropertyChangeListener(this);
1010 }
1011 fireTableDataChanged();
1012 }
1013
1014 /**
1015 * Marks <code>layer</code> as selected layer. Ignored, if
1016 * layer is null.
1017 *
1018 * @param layer the layer.
1019 */
1020 public void setSelectedLayer(Layer layer) {
1021 if (layer == null)
1022 return;
1023 int idx = getLayers().indexOf(layer);
1024 if (idx >= 0) {
1025 selectionModel.setSelectionInterval(idx, idx);
1026 }
1027 ensureSelectedIsVisible();
1028 }
1029
1030 /**
1031 * Replies the list of currently selected layers. Never null, but may
1032 * be empty.
1033 *
1034 * @return the list of currently selected layers. Never null, but may
1035 * be empty.
1036 */
1037 public List<Layer> getSelectedLayers() {
1038 ArrayList<Layer> selected = new ArrayList<Layer>();
1039 for (int i=0; i<getLayers().size(); i++) {
1040 if (selectionModel.isSelectedIndex(i)) {
1041 selected.add(getLayers().get(i));
1042 }
1043 }
1044 return selected;
1045 }
1046
1047 /**
1048 * Replies a the list of indices of the selected rows. Never null,
1049 * but may be empty.
1050 *
1051 * @return the list of indices of the selected rows. Never null,
1052 * but may be empty.
1053 */
1054 public List<Integer> getSelectedRows() {
1055 ArrayList<Integer> selected = new ArrayList<Integer>();
1056 for (int i=0; i<getLayers().size();i++) {
1057 if (selectionModel.isSelectedIndex(i)) {
1058 selected.add(i);
1059 }
1060 }
1061 return selected;
1062 }
1063
1064 /**
1065 * Invoked if a layer managed by {@see MapView} is removed
1066 *
1067 * @param layer the layer which is removed
1068 */
1069 protected void onRemoveLayer(Layer layer) {
1070 if (layer == null)
1071 return;
1072 layer.removePropertyChangeListener(this);
1073 int size = getRowCount();
1074 List<Integer> rows = getSelectedRows();
1075 if (rows.isEmpty() && size > 0) {
1076 selectionModel.setSelectionInterval(size-1, size-1);
1077 }
1078 fireTableDataChanged();
1079 fireRefresh();
1080 ensureActiveSelected();
1081 }
1082
1083 /**
1084 * Invoked when a layer managed by {@see MapView} is added
1085 *
1086 * @param layer the layer
1087 */
1088 protected void onAddLayer(Layer layer) {
1089 if (layer == null) return;
1090 layer.addPropertyChangeListener(this);
1091 fireTableDataChanged();
1092 int idx = getLayers().indexOf(layer);
1093 layerList.setRowHeight(idx, Math.max(16, layer.getIcon().getIconHeight()));
1094 selectionModel.setSelectionInterval(idx, idx);
1095 ensureSelectedIsVisible();
1096 }
1097
1098 /**
1099 * Replies the first layer. Null if no layers are present
1100 *
1101 * @return the first layer. Null if no layers are present
1102 */
1103 public Layer getFirstLayer() {
1104 if (getRowCount() == 0) return null;
1105 return getLayers().get(0);
1106 }
1107
1108 /**
1109 * Replies the layer at position <code>index</code>
1110 *
1111 * @param index the index
1112 * @return the layer at position <code>index</code>. Null,
1113 * if index is out of range.
1114 */
1115 public Layer getLayer(int index) {
1116 if (index < 0 || index >= getRowCount())
1117 return null;
1118 return getLayers().get(index);
1119 }
1120
1121 /**
1122 * Replies true if the currently selected layers can move up
1123 * by one position
1124 *
1125 * @return true if the currently selected layers can move up
1126 * by one position
1127 */
1128 public boolean canMoveUp() {
1129 List<Integer> sel = getSelectedRows();
1130 return !sel.isEmpty() && sel.get(0) > 0;
1131 }
1132
1133 /**
1134 * Move up the currently selected layers by one position
1135 *
1136 */
1137 public void moveUp() {
1138 if (!canMoveUp()) return;
1139 List<Integer> sel = getSelectedRows();
1140 for (int row : sel) {
1141 Layer l1 = getLayers().get(row);
1142 Layer l2 = getLayers().get(row-1);
1143 Main.map.mapView.moveLayer(l2,row);
1144 Main.map.mapView.moveLayer(l1, row-1);
1145 }
1146 fireTableDataChanged();
1147 selectionModel.clearSelection();
1148 for (int row : sel) {
1149 selectionModel.addSelectionInterval(row-1, row-1);
1150 }
1151 ensureSelectedIsVisible();
1152 }
1153
1154 /**
1155 * Replies true if the currently selected layers can move down
1156 * by one position
1157 *
1158 * @return true if the currently selected layers can move down
1159 * by one position
1160 */
1161 public boolean canMoveDown() {
1162 List<Integer> sel = getSelectedRows();
1163 return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1;
1164 }
1165
1166 /**
1167 * Move down the currently selected layers by one position
1168 *
1169 */
1170 public void moveDown() {
1171 if (!canMoveDown()) return;
1172 List<Integer> sel = getSelectedRows();
1173 Collections.reverse(sel);
1174 for (int row : sel) {
1175 Layer l1 = getLayers().get(row);
1176 Layer l2 = getLayers().get(row+1);
1177 Main.map.mapView.moveLayer(l1, row+1);
1178 Main.map.mapView.moveLayer(l2, row);
1179 }
1180 fireTableDataChanged();
1181 selectionModel.clearSelection();
1182 for (int row : sel) {
1183 selectionModel.addSelectionInterval(row+1, row+1);
1184 }
1185 ensureSelectedIsVisible();
1186 }
1187
1188 /**
1189 * Make sure the first of the selected layers is visible in the
1190 * views of this model.
1191 *
1192 */
1193 protected void ensureSelectedIsVisible() {
1194 int index = selectionModel.getMinSelectionIndex();
1195 if (index < 0) return;
1196 if (index >= getLayers().size()) return;
1197 Layer layer = getLayers().get(index);
1198 fireMakeVisible(index, layer);
1199 }
1200
1201 /**
1202 * Replies a list of layers which are possible merge targets
1203 * for <code>source</code>
1204 *
1205 * @param source the source layer
1206 * @return a list of layers which are possible merge targets
1207 * for <code>source</code>. Never null, but can be empty.
1208 */
1209 public List<Layer> getPossibleMergeTargets(Layer source) {
1210 ArrayList<Layer> targets = new ArrayList<Layer>();
1211 if (source == null)
1212 return targets;
1213 for (Layer target : getLayers()) {
1214 if (source == target) {
1215 continue;
1216 }
1217 if (target.isMergable(source)) {
1218 targets.add(target);
1219 }
1220 }
1221 return targets;
1222 }
1223
1224 /**
1225 * Replies the list of layers currently managed by {@see MapView}.
1226 * Never null, but can be empty.
1227 *
1228 * @return the list of layers currently managed by {@see MapView}.
1229 * Never null, but can be empty.
1230 */
1231 protected List<Layer> getLayers() {
1232 if (Main.map == null || Main.map.mapView == null)
1233 return Collections.<Layer>emptyList();
1234 return Main.map.mapView.getAllLayersAsList();
1235 }
1236
1237 /**
1238 * Ensures that at least one layer is selected in the layer dialog
1239 *
1240 */
1241 protected void ensureActiveSelected() {
1242 if (getLayers().isEmpty())
1243 return;
1244 if (getActiveLayer() != null) {
1245 // there's an active layer - select it and make it
1246 // visible
1247 int idx = getLayers().indexOf(getActiveLayer());
1248 selectionModel.setSelectionInterval(idx, idx);
1249 ensureSelectedIsVisible();
1250 } else {
1251 // no active layer - select the first one and make
1252 // it visible
1253 selectionModel.setSelectionInterval(0, 0);
1254 ensureSelectedIsVisible();
1255 }
1256 }
1257
1258 /**
1259 * Replies the active layer. null, if no active layer is available
1260 *
1261 * @return the active layer. null, if no active layer is available
1262 */
1263 protected Layer getActiveLayer() {
1264 if (Main.map == null || Main.map.mapView == null) return null;
1265 return Main.map.mapView.getActiveLayer();
1266 }
1267
1268 /* ------------------------------------------------------------------------------ */
1269 /* Interface TableModel */
1270 /* ------------------------------------------------------------------------------ */
1271
1272 @Override
1273 public int getRowCount() {
1274 List<Layer> layers = getLayers();
1275 if (layers == null) return 0;
1276 return layers.size();
1277 }
1278
1279 @Override
1280 public int getColumnCount() {
1281 return 3;
1282 }
1283
1284 @Override
1285 public Object getValueAt(int row, int col) {
1286 switch (col) {
1287 case 0: return getLayers().get(row) == getActiveLayer();
1288 case 1: return getLayers().get(row);
1289 case 2: return getLayers().get(row);
1290 default: throw new RuntimeException();
1291 }
1292 }
1293
1294 @Override
1295 public boolean isCellEditable(int row, int col) {
1296 if (col == 0 && getActiveLayer() == getLayers().get(row))
1297 return false;
1298 return true;
1299 }
1300
1301 @Override
1302 public void setValueAt(Object value, int row, int col) {
1303 Layer l = getLayers().get(row);
1304 switch (col) {
1305 case 0:
1306 Main.map.mapView.setActiveLayer(l);
1307 l.setVisible(true);
1308 break;
1309 case 1:
1310 l.setVisible((Boolean) value);
1311 break;
1312 case 2:
1313 l.setName((String) value);
1314 break;
1315 default: throw new RuntimeException();
1316 }
1317 fireTableCellUpdated(row, col);
1318 }
1319
1320 /* ------------------------------------------------------------------------------ */
1321 /* Interface LayerChangeListener */
1322 /* ------------------------------------------------------------------------------ */
1323 @Override
1324 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
1325 if (oldLayer != null) {
1326 int idx = getLayers().indexOf(oldLayer);
1327 if (idx >= 0) {
1328 fireTableRowsUpdated(idx,idx);
1329 }
1330 }
1331
1332 if (newLayer != null) {
1333 int idx = getLayers().indexOf(newLayer);
1334 if (idx >= 0) {
1335 fireTableRowsUpdated(idx,idx);
1336 }
1337 }
1338 ensureActiveSelected();
1339 }
1340
1341 @Override
1342 public void layerAdded(Layer newLayer) {
1343 onAddLayer(newLayer);
1344 }
1345
1346 @Override
1347 public void layerRemoved(final Layer oldLayer) {
1348 onRemoveLayer(oldLayer);
1349 }
1350
1351 /* ------------------------------------------------------------------------------ */
1352 /* Interface PropertyChangeListener */
1353 /* ------------------------------------------------------------------------------ */
1354 @Override
1355 public void propertyChange(PropertyChangeEvent evt) {
1356 if (evt.getSource() instanceof Layer) {
1357 Layer layer = (Layer)evt.getSource();
1358 final int idx = getLayers().indexOf(layer);
1359 if (idx < 0) return;
1360 fireRefresh();
1361 }
1362 }
1363 }
1364
1365 static class LayerList extends JTable {
1366 public LayerList(TableModel dataModel) {
1367 super(dataModel);
1368 }
1369
1370 public void scrollToVisible(int row, int col) {
1371 if (!(getParent() instanceof JViewport))
1372 return;
1373 JViewport viewport = (JViewport) getParent();
1374 Rectangle rect = getCellRect(row, col, true);
1375 Point pt = viewport.getViewPosition();
1376 rect.setLocation(rect.x - pt.x, rect.y - pt.y);
1377 viewport.scrollRectToVisible(rect);
1378 }
1379 }
1380
1381 /**
1382 * Creates a {@see ShowHideLayerAction} for <code>layer</code> in the
1383 * context of this {@see LayerListDialog}.
1384 *
1385 * @param layer the layer
1386 * @return the action
1387 */
1388 public ShowHideLayerAction createShowHideLayerAction() {
1389 ShowHideLayerAction act = new ShowHideLayerAction();
1390 act.putValue(Action.NAME, tr("Show/Hide"));
1391 return act;
1392 }
1393
1394 /**
1395 * Creates a {@see DeleteLayerAction} for <code>layer</code> in the
1396 * context of this {@see LayerListDialog}.
1397 *
1398 * @param layer the layer
1399 * @return the action
1400 */
1401 public DeleteLayerAction createDeleteLayerAction() {
1402 // the delete layer action doesn't depend on the current layer
1403 return new DeleteLayerAction();
1404 }
1405
1406 /**
1407 * Creates a {@see ActivateLayerAction} for <code>layer</code> in the
1408 * context of this {@see LayerListDialog}.
1409 *
1410 * @param layer the layer
1411 * @return the action
1412 */
1413 public ActivateLayerAction createActivateLayerAction(Layer layer) {
1414 return new ActivateLayerAction(layer);
1415 }
1416
1417 /**
1418 * Creates a {@see MergeLayerAction} for <code>layer</code> in the
1419 * context of this {@see LayerListDialog}.
1420 *
1421 * @param layer the layer
1422 * @return the action
1423 */
1424 public MergeAction createMergeLayerAction(Layer layer) {
1425 return new MergeAction(layer);
1426 }
1427}
Note: See TracBrowser for help on using the repository browser.