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

Last change on this file since 6070 was 5958, checked in by Don-vip, 11 years ago
  • Improve PopupMenuLauncher to automatically select JTable/JList/JTree line under cursor
  • Simplify all popup menus with this new feature
  • Remove custom popup menus in BoundingBoxSelection and BoundingBoxSelection to use default text field popup menus recently introduced
  • Fix EDT violations in changeset window
  • Make Autoscale actions publicly available in MainMenu
  • Add a new "Zoom to problem" mode in AutoScaleAction
  • Make "Zoom to problem" menu item in validator dialog use this new feature
  • Update enabled state of "Zoom to Conflict" and "Zoom to problem" against selection of conflict/validator dialog
  • Property svn:eol-style set to native
File size: 58.8 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.JViewport;
38import javax.swing.KeyStroke;
39import javax.swing.ListSelectionModel;
40import javax.swing.UIManager;
41import javax.swing.event.ChangeEvent;
42import javax.swing.event.ChangeListener;
43import javax.swing.event.ListDataEvent;
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.layer.JumpToMarkerActions;
60import org.openstreetmap.josm.gui.layer.Layer;
61import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
62import org.openstreetmap.josm.gui.layer.OsmDataLayer;
63import org.openstreetmap.josm.gui.util.GuiHelper;
64import org.openstreetmap.josm.gui.widgets.JosmTextField;
65import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
66import org.openstreetmap.josm.tools.CheckParameterUtil;
67import org.openstreetmap.josm.tools.ImageProvider;
68import org.openstreetmap.josm.tools.InputMapUtils;
69import org.openstreetmap.josm.tools.MultikeyActionsHandler;
70import org.openstreetmap.josm.tools.MultikeyShortcutAction;
71import org.openstreetmap.josm.tools.MultikeyShortcutAction.MultikeyInfo;
72import org.openstreetmap.josm.tools.Shortcut;
73
74/**
75 * This is a toggle dialog which displays the list of layers. Actions allow to
76 * change the ordering of the layers, to hide/show layers, to activate layers,
77 * and to delete layers.
78 *
79 */
80public class LayerListDialog extends ToggleDialog {
81 /** the unique instance of the dialog */
82 static private LayerListDialog instance;
83
84 /**
85 * Creates the instance of the dialog. It's connected to the map frame <code>mapFrame</code>
86 *
87 * @param mapFrame the map frame
88 */
89 static public void createInstance(MapFrame mapFrame) {
90 if (instance != null)
91 throw new IllegalStateException("Dialog was already created");
92 instance = new LayerListDialog(mapFrame);
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 - 1);
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 JosmTextField()));
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 {@link IEnabledStateUpdating#updateEnabledState()}
324 * on every {@link ListSelectionEvent}.
325 *
326 * @param listener the listener
327 * @param listSelectionModel the source emitting {@link 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 {@link IEnabledStateUpdating#updateEnabledState()}
343 * on every {@link ListDataEvent}.
344 *
345 * @param listener the listener
346 * @param listModel the source emitting {@link 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 {@link 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 @Override
390 public void actionPerformed(ActionEvent e) {
391 List<Layer> selectedLayers = getModel().getSelectedLayers();
392 if (selectedLayers.isEmpty())
393 return;
394 if (!Main.saveUnsavedModifications(selectedLayers, false))
395 return;
396 for (Layer l: selectedLayers) {
397 Main.main.removeLayer(l);
398 }
399 }
400
401 @Override
402 public void updateEnabledState() {
403 setEnabled(! getModel().getSelectedLayers().isEmpty());
404 }
405
406 @Override
407 public Component createMenuComponent() {
408 return new JMenuItem(this);
409 }
410
411 @Override
412 public boolean supportLayers(List<Layer> layers) {
413 return true;
414 }
415
416 @Override
417 public boolean equals(Object obj) {
418 return obj instanceof DeleteLayerAction;
419 }
420
421 @Override
422 public int hashCode() {
423 return getClass().hashCode();
424 }
425 }
426
427 public final class ShowHideLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction, MultikeyShortcutAction {
428
429 private WeakReference<Layer> lastLayer;
430 private Shortcut multikeyShortcut;
431
432 /**
433 * Creates a {@link ShowHideLayerAction} which will toggle the visibility of
434 * the currently selected layers
435 *
436 */
437 public ShowHideLayerAction(boolean init) {
438 putValue(NAME, tr("Show/hide"));
439 putValue(SMALL_ICON, ImageProvider.get("dialogs", "showhide"));
440 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the selected layer."));
441 putValue("help", HelpUtil.ht("/Dialog/LayerList#ShowHideLayer"));
442 multikeyShortcut = Shortcut.registerShortcut("core_multikey:showHideLayer", tr("Multikey: {0}",
443 tr("Show/hide layer")), KeyEvent.VK_S, Shortcut.SHIFT);
444 multikeyShortcut.setAccelerator(this);
445 if (init) {
446 updateEnabledState();
447 }
448 }
449
450 public ShowHideLayerAction() {
451 this(true);
452 }
453
454 @Override
455 public Shortcut getMultikeyShortcut() {
456 return multikeyShortcut;
457 }
458
459 @Override
460 public void actionPerformed(ActionEvent e) {
461 for(Layer l : model.getSelectedLayers()) {
462 l.toggleVisible();
463 }
464 }
465
466 @Override
467 public void executeMultikeyAction(int index, boolean repeat) {
468 Layer l = LayerListDialog.getLayerForIndex(index);
469 if (l != null) {
470 l.toggleVisible();
471 lastLayer = new WeakReference<Layer>(l);
472 } else if (repeat && lastLayer != null) {
473 l = lastLayer.get();
474 if (LayerListDialog.isLayerValid(l)) {
475 l.toggleVisible();
476 }
477 }
478 }
479
480 @Override
481 public void updateEnabledState() {
482 setEnabled(!model.getSelectedLayers().isEmpty());
483 }
484
485 @Override
486 public Component createMenuComponent() {
487 return new JMenuItem(this);
488 }
489
490 @Override
491 public boolean supportLayers(List<Layer> layers) {
492 return true;
493 }
494
495 @Override
496 public boolean equals(Object obj) {
497 return obj instanceof ShowHideLayerAction;
498 }
499
500 @Override
501 public int hashCode() {
502 return getClass().hashCode();
503 }
504
505 @Override
506 public List<MultikeyInfo> getMultikeyCombinations() {
507 return LayerListDialog.getLayerInfoByClass(Layer.class);
508 }
509
510 @Override
511 public MultikeyInfo getLastMultikeyAction() {
512 if (lastLayer != null)
513 return LayerListDialog.getLayerInfo(lastLayer.get());
514 return null;
515 }
516 }
517
518 public final class LayerOpacityAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
519 private Layer layer;
520 private JPopupMenu popup;
521 private JSlider slider = new JSlider(JSlider.VERTICAL);
522
523 /**
524 * Creates a {@link LayerOpacityAction} which allows to chenge the
525 * opacity of one or more layers.
526 *
527 * @param layer the layer. Must not be null.
528 * @exception IllegalArgumentException thrown, if layer is null
529 */
530 public LayerOpacityAction(Layer layer) throws IllegalArgumentException {
531 this();
532 putValue(NAME, tr("Opacity"));
533 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
534 this.layer = layer;
535 updateEnabledState();
536 }
537
538 /**
539 * Creates a {@link ShowHideLayerAction} which will toggle the visibility of
540 * the currently selected layers
541 *
542 */
543 public LayerOpacityAction() {
544 putValue(NAME, tr("Opacity"));
545 putValue(SHORT_DESCRIPTION, tr("Adjust opacity of the layer."));
546 putValue(SMALL_ICON, ImageProvider.get("dialogs/layerlist", "transparency"));
547 updateEnabledState();
548
549 popup = new JPopupMenu();
550 slider.addChangeListener(new ChangeListener() {
551 @Override
552 public void stateChanged(ChangeEvent e) {
553 setOpacity((double)slider.getValue()/100);
554 }
555 });
556 popup.add(slider);
557 }
558
559 private void setOpacity(double value) {
560 if (!isEnabled()) return;
561 if (layer != null) {
562 layer.setOpacity(value);
563 } else {
564 for(Layer layer: model.getSelectedLayers()) {
565 layer.setOpacity(value);
566 }
567 }
568 }
569
570 private double getOpacity() {
571 if (layer != null)
572 return layer.getOpacity();
573 else {
574 double opacity = 0;
575 List<Layer> layers = model.getSelectedLayers();
576 for(Layer layer: layers) {
577 opacity += layer.getOpacity();
578 }
579 return opacity / layers.size();
580 }
581 }
582
583 @Override
584 public void actionPerformed(ActionEvent e) {
585 slider.setValue((int)Math.round(getOpacity()*100));
586 if (e.getSource() == opacityButton) {
587 popup.show(opacityButton, 0, opacityButton.getHeight());
588 } else {
589 // Action can be trigger either by opacity button or by popup menu (in case toggle buttons are hidden).
590 // In that case, show it in the middle of screen (because opacityButton is not visible)
591 popup.show(Main.parent, Main.parent.getWidth() / 2, (Main.parent.getHeight() - popup.getHeight()) / 2);
592 }
593 }
594
595 @Override
596 public void updateEnabledState() {
597 if (layer == null) {
598 setEnabled(! getModel().getSelectedLayers().isEmpty());
599 } else {
600 setEnabled(true);
601 }
602 }
603
604 @Override
605 public Component createMenuComponent() {
606 return new JMenuItem(this);
607 }
608
609 @Override
610 public boolean supportLayers(List<Layer> layers) {
611 return true;
612 }
613
614 @Override
615 public boolean equals(Object obj) {
616 return obj instanceof LayerOpacityAction;
617 }
618
619 @Override
620 public int hashCode() {
621 return getClass().hashCode();
622 }
623 }
624
625 /**
626 * The action to activate the currently selected layer
627 */
628
629 public final class ActivateLayerAction extends AbstractAction implements IEnabledStateUpdating, MapView.LayerChangeListener, MultikeyShortcutAction{
630 private Layer layer;
631 private Shortcut multikeyShortcut;
632
633 public ActivateLayerAction(Layer layer) {
634 this();
635 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
636 this.layer = layer;
637 putValue(NAME, tr("Activate"));
638 updateEnabledState();
639 }
640
641 public ActivateLayerAction() {
642 putValue(NAME, tr("Activate"));
643 putValue(SMALL_ICON, ImageProvider.get("dialogs", "activate"));
644 putValue(SHORT_DESCRIPTION, tr("Activate the selected layer"));
645 multikeyShortcut = Shortcut.registerShortcut("core_multikey:activateLayer", tr("Multikey: {0}",
646 tr("Activate layer")), KeyEvent.VK_A, Shortcut.SHIFT);
647 multikeyShortcut.setAccelerator(this);
648 putValue("help", HelpUtil.ht("/Dialog/LayerList#ActivateLayer"));
649 }
650
651 @Override
652 public Shortcut getMultikeyShortcut() {
653 return multikeyShortcut;
654 }
655
656 @Override
657 public void actionPerformed(ActionEvent e) {
658 Layer toActivate;
659 if (layer != null) {
660 toActivate = layer;
661 } else {
662 toActivate = model.getSelectedLayers().get(0);
663 }
664 execute(toActivate);
665 }
666
667 private void execute(Layer layer) {
668 // model is going to be updated via LayerChangeListener
669 // and PropertyChangeEvents
670 Main.map.mapView.setActiveLayer(layer);
671 layer.setVisible(true);
672 }
673
674 protected boolean isActiveLayer(Layer layer) {
675 if (Main.map == null) return false;
676 if (Main.map.mapView == null) return false;
677 return Main.map.mapView.getActiveLayer() == layer;
678 }
679
680 @Override
681 public void updateEnabledState() {
682 GuiHelper.runInEDTAndWait(new Runnable() {
683 @Override
684 public void run() {
685 if (layer == null) {
686 if (getModel().getSelectedLayers().size() != 1) {
687 setEnabled(false);
688 return;
689 }
690 Layer selectedLayer = getModel().getSelectedLayers().get(0);
691 setEnabled(!isActiveLayer(selectedLayer));
692 } else {
693 setEnabled(!isActiveLayer(layer));
694 }
695 }
696 });
697 }
698
699 @Override
700 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
701 updateEnabledState();
702 }
703 @Override
704 public void layerAdded(Layer newLayer) {
705 updateEnabledState();
706 }
707 @Override
708 public void layerRemoved(Layer oldLayer) {
709 updateEnabledState();
710 }
711
712 @Override
713 public void executeMultikeyAction(int index, boolean repeat) {
714 Layer l = LayerListDialog.getLayerForIndex(index);
715 if (l != null) {
716 execute(l);
717 }
718 }
719
720 @Override
721 public List<MultikeyInfo> getMultikeyCombinations() {
722 return LayerListDialog.getLayerInfoByClass(Layer.class);
723 }
724
725 @Override
726 public MultikeyInfo getLastMultikeyAction() {
727 return null; // Repeating action doesn't make much sense for activating
728 }
729 }
730
731 /**
732 * The action to merge the currently selected layer into another layer.
733 */
734 public final class MergeAction extends AbstractAction implements IEnabledStateUpdating {
735 private Layer layer;
736
737 public MergeAction(Layer layer) throws IllegalArgumentException {
738 this();
739 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
740 this.layer = layer;
741 putValue(NAME, tr("Merge"));
742 updateEnabledState();
743 }
744
745 public MergeAction() {
746 putValue(NAME, tr("Merge"));
747 putValue(SMALL_ICON, ImageProvider.get("dialogs", "mergedown"));
748 putValue(SHORT_DESCRIPTION, tr("Merge this layer into another layer"));
749 putValue("help", HelpUtil.ht("/Dialog/LayerList#MergeLayer"));
750 updateEnabledState();
751 }
752
753 @Override
754 public void actionPerformed(ActionEvent e) {
755 if (layer != null) {
756 new MergeLayerAction().merge(layer);
757 } else {
758 if (getModel().getSelectedLayers().size() == 1) {
759 Layer selectedLayer = getModel().getSelectedLayers().get(0);
760 new MergeLayerAction().merge(selectedLayer);
761 } else {
762 new MergeLayerAction().merge(getModel().getSelectedLayers());
763 }
764 }
765 }
766
767 protected boolean isActiveLayer(Layer layer) {
768 if (Main.map == null) return false;
769 if (Main.map.mapView == null) return false;
770 return Main.map.mapView.getActiveLayer() == layer;
771 }
772
773 @Override
774 public void updateEnabledState() {
775 if (layer == null) {
776 if (getModel().getSelectedLayers().isEmpty()) {
777 setEnabled(false);
778 } else if (getModel().getSelectedLayers().size() > 1) {
779 Layer firstLayer = getModel().getSelectedLayers().get(0);
780 for (Layer l: getModel().getSelectedLayers()) {
781 if (l != firstLayer && (!l.isMergable(firstLayer) || !firstLayer.isMergable(l))) {
782 setEnabled(false);
783 return;
784 }
785 }
786 setEnabled(true);
787 } else {
788 Layer selectedLayer = getModel().getSelectedLayers().get(0);
789 List<Layer> targets = getModel().getPossibleMergeTargets(selectedLayer);
790 setEnabled(!targets.isEmpty());
791 }
792 } else {
793 List<Layer> targets = getModel().getPossibleMergeTargets(layer);
794 setEnabled(!targets.isEmpty());
795 }
796 }
797 }
798
799 /**
800 * The action to merge the currently selected layer into another layer.
801 */
802 public final class DuplicateAction extends AbstractAction implements IEnabledStateUpdating {
803 private Layer layer;
804
805 public DuplicateAction(Layer layer) throws IllegalArgumentException {
806 this();
807 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
808 this.layer = layer;
809 updateEnabledState();
810 }
811
812 public DuplicateAction() {
813 putValue(NAME, tr("Duplicate"));
814 putValue(SMALL_ICON, ImageProvider.get("dialogs", "duplicatelayer"));
815 putValue(SHORT_DESCRIPTION, tr("Duplicate this layer"));
816 putValue("help", HelpUtil.ht("/Dialog/LayerList#DuplicateLayer"));
817 updateEnabledState();
818 }
819
820 private void duplicate(Layer layer) {
821 if (Main.map == null || Main.map.mapView == null)
822 return;
823
824 List<String> layerNames = new ArrayList<String>();
825 for (Layer l: Main.map.mapView.getAllLayers()) {
826 layerNames.add(l.getName());
827 }
828 if (layer instanceof OsmDataLayer) {
829 OsmDataLayer oldLayer = (OsmDataLayer)layer;
830 // Translators: "Copy of {layer name}"
831 String newName = tr("Copy of {0}", oldLayer.getName());
832 int i = 2;
833 while (layerNames.contains(newName)) {
834 // Translators: "Copy {number} of {layer name}"
835 newName = tr("Copy {1} of {0}", oldLayer.getName(), i);
836 i++;
837 }
838 Main.main.addLayer(new OsmDataLayer(oldLayer.data.clone(), newName, null));
839 }
840 }
841
842 @Override
843 public void actionPerformed(ActionEvent e) {
844 if (layer != null) {
845 duplicate(layer);
846 } else {
847 duplicate(getModel().getSelectedLayers().get(0));
848 }
849 }
850
851 protected boolean isActiveLayer(Layer layer) {
852 if (Main.map == null || Main.map.mapView == null)
853 return false;
854 return Main.map.mapView.getActiveLayer() == layer;
855 }
856
857 @Override
858 public void updateEnabledState() {
859 if (layer == null) {
860 if (getModel().getSelectedLayers().size() == 1) {
861 setEnabled(getModel().getSelectedLayers().get(0) instanceof OsmDataLayer);
862 } else {
863 setEnabled(false);
864 }
865 } else {
866 setEnabled(layer instanceof OsmDataLayer);
867 }
868 }
869 }
870
871 private static class ActiveLayerCheckBox extends JCheckBox {
872 public ActiveLayerCheckBox() {
873 setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
874 ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank");
875 ImageIcon active = ImageProvider.get("dialogs/layerlist", "active");
876 setIcon(blank);
877 setSelectedIcon(active);
878 setRolloverIcon(blank);
879 setRolloverSelectedIcon(active);
880 setPressedIcon(ImageProvider.get("dialogs/layerlist", "active-pressed"));
881 }
882 }
883
884 private static class LayerVisibleCheckBox extends JCheckBox {
885 private final ImageIcon icon_eye;
886 private final ImageIcon icon_eye_translucent;
887 private boolean isTranslucent;
888 public LayerVisibleCheckBox() {
889 setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
890 icon_eye = ImageProvider.get("dialogs/layerlist", "eye");
891 icon_eye_translucent = ImageProvider.get("dialogs/layerlist", "eye-translucent");
892 setIcon(ImageProvider.get("dialogs/layerlist", "eye-off"));
893 setPressedIcon(ImageProvider.get("dialogs/layerlist", "eye-pressed"));
894 setSelectedIcon(icon_eye);
895 isTranslucent = false;
896 }
897
898 public void setTranslucent(boolean isTranslucent) {
899 if (this.isTranslucent == isTranslucent) return;
900 if (isTranslucent) {
901 setSelectedIcon(icon_eye_translucent);
902 } else {
903 setSelectedIcon(icon_eye);
904 }
905 this.isTranslucent = isTranslucent;
906 }
907
908 public void updateStatus(Layer layer) {
909 boolean visible = layer.isVisible();
910 setSelected(visible);
911 setTranslucent(layer.getOpacity()<1.0);
912 setToolTipText(visible ? tr("layer is currently visible (click to hide layer)") : tr("layer is currently hidden (click to show layer)"));
913 }
914 }
915
916 private static class ActiveLayerCellRenderer implements TableCellRenderer {
917 JCheckBox cb;
918 public ActiveLayerCellRenderer() {
919 cb = new ActiveLayerCheckBox();
920 }
921
922 @Override
923 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
924 boolean active = value != null && (Boolean) value;
925 cb.setSelected(active);
926 cb.setToolTipText(active ? tr("this layer is the active layer") : tr("this layer is not currently active (click to activate)"));
927 return cb;
928 }
929 }
930
931 private static class LayerVisibleCellRenderer implements TableCellRenderer {
932 LayerVisibleCheckBox cb;
933 public LayerVisibleCellRenderer() {
934 this.cb = new LayerVisibleCheckBox();
935 }
936
937 @Override
938 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
939 if (value != null) {
940 cb.updateStatus((Layer)value);
941 }
942 return cb;
943 }
944 }
945
946 private static class LayerVisibleCellEditor extends DefaultCellEditor {
947 LayerVisibleCheckBox cb;
948 public LayerVisibleCellEditor(LayerVisibleCheckBox cb) {
949 super(cb);
950 this.cb = cb;
951 }
952
953 @Override
954 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
955 cb.updateStatus((Layer)value);
956 return cb;
957 }
958 }
959
960 private class LayerNameCellRenderer extends DefaultTableCellRenderer {
961
962 protected boolean isActiveLayer(Layer layer) {
963 if (Main.map == null) return false;
964 if (Main.map.mapView == null) return false;
965 return Main.map.mapView.getActiveLayer() == layer;
966 }
967
968 @Override
969 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
970 if (value == null)
971 return this;
972 Layer layer = (Layer)value;
973 JLabel label = (JLabel)super.getTableCellRendererComponent(table,
974 layer.getName(), isSelected, hasFocus, row, column);
975 if (isActiveLayer(layer)) {
976 label.setFont(label.getFont().deriveFont(Font.BOLD));
977 }
978 if(Main.pref.getBoolean("dialog.layer.colorname", true)) {
979 Color c = layer.getColor(false);
980 if(c != null) {
981 Color oc = null;
982 for(Layer l : model.getLayers()) {
983 oc = l.getColor(false);
984 if(oc != null) {
985 if(oc.equals(c)) {
986 oc = null;
987 } else {
988 break;
989 }
990 }
991 }
992 /* not more than one color, don't use coloring */
993 if(oc == null) {
994 c = null;
995 }
996 }
997 if(c == null) {
998 c = Main.pref.getUIColor(isSelected ? "Table.selectionForeground" : "Table.foreground");
999 }
1000 label.setForeground(c);
1001 }
1002 label.setIcon(layer.getIcon());
1003 label.setToolTipText(layer.getToolTipText());
1004 return label;
1005 }
1006 }
1007
1008 private static class LayerNameCellEditor extends DefaultCellEditor {
1009 public LayerNameCellEditor(JosmTextField tf) {
1010 super(tf);
1011 }
1012
1013 @Override
1014 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
1015 JosmTextField tf = (JosmTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
1016 tf.setText(value == null ? "" : ((Layer) value).getName());
1017 return tf;
1018 }
1019 }
1020
1021 class PopupMenuHandler extends PopupMenuLauncher {
1022 @Override public void launch(MouseEvent evt) {
1023 Layer layer = getModel().getLayer(layerList.getSelectedRow());
1024 menu = new LayerListPopup(getModel().getSelectedLayers(), layer);
1025 super.launch(evt);
1026 }
1027 }
1028
1029 /**
1030 * The action to move up the currently selected entries in the list.
1031 */
1032 class MoveUpAction extends AbstractAction implements IEnabledStateUpdating{
1033 public MoveUpAction() {
1034 putValue(NAME, tr("Move up"));
1035 putValue(SMALL_ICON, ImageProvider.get("dialogs", "up"));
1036 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row up."));
1037 updateEnabledState();
1038 }
1039
1040 @Override
1041 public void updateEnabledState() {
1042 setEnabled(model.canMoveUp());
1043 }
1044
1045 @Override
1046 public void actionPerformed(ActionEvent e) {
1047 model.moveUp();
1048 }
1049 }
1050
1051 /**
1052 * The action to move down the currently selected entries in the list.
1053 */
1054 class MoveDownAction extends AbstractAction implements IEnabledStateUpdating {
1055 public MoveDownAction() {
1056 putValue(NAME, tr("Move down"));
1057 putValue(SMALL_ICON, ImageProvider.get("dialogs", "down"));
1058 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row down."));
1059 updateEnabledState();
1060 }
1061
1062 @Override
1063 public void updateEnabledState() {
1064 setEnabled(model.canMoveDown());
1065 }
1066
1067 @Override
1068 public void actionPerformed(ActionEvent e) {
1069 model.moveDown();
1070 }
1071 }
1072
1073 /**
1074 * Observer interface to be implemented by views using {@link LayerListModel}
1075 *
1076 */
1077 public interface LayerListModelListener {
1078 public void makeVisible(int index, Layer layer);
1079 public void refresh();
1080 }
1081
1082 /**
1083 * The layer list model. The model manages a list of layers and provides methods for
1084 * moving layers up and down, for toggling their visibility, and for activating a layer.
1085 *
1086 * The model is a {@link TableModel} and it provides a {@link ListSelectionModel}. It expects
1087 * to be configured with a {@link DefaultListSelectionModel}. The selection model is used
1088 * to update the selection state of views depending on messages sent to the model.
1089 *
1090 * The model manages a list of {@link LayerListModelListener} which are mainly notified if
1091 * the model requires views to make a specific list entry visible.
1092 *
1093 * It also listens to {@link PropertyChangeEvent}s of every {@link Layer} it manages, in particular to
1094 * the properties {@link Layer#VISIBLE_PROP} and {@link Layer#NAME_PROP}.
1095 */
1096 public class LayerListModel extends AbstractTableModel implements MapView.LayerChangeListener, PropertyChangeListener {
1097 /** manages list selection state*/
1098 private DefaultListSelectionModel selectionModel;
1099 private CopyOnWriteArrayList<LayerListModelListener> listeners;
1100
1101 /**
1102 * constructor
1103 *
1104 * @param selectionModel the list selection model
1105 */
1106 private LayerListModel(DefaultListSelectionModel selectionModel) {
1107 this.selectionModel = selectionModel;
1108 listeners = new CopyOnWriteArrayList<LayerListModelListener>();
1109 }
1110
1111 /**
1112 * Adds a listener to this model
1113 *
1114 * @param listener the listener
1115 */
1116 public void addLayerListModelListener(LayerListModelListener listener) {
1117 if (listener != null) {
1118 listeners.addIfAbsent(listener);
1119 }
1120 }
1121
1122 /**
1123 * removes a listener from this model
1124 * @param listener the listener
1125 *
1126 */
1127 public void removeLayerListModelListener(LayerListModelListener listener) {
1128 listeners.remove(listener);
1129 }
1130
1131 /**
1132 * Fires a make visible event to listeners
1133 *
1134 * @param index the index of the row to make visible
1135 * @param layer the layer at this index
1136 * @see LayerListModelListener#makeVisible(int, Layer)
1137 */
1138 protected void fireMakeVisible(int index, Layer layer) {
1139 for (LayerListModelListener listener : listeners) {
1140 listener.makeVisible(index, layer);
1141 }
1142 }
1143
1144 /**
1145 * Fires a refresh event to listeners of this model
1146 *
1147 * @see LayerListModelListener#refresh()
1148 */
1149 protected void fireRefresh() {
1150 for (LayerListModelListener listener : listeners) {
1151 listener.refresh();
1152 }
1153 }
1154
1155 /**
1156 * Populates the model with the current layers managed by
1157 * {@link MapView}.
1158 *
1159 */
1160 public void populate() {
1161 for (Layer layer: getLayers()) {
1162 // make sure the model is registered exactly once
1163 //
1164 layer.removePropertyChangeListener(this);
1165 layer.addPropertyChangeListener(this);
1166 }
1167 fireTableDataChanged();
1168 }
1169
1170 /**
1171 * Marks <code>layer</code> as selected layer. Ignored, if
1172 * layer is null.
1173 *
1174 * @param layer the layer.
1175 */
1176 public void setSelectedLayer(Layer layer) {
1177 if (layer == null)
1178 return;
1179 int idx = getLayers().indexOf(layer);
1180 if (idx >= 0) {
1181 selectionModel.setSelectionInterval(idx, idx);
1182 }
1183 ensureSelectedIsVisible();
1184 }
1185
1186 /**
1187 * Replies the list of currently selected layers. Never null, but may
1188 * be empty.
1189 *
1190 * @return the list of currently selected layers. Never null, but may
1191 * be empty.
1192 */
1193 public List<Layer> getSelectedLayers() {
1194 ArrayList<Layer> selected = new ArrayList<Layer>();
1195 for (int i=0; i<getLayers().size(); i++) {
1196 if (selectionModel.isSelectedIndex(i)) {
1197 selected.add(getLayers().get(i));
1198 }
1199 }
1200 return selected;
1201 }
1202
1203 /**
1204 * Replies a the list of indices of the selected rows. Never null,
1205 * but may be empty.
1206 *
1207 * @return the list of indices of the selected rows. Never null,
1208 * but may be empty.
1209 */
1210 public List<Integer> getSelectedRows() {
1211 ArrayList<Integer> selected = new ArrayList<Integer>();
1212 for (int i=0; i<getLayers().size();i++) {
1213 if (selectionModel.isSelectedIndex(i)) {
1214 selected.add(i);
1215 }
1216 }
1217 return selected;
1218 }
1219
1220 /**
1221 * Invoked if a layer managed by {@link MapView} is removed
1222 *
1223 * @param layer the layer which is removed
1224 */
1225 protected void onRemoveLayer(Layer layer) {
1226 if (layer == null)
1227 return;
1228 layer.removePropertyChangeListener(this);
1229 final int size = getRowCount();
1230 final List<Integer> rows = getSelectedRows();
1231 GuiHelper.runInEDTAndWait(new Runnable() {
1232 @Override
1233 public void run() {
1234 if (rows.isEmpty() && size > 0) {
1235 selectionModel.setSelectionInterval(size-1, size-1);
1236 }
1237 fireTableDataChanged();
1238 fireRefresh();
1239 ensureActiveSelected();
1240 }
1241 });
1242 }
1243
1244 /**
1245 * Invoked when a layer managed by {@link 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 {@link MapView}.
1387 * Never null, but can be empty.
1388 *
1389 * @return the list of layers currently managed by {@link 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 final Layer activeLayer = getActiveLayer();
1406 if (activeLayer != null) {
1407 // there's an active layer - select it and make it
1408 // visible
1409 int idx = getLayers().indexOf(activeLayer);
1410 selectionModel.setSelectionInterval(idx, idx);
1411 ensureSelectedIsVisible();
1412 } else {
1413 // no active layer - select the first one and make
1414 // it visible
1415 selectionModel.setSelectionInterval(0, 0);
1416 ensureSelectedIsVisible();
1417 }
1418 }
1419
1420 /**
1421 * Replies the active layer. null, if no active layer is available
1422 *
1423 * @return the active layer. null, if no active layer is available
1424 */
1425 protected Layer getActiveLayer() {
1426 if (Main.map == null || Main.map.mapView == null) return null;
1427 return Main.map.mapView.getActiveLayer();
1428 }
1429
1430 /* ------------------------------------------------------------------------------ */
1431 /* Interface TableModel */
1432 /* ------------------------------------------------------------------------------ */
1433
1434 @Override
1435 public int getRowCount() {
1436 List<Layer> layers = getLayers();
1437 if (layers == null) return 0;
1438 return layers.size();
1439 }
1440
1441 @Override
1442 public int getColumnCount() {
1443 return 3;
1444 }
1445
1446 @Override
1447 public Object getValueAt(int row, int col) {
1448 if (row >= 0 && row < getLayers().size()) {
1449 switch (col) {
1450 case 0: return getLayers().get(row) == getActiveLayer();
1451 case 1: return getLayers().get(row);
1452 case 2: return getLayers().get(row);
1453 default: throw new RuntimeException();
1454 }
1455 }
1456 return null;
1457 }
1458
1459 @Override
1460 public boolean isCellEditable(int row, int col) {
1461 if (col == 0 && getActiveLayer() == getLayers().get(row))
1462 return false;
1463 return true;
1464 }
1465
1466 @Override
1467 public void setValueAt(Object value, int row, int col) {
1468 Layer l = getLayers().get(row);
1469 switch (col) {
1470 case 0:
1471 Main.map.mapView.setActiveLayer(l);
1472 l.setVisible(true);
1473 break;
1474 case 1:
1475 l.setVisible((Boolean) value);
1476 break;
1477 case 2:
1478 l.setName((String) value);
1479 break;
1480 default: throw new RuntimeException();
1481 }
1482 fireTableCellUpdated(row, col);
1483 }
1484
1485 /* ------------------------------------------------------------------------------ */
1486 /* Interface LayerChangeListener */
1487 /* ------------------------------------------------------------------------------ */
1488 @Override
1489 public void activeLayerChange(final Layer oldLayer, final Layer newLayer) {
1490 GuiHelper.runInEDTAndWait(new Runnable() {
1491 @Override
1492 public void run() {
1493 if (oldLayer != null) {
1494 int idx = getLayers().indexOf(oldLayer);
1495 if (idx >= 0) {
1496 fireTableRowsUpdated(idx,idx);
1497 }
1498 }
1499
1500 if (newLayer != null) {
1501 int idx = getLayers().indexOf(newLayer);
1502 if (idx >= 0) {
1503 fireTableRowsUpdated(idx,idx);
1504 }
1505 }
1506 ensureActiveSelected();
1507 }
1508 });
1509 }
1510
1511 @Override
1512 public void layerAdded(Layer newLayer) {
1513 onAddLayer(newLayer);
1514 }
1515
1516 @Override
1517 public void layerRemoved(final Layer oldLayer) {
1518 onRemoveLayer(oldLayer);
1519 }
1520
1521 /* ------------------------------------------------------------------------------ */
1522 /* Interface PropertyChangeListener */
1523 /* ------------------------------------------------------------------------------ */
1524 @Override
1525 public void propertyChange(PropertyChangeEvent evt) {
1526 if (evt.getSource() instanceof Layer) {
1527 Layer layer = (Layer)evt.getSource();
1528 final int idx = getLayers().indexOf(layer);
1529 if (idx < 0) return;
1530 fireRefresh();
1531 }
1532 }
1533 }
1534
1535 static class LayerList extends JTable {
1536 public LayerList(TableModel dataModel) {
1537 super(dataModel);
1538 }
1539
1540 public void scrollToVisible(int row, int col) {
1541 if (!(getParent() instanceof JViewport))
1542 return;
1543 JViewport viewport = (JViewport) getParent();
1544 Rectangle rect = getCellRect(row, col, true);
1545 Point pt = viewport.getViewPosition();
1546 rect.setLocation(rect.x - pt.x, rect.y - pt.y);
1547 viewport.scrollRectToVisible(rect);
1548 }
1549 }
1550
1551 /**
1552 * Creates a {@link ShowHideLayerAction} for <code>layer</code> in the
1553 * context of this {@link LayerListDialog}.
1554 *
1555 * @return the action
1556 */
1557 public ShowHideLayerAction createShowHideLayerAction() {
1558 ShowHideLayerAction act = new ShowHideLayerAction(true);
1559 act.putValue(Action.NAME, tr("Show/Hide"));
1560 return act;
1561 }
1562
1563 /**
1564 * Creates a {@link DeleteLayerAction} for <code>layer</code> in the
1565 * context of this {@link LayerListDialog}.
1566 *
1567 * @return the action
1568 */
1569 public DeleteLayerAction createDeleteLayerAction() {
1570 // the delete layer action doesn't depend on the current layer
1571 return new DeleteLayerAction();
1572 }
1573
1574 /**
1575 * Creates a {@link ActivateLayerAction} for <code>layer</code> in the
1576 * context of this {@link LayerListDialog}.
1577 *
1578 * @param layer the layer
1579 * @return the action
1580 */
1581 public ActivateLayerAction createActivateLayerAction(Layer layer) {
1582 return new ActivateLayerAction(layer);
1583 }
1584
1585 /**
1586 * Creates a {@link MergeLayerAction} for <code>layer</code> in the
1587 * context of this {@link LayerListDialog}.
1588 *
1589 * @param layer the layer
1590 * @return the action
1591 */
1592 public MergeAction createMergeLayerAction(Layer layer) {
1593 return new MergeAction(layer);
1594 }
1595
1596 public static Layer getLayerForIndex(int index) {
1597
1598 if (!Main.isDisplayingMapView())
1599 return null;
1600
1601 List<Layer> layers = Main.map.mapView.getAllLayersAsList();
1602
1603 if (index < layers.size() && index >= 0)
1604 return layers.get(index);
1605 else
1606 return null;
1607 }
1608
1609 // This is not Class<? extends Layer> on purpose, to allow asking for layers implementing some interface
1610 public static List<MultikeyInfo> getLayerInfoByClass(Class<?> layerClass) {
1611
1612 List<MultikeyInfo> result = new ArrayList<MultikeyShortcutAction.MultikeyInfo>();
1613
1614 if (!Main.isDisplayingMapView())
1615 return result;
1616
1617 List<Layer> layers = Main.map.mapView.getAllLayersAsList();
1618
1619 int index = 0;
1620 for (Layer l: layers) {
1621 if (layerClass.isAssignableFrom(l.getClass())) {
1622 result.add(new MultikeyInfo(index, l.getName()));
1623 }
1624 index++;
1625 }
1626
1627 return result;
1628 }
1629
1630 public static boolean isLayerValid(Layer l) {
1631 if (l == null)
1632 return false;
1633
1634 if (!Main.isDisplayingMapView())
1635 return false;
1636
1637 return Main.map.mapView.getAllLayersAsList().indexOf(l) >= 0;
1638 }
1639
1640 public static MultikeyInfo getLayerInfo(Layer l) {
1641
1642 if (l == null)
1643 return null;
1644
1645 if (!Main.isDisplayingMapView())
1646 return null;
1647
1648 int index = Main.map.mapView.getAllLayersAsList().indexOf(l);
1649 if (index < 0)
1650 return null;
1651
1652 return new MultikeyInfo(index, l.getName());
1653 }
1654}
Note: See TracBrowser for help on using the repository browser.