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

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

see #4899, see #7266, see #7333: Resolved NPE in conflict manager when copying a member created by merging two layers

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