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

Last change on this file since 5200 was 5200, checked in by akks, 12 years ago

see #7626, fix #7463: keys Ctrl-Shift-Up/Down, Enter, Spacebar work better in toggle dialogs
Enter and Spacebar = useful actions for list items (select, toggle, etc.)

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