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

Last change on this file since 3705 was 3705, checked in by Upliner, 13 years ago

applied #5570 - implement layer opacity control

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