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

Last change on this file since 5028 was 5028, checked in by jttt, 12 years ago

Add possibility to hide side buttons in toggle dialogs permanently, show actions from buttons in popup menu

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