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

Last change on this file since 8291 was 8291, checked in by Don-vip, 9 years ago

fix squid:RedundantThrowsDeclarationCheck + consistent Javadoc for exceptions

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