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

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

fix Checkstyle issues

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