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

Last change on this file since 4191 was 4191, checked in by stoecker, 13 years ago

remove old debug stuff

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