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

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

sonar - Unused private method should be removed
sonar - Unused protected methods should be removed
sonar - Sections of code should not be "commented out"
sonar - Empty statements should be removed
sonar - squid:S1172 - Unused method parameters should be removed
sonar - squid:S1481 - Unused local variables should be removed

  • Property svn:eol-style set to native
File size: 63.7 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 @Override
963 public void updateEnabledState() {
964 if (layer == null) {
965 if (getModel().getSelectedLayers().size() == 1) {
966 setEnabled(getModel().getSelectedLayers().get(0) instanceof OsmDataLayer);
967 } else {
968 setEnabled(false);
969 }
970 } else {
971 setEnabled(layer instanceof OsmDataLayer);
972 }
973 }
974 }
975
976 private static class ActiveLayerCheckBox extends JCheckBox {
977 ActiveLayerCheckBox() {
978 setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
979 ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank");
980 ImageIcon active = ImageProvider.get("dialogs/layerlist", "active");
981 setIcon(blank);
982 setSelectedIcon(active);
983 setRolloverIcon(blank);
984 setRolloverSelectedIcon(active);
985 setPressedIcon(ImageProvider.get("dialogs/layerlist", "active-pressed"));
986 }
987 }
988
989 private static class LayerVisibleCheckBox extends JCheckBox {
990 private final ImageIcon iconEye;
991 private final ImageIcon iconEyeTranslucent;
992 private boolean isTranslucent;
993
994 /**
995 * Constructs a new {@code LayerVisibleCheckBox}.
996 */
997 LayerVisibleCheckBox() {
998 setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
999 iconEye = ImageProvider.get("dialogs/layerlist", "eye");
1000 iconEyeTranslucent = ImageProvider.get("dialogs/layerlist", "eye-translucent");
1001 setIcon(ImageProvider.get("dialogs/layerlist", "eye-off"));
1002 setPressedIcon(ImageProvider.get("dialogs/layerlist", "eye-pressed"));
1003 setSelectedIcon(iconEye);
1004 isTranslucent = false;
1005 }
1006
1007 public void setTranslucent(boolean isTranslucent) {
1008 if (this.isTranslucent == isTranslucent) return;
1009 if (isTranslucent) {
1010 setSelectedIcon(iconEyeTranslucent);
1011 } else {
1012 setSelectedIcon(iconEye);
1013 }
1014 this.isTranslucent = isTranslucent;
1015 }
1016
1017 public void updateStatus(Layer layer) {
1018 boolean visible = layer.isVisible();
1019 setSelected(visible);
1020 setTranslucent(layer.getOpacity() < 1.0);
1021 setToolTipText(visible ?
1022 tr("layer is currently visible (click to hide layer)") :
1023 tr("layer is currently hidden (click to show layer)"));
1024 }
1025 }
1026
1027 private static class ActiveLayerCellRenderer implements TableCellRenderer {
1028 private final JCheckBox cb;
1029
1030 /**
1031 * Constructs a new {@code ActiveLayerCellRenderer}.
1032 */
1033 ActiveLayerCellRenderer() {
1034 cb = new ActiveLayerCheckBox();
1035 }
1036
1037 @Override
1038 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1039 boolean active = value != null && (Boolean) value;
1040 cb.setSelected(active);
1041 cb.setToolTipText(active ? tr("this layer is the active layer") : tr("this layer is not currently active (click to activate)"));
1042 return cb;
1043 }
1044 }
1045
1046 private static class LayerVisibleCellRenderer implements TableCellRenderer {
1047 private final LayerVisibleCheckBox cb;
1048
1049 /**
1050 * Constructs a new {@code LayerVisibleCellRenderer}.
1051 */
1052 LayerVisibleCellRenderer() {
1053 this.cb = new LayerVisibleCheckBox();
1054 }
1055
1056 @Override
1057 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1058 if (value != null) {
1059 cb.updateStatus((Layer) value);
1060 }
1061 return cb;
1062 }
1063 }
1064
1065 private static class LayerVisibleCellEditor extends DefaultCellEditor {
1066 private final LayerVisibleCheckBox cb;
1067
1068 LayerVisibleCellEditor(LayerVisibleCheckBox cb) {
1069 super(cb);
1070 this.cb = cb;
1071 }
1072
1073 @Override
1074 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
1075 cb.updateStatus((Layer) value);
1076 return cb;
1077 }
1078 }
1079
1080 private class LayerNameCellRenderer extends DefaultTableCellRenderer {
1081
1082 protected boolean isActiveLayer(Layer layer) {
1083 if (!Main.isDisplayingMapView()) return false;
1084 return Main.map.mapView.getActiveLayer() == layer;
1085 }
1086
1087 @Override
1088 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1089 if (value == null)
1090 return this;
1091 Layer layer = (Layer) value;
1092 JLabel label = (JLabel) super.getTableCellRendererComponent(table,
1093 layer.getName(), isSelected, hasFocus, row, column);
1094 if (isActiveLayer(layer)) {
1095 label.setFont(label.getFont().deriveFont(Font.BOLD));
1096 }
1097 if (Main.pref.getBoolean("dialog.layer.colorname", true)) {
1098 Color c = layer.getColor(false);
1099 if (c != null) {
1100 Color oc = null;
1101 for (Layer l : model.getLayers()) {
1102 oc = l.getColor(false);
1103 if (oc != null) {
1104 if (oc.equals(c)) {
1105 oc = null;
1106 } else {
1107 break;
1108 }
1109 }
1110 }
1111 /* not more than one color, don't use coloring */
1112 if (oc == null) {
1113 c = null;
1114 }
1115 }
1116 if (c == null) {
1117 c = Main.pref.getUIColor(isSelected ? "Table.selectionForeground" : "Table.foreground");
1118 }
1119 label.setForeground(c);
1120 }
1121 label.setIcon(layer.getIcon());
1122 label.setToolTipText(layer.getToolTipText());
1123 return label;
1124 }
1125 }
1126
1127 private static class LayerNameCellEditor extends DefaultCellEditor {
1128 LayerNameCellEditor(DisableShortcutsOnFocusGainedTextField tf) {
1129 super(tf);
1130 }
1131
1132 @Override
1133 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
1134 JosmTextField tf = (JosmTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
1135 tf.setText(value == null ? "" : ((Layer) value).getName());
1136 return tf;
1137 }
1138 }
1139
1140 class PopupMenuHandler extends PopupMenuLauncher {
1141 @Override
1142 public void showMenu(MouseEvent evt) {
1143 Layer layer = getModel().getLayer(layerList.getSelectedRow());
1144 menu = new LayerListPopup(getModel().getSelectedLayers());
1145 super.showMenu(evt);
1146 }
1147 }
1148
1149 /**
1150 * The action to move up the currently selected entries in the list.
1151 */
1152 class MoveUpAction extends AbstractAction implements IEnabledStateUpdating{
1153 MoveUpAction() {
1154 putValue(NAME, tr("Move up"));
1155 putValue(SMALL_ICON, ImageProvider.get("dialogs", "up"));
1156 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row up."));
1157 updateEnabledState();
1158 }
1159
1160 @Override
1161 public void updateEnabledState() {
1162 setEnabled(model.canMoveUp());
1163 }
1164
1165 @Override
1166 public void actionPerformed(ActionEvent e) {
1167 model.moveUp();
1168 }
1169 }
1170
1171 /**
1172 * The action to move down the currently selected entries in the list.
1173 */
1174 class MoveDownAction extends AbstractAction implements IEnabledStateUpdating {
1175 MoveDownAction() {
1176 putValue(NAME, tr("Move down"));
1177 putValue(SMALL_ICON, ImageProvider.get("dialogs", "down"));
1178 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row down."));
1179 updateEnabledState();
1180 }
1181
1182 @Override
1183 public void updateEnabledState() {
1184 setEnabled(model.canMoveDown());
1185 }
1186
1187 @Override
1188 public void actionPerformed(ActionEvent e) {
1189 model.moveDown();
1190 }
1191 }
1192
1193 /**
1194 * Observer interface to be implemented by views using {@link LayerListModel}.
1195 */
1196 public interface LayerListModelListener {
1197
1198 /**
1199 * Fired when a layer is made visible.
1200 * @param index the layer index
1201 * @param layer the layer
1202 */
1203 void makeVisible(int index, Layer layer);
1204
1205
1206 /**
1207 * Fired when something has changed in the layer list model.
1208 */
1209 void refresh();
1210 }
1211
1212 /**
1213 * The layer list model. The model manages a list of layers and provides methods for
1214 * moving layers up and down, for toggling their visibility, and for activating a layer.
1215 *
1216 * The model is a {@link TableModel} and it provides a {@link ListSelectionModel}. It expects
1217 * to be configured with a {@link DefaultListSelectionModel}. The selection model is used
1218 * to update the selection state of views depending on messages sent to the model.
1219 *
1220 * The model manages a list of {@link LayerListModelListener} which are mainly notified if
1221 * the model requires views to make a specific list entry visible.
1222 *
1223 * It also listens to {@link PropertyChangeEvent}s of every {@link Layer} it manages, in particular to
1224 * the properties {@link Layer#VISIBLE_PROP} and {@link Layer#NAME_PROP}.
1225 */
1226 public final class LayerListModel extends AbstractTableModel implements MapView.LayerChangeListener, PropertyChangeListener {
1227 /** manages list selection state*/
1228 private DefaultListSelectionModel selectionModel;
1229 private CopyOnWriteArrayList<LayerListModelListener> listeners;
1230
1231 /**
1232 * constructor
1233 *
1234 * @param selectionModel the list selection model
1235 */
1236 private LayerListModel(DefaultListSelectionModel selectionModel) {
1237 this.selectionModel = selectionModel;
1238 listeners = new CopyOnWriteArrayList<>();
1239 }
1240
1241 /**
1242 * Adds a listener to this model
1243 *
1244 * @param listener the listener
1245 */
1246 public void addLayerListModelListener(LayerListModelListener listener) {
1247 if (listener != null) {
1248 listeners.addIfAbsent(listener);
1249 }
1250 }
1251
1252 /**
1253 * removes a listener from this model
1254 * @param listener the listener
1255 *
1256 */
1257 public void removeLayerListModelListener(LayerListModelListener listener) {
1258 listeners.remove(listener);
1259 }
1260
1261 /**
1262 * Fires a make visible event to listeners
1263 *
1264 * @param index the index of the row to make visible
1265 * @param layer the layer at this index
1266 * @see LayerListModelListener#makeVisible(int, Layer)
1267 */
1268 protected void fireMakeVisible(int index, Layer layer) {
1269 for (LayerListModelListener listener : listeners) {
1270 listener.makeVisible(index, layer);
1271 }
1272 }
1273
1274 /**
1275 * Fires a refresh event to listeners of this model
1276 *
1277 * @see LayerListModelListener#refresh()
1278 */
1279 protected void fireRefresh() {
1280 for (LayerListModelListener listener : listeners) {
1281 listener.refresh();
1282 }
1283 }
1284
1285 /**
1286 * Populates the model with the current layers managed by {@link MapView}.
1287 */
1288 public void populate() {
1289 for (Layer layer: getLayers()) {
1290 // make sure the model is registered exactly once
1291 layer.removePropertyChangeListener(this);
1292 layer.addPropertyChangeListener(this);
1293 }
1294 fireTableDataChanged();
1295 }
1296
1297 /**
1298 * Marks <code>layer</code> as selected layer. Ignored, if layer is null.
1299 *
1300 * @param layer the layer.
1301 */
1302 public void setSelectedLayer(Layer layer) {
1303 if (layer == null)
1304 return;
1305 int idx = getLayers().indexOf(layer);
1306 if (idx >= 0) {
1307 selectionModel.setSelectionInterval(idx, idx);
1308 }
1309 ensureSelectedIsVisible();
1310 }
1311
1312 /**
1313 * Replies the list of currently selected layers. Never null, but may be empty.
1314 *
1315 * @return the list of currently selected layers. Never null, but may be empty.
1316 */
1317 public List<Layer> getSelectedLayers() {
1318 List<Layer> selected = new ArrayList<>();
1319 List<Layer> layers = getLayers();
1320 for (int i = 0; i < layers.size(); i++) {
1321 if (selectionModel.isSelectedIndex(i)) {
1322 selected.add(layers.get(i));
1323 }
1324 }
1325 return selected;
1326 }
1327
1328 /**
1329 * Replies a the list of indices of the selected rows. Never null, but may be empty.
1330 *
1331 * @return the list of indices of the selected rows. Never null, but may be empty.
1332 */
1333 public List<Integer> getSelectedRows() {
1334 List<Integer> selected = new ArrayList<>();
1335 for (int i = 0; i < getLayers().size(); i++) {
1336 if (selectionModel.isSelectedIndex(i)) {
1337 selected.add(i);
1338 }
1339 }
1340 return selected;
1341 }
1342
1343 /**
1344 * Invoked if a layer managed by {@link MapView} is removed
1345 *
1346 * @param layer the layer which is removed
1347 */
1348 protected void onRemoveLayer(Layer layer) {
1349 if (layer == null)
1350 return;
1351 layer.removePropertyChangeListener(this);
1352 final int size = getRowCount();
1353 final List<Integer> rows = getSelectedRows();
1354 GuiHelper.runInEDTAndWait(new Runnable() {
1355 @Override
1356 public void run() {
1357 if (rows.isEmpty() && size > 0) {
1358 selectionModel.setSelectionInterval(size-1, size-1);
1359 }
1360 fireTableDataChanged();
1361 fireRefresh();
1362 ensureActiveSelected();
1363 }
1364 });
1365 }
1366
1367 /**
1368 * Invoked when a layer managed by {@link MapView} is added
1369 *
1370 * @param layer the layer
1371 */
1372 protected void onAddLayer(Layer layer) {
1373 if (layer == null) return;
1374 layer.addPropertyChangeListener(this);
1375 fireTableDataChanged();
1376 int idx = getLayers().indexOf(layer);
1377 layerList.setRowHeight(idx, Math.max(16, layer.getIcon().getIconHeight()));
1378 selectionModel.setSelectionInterval(idx, idx);
1379 ensureSelectedIsVisible();
1380 }
1381
1382 /**
1383 * Replies the first layer. Null if no layers are present
1384 *
1385 * @return the first layer. Null if no layers are present
1386 */
1387 public Layer getFirstLayer() {
1388 if (getRowCount() == 0) return null;
1389 return getLayers().get(0);
1390 }
1391
1392 /**
1393 * Replies the layer at position <code>index</code>
1394 *
1395 * @param index the index
1396 * @return the layer at position <code>index</code>. Null,
1397 * if index is out of range.
1398 */
1399 public Layer getLayer(int index) {
1400 if (index < 0 || index >= getRowCount())
1401 return null;
1402 return getLayers().get(index);
1403 }
1404
1405 /**
1406 * Replies true if the currently selected layers can move up by one position
1407 *
1408 * @return true if the currently selected layers can move up by one position
1409 */
1410 public boolean canMoveUp() {
1411 List<Integer> sel = getSelectedRows();
1412 return !sel.isEmpty() && sel.get(0) > 0;
1413 }
1414
1415 /**
1416 * Move up the currently selected layers by one position
1417 *
1418 */
1419 public void moveUp() {
1420 if (!canMoveUp()) return;
1421 List<Integer> sel = getSelectedRows();
1422 List<Layer> layers = getLayers();
1423 for (int row : sel) {
1424 Layer l1 = layers.get(row);
1425 Layer l2 = layers.get(row-1);
1426 Main.map.mapView.moveLayer(l2, row);
1427 Main.map.mapView.moveLayer(l1, row-1);
1428 }
1429 fireTableDataChanged();
1430 selectionModel.clearSelection();
1431 for (int row : sel) {
1432 selectionModel.addSelectionInterval(row-1, row-1);
1433 }
1434 ensureSelectedIsVisible();
1435 }
1436
1437 /**
1438 * Replies true if the currently selected layers can move down by one position
1439 *
1440 * @return true if the currently selected layers can move down by one position
1441 */
1442 public boolean canMoveDown() {
1443 List<Integer> sel = getSelectedRows();
1444 return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1;
1445 }
1446
1447 /**
1448 * Move down the currently selected layers by one position
1449 *
1450 */
1451 public void moveDown() {
1452 if (!canMoveDown()) return;
1453 List<Integer> sel = getSelectedRows();
1454 Collections.reverse(sel);
1455 List<Layer> layers = getLayers();
1456 for (int row : sel) {
1457 Layer l1 = layers.get(row);
1458 Layer l2 = layers.get(row+1);
1459 Main.map.mapView.moveLayer(l1, row+1);
1460 Main.map.mapView.moveLayer(l2, row);
1461 }
1462 fireTableDataChanged();
1463 selectionModel.clearSelection();
1464 for (int row : sel) {
1465 selectionModel.addSelectionInterval(row+1, row+1);
1466 }
1467 ensureSelectedIsVisible();
1468 }
1469
1470 /**
1471 * Make sure the first of the selected layers is visible in the
1472 * views of this model.
1473 *
1474 */
1475 protected void ensureSelectedIsVisible() {
1476 int index = selectionModel.getMinSelectionIndex();
1477 if (index < 0) return;
1478 List<Layer> layers = getLayers();
1479 if (index >= layers.size()) return;
1480 Layer layer = layers.get(index);
1481 fireMakeVisible(index, layer);
1482 }
1483
1484 /**
1485 * Replies a list of layers which are possible merge targets
1486 * for <code>source</code>
1487 *
1488 * @param source the source layer
1489 * @return a list of layers which are possible merge targets
1490 * for <code>source</code>. Never null, but can be empty.
1491 */
1492 public List<Layer> getPossibleMergeTargets(Layer source) {
1493 List<Layer> targets = new ArrayList<>();
1494 if (source == null || !Main.isDisplayingMapView()) {
1495 return targets;
1496 }
1497 for (Layer target : Main.map.mapView.getAllLayersAsList()) {
1498 if (source == target) {
1499 continue;
1500 }
1501 if (target.isMergable(source) && source.isMergable(target)) {
1502 targets.add(target);
1503 }
1504 }
1505 return targets;
1506 }
1507
1508 /**
1509 * Replies the list of layers currently managed by {@link MapView}.
1510 * Never null, but can be empty.
1511 *
1512 * @return the list of layers currently managed by {@link MapView}.
1513 * Never null, but can be empty.
1514 */
1515 public List<Layer> getLayers() {
1516 if (!Main.isDisplayingMapView())
1517 return Collections.<Layer>emptyList();
1518 return Main.map.mapView.getAllLayersAsList();
1519 }
1520
1521 /**
1522 * Ensures that at least one layer is selected in the layer dialog
1523 *
1524 */
1525 protected void ensureActiveSelected() {
1526 List<Layer> layers = getLayers();
1527 if (layers.isEmpty())
1528 return;
1529 final Layer activeLayer = getActiveLayer();
1530 if (activeLayer != null) {
1531 // there's an active layer - select it and make it visible
1532 int idx = layers.indexOf(activeLayer);
1533 selectionModel.setSelectionInterval(idx, idx);
1534 ensureSelectedIsVisible();
1535 } else {
1536 // no active layer - select the first one and make it visible
1537 selectionModel.setSelectionInterval(0, 0);
1538 ensureSelectedIsVisible();
1539 }
1540 }
1541
1542 /**
1543 * Replies the active layer. null, if no active layer is available
1544 *
1545 * @return the active layer. null, if no active layer is available
1546 */
1547 protected Layer getActiveLayer() {
1548 if (!Main.isDisplayingMapView()) return null;
1549 return Main.map.mapView.getActiveLayer();
1550 }
1551
1552 /* ------------------------------------------------------------------------------ */
1553 /* Interface TableModel */
1554 /* ------------------------------------------------------------------------------ */
1555
1556 @Override
1557 public int getRowCount() {
1558 List<Layer> layers = getLayers();
1559 if (layers == null) return 0;
1560 return layers.size();
1561 }
1562
1563 @Override
1564 public int getColumnCount() {
1565 return 3;
1566 }
1567
1568 @Override
1569 public Object getValueAt(int row, int col) {
1570 List<Layer> layers = getLayers();
1571 if (row >= 0 && row < layers.size()) {
1572 switch (col) {
1573 case 0: return layers.get(row) == getActiveLayer();
1574 case 1: return layers.get(row);
1575 case 2: return layers.get(row);
1576 default: throw new RuntimeException();
1577 }
1578 }
1579 return null;
1580 }
1581
1582 @Override
1583 public boolean isCellEditable(int row, int col) {
1584 if (col == 0 && getActiveLayer() == getLayers().get(row))
1585 return false;
1586 return true;
1587 }
1588
1589 @Override
1590 public void setValueAt(Object value, int row, int col) {
1591 List<Layer> layers = getLayers();
1592 if (row < layers.size()) {
1593 Layer l = layers.get(row);
1594 switch (col) {
1595 case 0:
1596 Main.map.mapView.setActiveLayer(l);
1597 l.setVisible(true);
1598 break;
1599 case 1:
1600 l.setVisible((Boolean) value);
1601 break;
1602 case 2:
1603 l.setName((String) value);
1604 break;
1605 default: throw new RuntimeException();
1606 }
1607 fireTableCellUpdated(row, col);
1608 }
1609 }
1610
1611 /* ------------------------------------------------------------------------------ */
1612 /* Interface LayerChangeListener */
1613 /* ------------------------------------------------------------------------------ */
1614 @Override
1615 public void activeLayerChange(final Layer oldLayer, final Layer newLayer) {
1616 GuiHelper.runInEDTAndWait(new Runnable() {
1617 @Override
1618 public void run() {
1619 if (oldLayer != null) {
1620 int idx = getLayers().indexOf(oldLayer);
1621 if (idx >= 0) {
1622 fireTableRowsUpdated(idx, idx);
1623 }
1624 }
1625
1626 if (newLayer != null) {
1627 int idx = getLayers().indexOf(newLayer);
1628 if (idx >= 0) {
1629 fireTableRowsUpdated(idx, idx);
1630 }
1631 }
1632 ensureActiveSelected();
1633 }
1634 });
1635 }
1636
1637 @Override
1638 public void layerAdded(Layer newLayer) {
1639 onAddLayer(newLayer);
1640 }
1641
1642 @Override
1643 public void layerRemoved(final Layer oldLayer) {
1644 onRemoveLayer(oldLayer);
1645 }
1646
1647 /* ------------------------------------------------------------------------------ */
1648 /* Interface PropertyChangeListener */
1649 /* ------------------------------------------------------------------------------ */
1650 @Override
1651 public void propertyChange(PropertyChangeEvent evt) {
1652 if (evt.getSource() instanceof Layer) {
1653 Layer layer = (Layer) evt.getSource();
1654 final int idx = getLayers().indexOf(layer);
1655 if (idx < 0) return;
1656 fireRefresh();
1657 }
1658 }
1659 }
1660
1661 static class LayerList extends JTable {
1662 LayerList(TableModel dataModel) {
1663 super(dataModel);
1664 }
1665
1666 public void scrollToVisible(int row, int col) {
1667 if (!(getParent() instanceof JViewport))
1668 return;
1669 JViewport viewport = (JViewport) getParent();
1670 Rectangle rect = getCellRect(row, col, true);
1671 Point pt = viewport.getViewPosition();
1672 rect.setLocation(rect.x - pt.x, rect.y - pt.y);
1673 viewport.scrollRectToVisible(rect);
1674 }
1675 }
1676
1677 /**
1678 * Creates a {@link ShowHideLayerAction} in the
1679 * context of this {@link LayerListDialog}.
1680 *
1681 * @return the action
1682 */
1683 public ShowHideLayerAction createShowHideLayerAction() {
1684 return new ShowHideLayerAction();
1685 }
1686
1687 /**
1688 * Creates a {@link DeleteLayerAction} in the
1689 * context of this {@link LayerListDialog}.
1690 *
1691 * @return the action
1692 */
1693 public DeleteLayerAction createDeleteLayerAction() {
1694 return new DeleteLayerAction();
1695 }
1696
1697 /**
1698 * Creates a {@link ActivateLayerAction} for <code>layer</code> in the
1699 * context of this {@link LayerListDialog}.
1700 *
1701 * @param layer the layer
1702 * @return the action
1703 */
1704 public ActivateLayerAction createActivateLayerAction(Layer layer) {
1705 return new ActivateLayerAction(layer);
1706 }
1707
1708 /**
1709 * Creates a {@link MergeLayerAction} for <code>layer</code> in the
1710 * context of this {@link LayerListDialog}.
1711 *
1712 * @param layer the layer
1713 * @return the action
1714 */
1715 public MergeAction createMergeLayerAction(Layer layer) {
1716 return new MergeAction(layer);
1717 }
1718
1719 /**
1720 * Creates a {@link DuplicateAction} for <code>layer</code> in the
1721 * context of this {@link LayerListDialog}.
1722 *
1723 * @param layer the layer
1724 * @return the action
1725 */
1726 public DuplicateAction createDuplicateLayerAction(Layer layer) {
1727 return new DuplicateAction(layer);
1728 }
1729
1730 /**
1731 * Returns the layer at given index, or {@code null}.
1732 * @param index the index
1733 * @return the layer at given index, or {@code null} if index out of range
1734 */
1735 public static Layer getLayerForIndex(int index) {
1736
1737 if (!Main.isDisplayingMapView())
1738 return null;
1739
1740 List<Layer> layers = Main.map.mapView.getAllLayersAsList();
1741
1742 if (index < layers.size() && index >= 0)
1743 return layers.get(index);
1744 else
1745 return null;
1746 }
1747
1748 /**
1749 * Returns a list of info on all layers of a given class.
1750 * @param layerClass The layer class. This is not {@code Class<? extends Layer>} on purpose,
1751 * to allow asking for layers implementing some interface
1752 * @return list of info on all layers assignable from {@code layerClass}
1753 */
1754 public static List<MultikeyInfo> getLayerInfoByClass(Class<?> layerClass) {
1755
1756 List<MultikeyInfo> result = new ArrayList<>();
1757
1758 if (!Main.isDisplayingMapView())
1759 return result;
1760
1761 List<Layer> layers = Main.map.mapView.getAllLayersAsList();
1762
1763 int index = 0;
1764 for (Layer l: layers) {
1765 if (layerClass.isAssignableFrom(l.getClass())) {
1766 result.add(new MultikeyInfo(index, l.getName()));
1767 }
1768 index++;
1769 }
1770
1771 return result;
1772 }
1773
1774 /**
1775 * Determines if a layer is valid (contained in layer list).
1776 * @param l the layer
1777 * @return {@code true} if layer {@code l} is contained in current layer list
1778 */
1779 public static boolean isLayerValid(Layer l) {
1780
1781 if (l == null || !Main.isDisplayingMapView())
1782 return false;
1783
1784 return Main.map.mapView.getAllLayersAsList().contains(l);
1785 }
1786
1787 /**
1788 * Returns info about layer.
1789 * @param l the layer
1790 * @return info about layer {@code l}
1791 */
1792 public static MultikeyInfo getLayerInfo(Layer l) {
1793
1794 if (l == null || !Main.isDisplayingMapView())
1795 return null;
1796
1797 int index = Main.map.mapView.getAllLayersAsList().indexOf(l);
1798 if (index < 0)
1799 return null;
1800
1801 return new MultikeyInfo(index, l.getName());
1802 }
1803}
Note: See TracBrowser for help on using the repository browser.