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

Last change on this file since 3664 was 3664, checked in by bastiK, 13 years ago

gui fine tuning

  • Property svn:eol-style set to native
File size: 44.5 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.BorderLayout;
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.KeyEvent;
14import java.awt.event.MouseEvent;
15import java.beans.PropertyChangeEvent;
16import java.beans.PropertyChangeListener;
17import java.util.ArrayList;
18import java.util.Collections;
19import java.util.List;
20import java.util.concurrent.CopyOnWriteArrayList;
21
22import javax.swing.AbstractAction;
23import javax.swing.DefaultCellEditor;
24import javax.swing.DefaultListSelectionModel;
25import javax.swing.ImageIcon;
26import javax.swing.JCheckBox;
27import javax.swing.JComponent;
28import javax.swing.JLabel;
29import javax.swing.JMenuItem;
30import javax.swing.JPanel;
31import javax.swing.JScrollPane;
32import javax.swing.JTable;
33import javax.swing.JTextField;
34import javax.swing.JViewport;
35import javax.swing.KeyStroke;
36import javax.swing.ListSelectionModel;
37import javax.swing.UIManager;
38import javax.swing.event.ListSelectionEvent;
39import javax.swing.event.ListSelectionListener;
40import javax.swing.event.TableModelEvent;
41import javax.swing.event.TableModelListener;
42import javax.swing.table.AbstractTableModel;
43import javax.swing.table.DefaultTableCellRenderer;
44import javax.swing.table.TableCellRenderer;
45import javax.swing.table.TableModel;
46
47import org.openstreetmap.josm.Main;
48import org.openstreetmap.josm.actions.DuplicateLayerAction;
49import org.openstreetmap.josm.actions.MergeLayerAction;
50import org.openstreetmap.josm.gui.MapFrame;
51import org.openstreetmap.josm.gui.MapView;
52import org.openstreetmap.josm.gui.SideButton;
53import org.openstreetmap.josm.gui.help.HelpUtil;
54import org.openstreetmap.josm.gui.io.SaveLayersDialog;
55import org.openstreetmap.josm.gui.layer.Layer;
56import org.openstreetmap.josm.gui.layer.OsmDataLayer;
57import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
58import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
59import org.openstreetmap.josm.tools.CheckParameterUtil;
60import org.openstreetmap.josm.tools.ImageProvider;
61import org.openstreetmap.josm.tools.Shortcut;
62
63/**
64 * This is a toggle dialog which displays the list of layers. Actions allow to
65 * change the ordering of the layers, to hide/show layers, to activate layers,
66 * and to delete layers.
67 *
68 */
69public class LayerListDialog extends ToggleDialog {
70 //static private final Logger logger = Logger.getLogger(LayerListDialog.class.getName());
71
72 /** the unique instance of the dialog */
73 static private LayerListDialog instance;
74
75 /**
76 * Creates the instance of the dialog. It's connected to the map frame <code>mapFrame</code>
77 *
78 * @param mapFrame the map frame
79 */
80 static public void createInstance(MapFrame mapFrame) {
81 if (instance != null)
82 throw new IllegalStateException("Dialog was already created");
83 instance = new LayerListDialog(mapFrame);
84 }
85
86 /**
87 * Replies the instance of the dialog
88 *
89 * @return the instance of the dialog
90 * @throws IllegalStateException thrown, if the dialog is not created yet
91 * @see #createInstance(MapFrame)
92 */
93 static public LayerListDialog getInstance() throws IllegalStateException {
94 if (instance == null)
95 throw new IllegalStateException("Dialog not created yet. Invoke createInstance() first");
96 return instance;
97 }
98
99 /** the model for the layer list */
100 private LayerListModel model;
101
102 /** the selection model */
103 private DefaultListSelectionModel selectionModel;
104
105 /** the list of layers (technically its a JTable, but appears like a list) */
106 private LayerList layerList;
107
108 ActivateLayerAction activateLayerAction;
109
110 protected JPanel createButtonPanel() {
111 JPanel buttonPanel = getButtonPanel(5);
112
113 // -- move up action
114 MoveUpAction moveUpAction = new MoveUpAction();
115 adaptTo(moveUpAction, model);
116 adaptTo(moveUpAction,selectionModel);
117 buttonPanel.add(new SideButton(moveUpAction));
118
119 // -- move down action
120 MoveDownAction moveDownAction = new MoveDownAction();
121 adaptTo(moveDownAction, model);
122 adaptTo(moveDownAction,selectionModel);
123 buttonPanel.add(new SideButton(moveDownAction));
124
125 // -- activate action
126 activateLayerAction = new ActivateLayerAction();
127 adaptTo(activateLayerAction, selectionModel);
128 buttonPanel.add(new SideButton(activateLayerAction));
129
130 // -- show hide action
131 ShowHideLayerAction showHideLayerAction = new ShowHideLayerAction();
132 adaptTo(showHideLayerAction, selectionModel);
133 buttonPanel.add(new SideButton(showHideLayerAction));
134
135 // -- merge layer action
136 MergeAction mergeLayerAction = new MergeAction();
137 adaptTo(mergeLayerAction, model);
138 adaptTo(mergeLayerAction,selectionModel);
139 buttonPanel.add(new SideButton(mergeLayerAction));
140
141 // -- duplicate layer action
142 DuplicateAction duplicateLayerAction = new DuplicateAction();
143 adaptTo(duplicateLayerAction, model);
144 adaptTo(duplicateLayerAction, selectionModel);
145 buttonPanel.add(new SideButton(duplicateLayerAction));
146
147 //-- delete layer action
148 DeleteLayerAction deleteLayerAction = new DeleteLayerAction();
149 layerList.getInputMap(JComponent.WHEN_FOCUSED).put(
150 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"deleteLayer"
151 );
152 layerList.getActionMap().put("deleteLayer", deleteLayerAction);
153 adaptTo(deleteLayerAction, selectionModel);
154 buttonPanel.add(new SideButton(deleteLayerAction, false));
155
156 return buttonPanel;
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, Shortcut.GROUP_LAYER), 100, true);
165
166 // create the models
167 //
168 selectionModel = new DefaultListSelectionModel();
169 selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
170 model = new LayerListModel(selectionModel);
171
172 // create the list control
173 //
174 layerList = new LayerList(model);
175 layerList.setSelectionModel(selectionModel);
176 layerList.addMouseListener(new PopupMenuHandler());
177 layerList.setBackground(UIManager.getColor("Button.background"));
178 layerList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
179 layerList.setTableHeader(null);
180 layerList.setShowGrid(false);
181 layerList.setIntercellSpacing(new Dimension(0, 0));
182 layerList.getColumnModel().getColumn(0).setCellRenderer(new ActiveLayerCellRenderer());
183 layerList.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(new ActiveLayerCheckBox()));
184 layerList.getColumnModel().getColumn(0).setMaxWidth(12);
185 layerList.getColumnModel().getColumn(0).setPreferredWidth(12);
186 layerList.getColumnModel().getColumn(0).setResizable(false);
187 layerList.getColumnModel().getColumn(1).setCellRenderer(new LayerVisibleCellRenderer());
188 layerList.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(new LayerVisibleCheckBox()));
189 layerList.getColumnModel().getColumn(1).setMaxWidth(16);
190 layerList.getColumnModel().getColumn(1).setPreferredWidth(16);
191 layerList.getColumnModel().getColumn(1).setResizable(false);
192 layerList.getColumnModel().getColumn(2).setCellRenderer(new LayerNameCellRenderer());
193 layerList.getColumnModel().getColumn(2).setCellEditor(new LayerNameCellEditor(new JTextField()));
194
195 add(new JScrollPane(layerList), BorderLayout.CENTER);
196
197 // init the model
198 //
199 final MapView mapView = mapFrame.mapView;
200 model.populate();
201 model.setSelectedLayer(mapView.getActiveLayer());
202 model.addLayerListModelListener(
203 new LayerListModelListener() {
204 @Override
205 public void makeVisible(int row, Layer layer) {
206 System.err.println(Thread.currentThread());
207 layerList.scrollToVisible(row, 0);
208 layerList.repaint();
209 }
210 @Override
211 public void refresh() {
212 layerList.repaint();
213 }
214 }
215 );
216
217 add(createButtonPanel(), BorderLayout.SOUTH);
218 }
219
220 @Override
221 public void showNotify() {
222 MapView.addLayerChangeListener(activateLayerAction);
223 MapView.addLayerChangeListener(model);
224 model.populate();
225 }
226
227 @Override
228 public void hideNotify() {
229 MapView.removeLayerChangeListener(model);
230 MapView.removeLayerChangeListener(activateLayerAction);
231 }
232
233 public LayerListModel getModel() {
234 return model;
235 }
236
237 protected interface IEnabledStateUpdating {
238 void updateEnabledState();
239 }
240
241 /**
242 * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that
243 * <code>listener</code> receives a {@see IEnabledStateUpdating#updateEnabledState()}
244 * on every {@see ListSelectionEvent}.
245 *
246 * @param listener the listener
247 * @param listSelectionModel the source emitting {@see ListSelectionEvent}s
248 */
249 protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) {
250 listSelectionModel.addListSelectionListener(
251 new ListSelectionListener() {
252 @Override
253 public void valueChanged(ListSelectionEvent e) {
254 listener.updateEnabledState();
255 }
256 }
257 );
258 }
259
260 /**
261 * Wires <code>listener</code> to <code>listModel</code> in such a way, that
262 * <code>listener</code> receives a {@see IEnabledStateUpdating#updateEnabledState()}
263 * on every {@see ListDataEvent}.
264 *
265 * @param listener the listener
266 * @param listSelectionModel the source emitting {@see ListDataEvent}s
267 */
268 protected void adaptTo(final IEnabledStateUpdating listener, LayerListModel listModel) {
269 listModel.addTableModelListener(
270 new TableModelListener() {
271
272 @Override
273 public void tableChanged(TableModelEvent e) {
274 listener.updateEnabledState();
275 }
276 }
277 );
278 }
279
280 @Override
281 public void destroy() {
282 super.destroy();
283 instance = null;
284 }
285
286 /**
287 * The action to delete the currently selected layer
288 */
289 public final class DeleteLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
290 /**
291 * Creates a {@see DeleteLayerAction} which will delete the currently
292 * selected layers in the layer dialog.
293 *
294 */
295 public DeleteLayerAction() {
296 putValue(SMALL_ICON,ImageProvider.get("dialogs", "delete"));
297 putValue(SHORT_DESCRIPTION, tr("Delete the selected layers."));
298 putValue(NAME, tr("Delete"));
299 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#DeleteLayer"));
300 updateEnabledState();
301 }
302
303 protected boolean enforceUploadOrSaveModifiedData(List<Layer> selectedLayers) {
304 SaveLayersDialog dialog = new SaveLayersDialog(Main.parent);
305 List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>();
306 for (Layer l: selectedLayers) {
307 if (! (l instanceof OsmDataLayer)) {
308 continue;
309 }
310 OsmDataLayer odl = (OsmDataLayer)l;
311 if ((odl.requiresSaveToFile() || odl.requiresUploadToServer()) && odl.data.isModified()) {
312 layersWithUnmodifiedChanges.add(odl);
313 }
314 }
315 dialog.prepareForSavingAndUpdatingLayersBeforeDelete();
316 if (!layersWithUnmodifiedChanges.isEmpty()) {
317 dialog.getModel().populate(layersWithUnmodifiedChanges);
318 dialog.setVisible(true);
319 switch(dialog.getUserAction()) {
320 case CANCEL: return false;
321 case PROCEED: return true;
322 default: return false;
323 }
324 }
325 return true;
326 }
327
328 @Override
329 public void actionPerformed(ActionEvent e) {
330 List<Layer> selectedLayers = getModel().getSelectedLayers();
331 if (selectedLayers.isEmpty())
332 return;
333 if (! enforceUploadOrSaveModifiedData(selectedLayers))
334 return;
335 for(Layer l: selectedLayers) {
336 Main.main.removeLayer(l);
337 }
338 }
339
340 @Override
341 public void updateEnabledState() {
342 setEnabled(! getModel().getSelectedLayers().isEmpty());
343 }
344
345 @Override
346 public Component createMenuComponent() {
347 return new JMenuItem(this);
348 }
349
350 @Override
351 public boolean supportLayers(List<Layer> layers) {
352 return true;
353 }
354
355 @Override
356 public boolean equals(Object obj) {
357 return obj instanceof DeleteLayerAction;
358 }
359
360 @Override
361 public int hashCode() {
362 return getClass().hashCode();
363 }
364 }
365
366 public final class ShowHideLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
367 private Layer layer;
368
369 /**
370 * Creates a {@see ShowHideLayerAction} which toggle the visibility of
371 * a specific layer.
372 *
373 * @param layer the layer. Must not be null.
374 * @exception IllegalArgumentException thrown, if layer is null
375 */
376 public ShowHideLayerAction(Layer layer) throws IllegalArgumentException {
377 this();
378 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
379 this.layer = layer;
380 updateEnabledState();
381 }
382
383 /**
384 * Creates a {@see ShowHideLayerAction} which will toggle the visibility of
385 * the currently selected layers
386 *
387 */
388 public ShowHideLayerAction() {
389 putValue(SMALL_ICON, ImageProvider.get("dialogs", "showhide"));
390 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the selected layer."));
391 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#ShowHideLayer"));
392 updateEnabledState();
393 }
394
395 @Override
396 public void actionPerformed(ActionEvent e) {
397 if (layer != null) {
398 layer.toggleVisible();
399 } else {
400 for(Layer l : model.getSelectedLayers()) {
401 l.toggleVisible();
402 }
403 }
404 }
405
406 @Override
407 public void updateEnabledState() {
408 if (layer == null) {
409 setEnabled(! getModel().getSelectedLayers().isEmpty());
410 } else {
411 setEnabled(true);
412 }
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 ShowHideLayerAction;
428 }
429
430 @Override
431 public int hashCode() {
432 return getClass().hashCode();
433 }
434 }
435
436 /**
437 * The action to activate the currently selected layer
438 */
439
440 public final class ActivateLayerAction extends AbstractAction implements IEnabledStateUpdating, MapView.LayerChangeListener{
441 private Layer layer;
442
443 public ActivateLayerAction(Layer layer) {
444 this();
445 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
446 this.layer = layer;
447 putValue(NAME, tr("Activate"));
448 updateEnabledState();
449 }
450
451 public ActivateLayerAction() {
452 putValue(SMALL_ICON, ImageProvider.get("dialogs", "activate"));
453 putValue(SHORT_DESCRIPTION, tr("Activate the selected layer"));
454 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#ActivateLayer"));
455 updateEnabledState();
456 }
457
458 @Override
459 public void actionPerformed(ActionEvent e) {
460 Layer toActivate;
461 if (layer != null) {
462 toActivate = layer;
463 } else {
464 toActivate = model.getSelectedLayers().get(0);
465 }
466 // model is going to be updated via LayerChangeListener
467 // and PropertyChangeEvents
468 Main.map.mapView.setActiveLayer(toActivate);
469 toActivate.setVisible(true);
470 }
471
472 protected boolean isActiveLayer(Layer layer) {
473 if (Main.map == null) return false;
474 if (Main.map.mapView == null) return false;
475 return Main.map.mapView.getActiveLayer() == layer;
476 }
477
478 @Override
479 public void updateEnabledState() {
480 if (layer == null) {
481 if (getModel().getSelectedLayers().size() != 1) {
482 setEnabled(false);
483 return;
484 }
485 Layer selectedLayer = getModel().getSelectedLayers().get(0);
486 setEnabled(!isActiveLayer(selectedLayer));
487 } else {
488 setEnabled(!isActiveLayer(layer));
489 }
490 }
491
492 @Override
493 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
494 updateEnabledState();
495 }
496 @Override
497 public void layerAdded(Layer newLayer) {
498 updateEnabledState();
499 }
500 @Override
501 public void layerRemoved(Layer oldLayer) {
502 updateEnabledState();
503 }
504 }
505
506 /**
507 * The action to merge the currently selected layer into another layer.
508 */
509 public final class MergeAction extends AbstractAction implements IEnabledStateUpdating {
510 private Layer layer;
511
512 public MergeAction(Layer layer) throws IllegalArgumentException {
513 this();
514 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
515 this.layer = layer;
516 putValue(NAME, tr("Merge"));
517 updateEnabledState();
518 }
519
520 public MergeAction() {
521 putValue(SMALL_ICON, ImageProvider.get("dialogs", "mergedown"));
522 putValue(SHORT_DESCRIPTION, tr("Merge this layer into another layer"));
523 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#MergeLayer"));
524 updateEnabledState();
525 }
526
527 @Override
528 public void actionPerformed(ActionEvent e) {
529 if (layer != null) {
530 new MergeLayerAction().merge(layer);
531 } else {
532 Layer selectedLayer = getModel().getSelectedLayers().get(0);
533 new MergeLayerAction().merge(selectedLayer);
534 }
535 }
536
537 protected boolean isActiveLayer(Layer layer) {
538 if (Main.map == null) return false;
539 if (Main.map.mapView == null) return false;
540 return Main.map.mapView.getActiveLayer() == layer;
541 }
542
543 @Override
544 public void updateEnabledState() {
545 if (layer == null) {
546 if (getModel().getSelectedLayers().size() != 1) {
547 setEnabled(false);
548 return;
549 }
550 Layer selectedLayer = getModel().getSelectedLayers().get(0);
551 List<Layer> targets = getModel().getPossibleMergeTargets(selectedLayer);
552 setEnabled(!targets.isEmpty());
553 } else {
554 List<Layer> targets = getModel().getPossibleMergeTargets(layer);
555 setEnabled(!targets.isEmpty());
556 }
557 }
558 }
559
560 /**
561 * The action to merge the currently selected layer into another layer.
562 */
563 public final class DuplicateAction extends AbstractAction implements IEnabledStateUpdating {
564 private Layer layer;
565
566 public DuplicateAction(Layer layer) throws IllegalArgumentException {
567 this();
568 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
569 this.layer = layer;
570 putValue(NAME, tr("Duplicate"));
571 updateEnabledState();
572 }
573
574 public DuplicateAction() {
575 putValue(SMALL_ICON, ImageProvider.get("dialogs", "duplicatelayer"));
576 putValue(SHORT_DESCRIPTION, tr("Duplicate this layer"));
577 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#DuplicateLayer"));
578 updateEnabledState();
579 }
580
581 @Override
582 public void actionPerformed(ActionEvent e) {
583 if (layer != null) {
584 new DuplicateLayerAction().duplicate(layer);
585 } else {
586 Layer selectedLayer = getModel().getSelectedLayers().get(0);
587 new DuplicateLayerAction().duplicate(selectedLayer);
588 }
589 }
590
591 protected boolean isActiveLayer(Layer layer) {
592 if (Main.map == null) return false;
593 if (Main.map.mapView == null) return false;
594 return Main.map.mapView.getActiveLayer() == layer;
595 }
596
597 @Override
598 public void updateEnabledState() {
599 if (layer == null) {
600 if (getModel().getSelectedLayers().size() == 1) {
601 setEnabled(DuplicateLayerAction.canDuplicate(getModel().getSelectedLayers().get(0)));
602 } else {
603 setEnabled(false);
604 }
605 } else {
606 setEnabled(DuplicateLayerAction.canDuplicate(layer));
607 }
608 }
609 }
610
611 private static class ActiveLayerCheckBox extends JCheckBox {
612 public ActiveLayerCheckBox() {
613 setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
614 ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank");
615 ImageIcon active = ImageProvider.get("dialogs/layerlist", "active");
616 setIcon(blank);
617 setSelectedIcon(active);
618 setRolloverIcon(blank);
619 setRolloverSelectedIcon(active);
620 setPressedIcon(ImageProvider.get("dialogs/layerlist", "active-pressed"));
621 }
622 }
623
624 private static class LayerVisibleCheckBox extends JCheckBox {
625 public LayerVisibleCheckBox() {
626 setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
627 ImageIcon eye = ImageProvider.get("dialogs/layerlist", "eye");
628 ImageIcon eye_off = ImageProvider.get("dialogs/layerlist", "eye-off");
629 setIcon(eye_off);
630 setSelectedIcon(eye);
631 setRolloverIcon(eye_off);
632 setRolloverSelectedIcon(eye);
633 setPressedIcon(ImageProvider.get("dialogs/layerlist", "eye-pressed"));
634 }
635 }
636
637 private static class ActiveLayerCellRenderer implements TableCellRenderer {
638 JCheckBox cb;
639 public ActiveLayerCellRenderer() {
640 cb = new ActiveLayerCheckBox();
641 }
642
643 @Override
644 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
645 boolean active = (Boolean) value;
646 cb.setSelected(active);
647 cb.setToolTipText(active ? tr("this layer is the active layer") : tr("this layer is not currently active (click to activate)"));
648 return cb;
649 }
650 }
651
652 private static class LayerVisibleCellRenderer implements TableCellRenderer {
653 JCheckBox cb;
654 public LayerVisibleCellRenderer() {
655 cb = new LayerVisibleCheckBox();
656 }
657
658 @Override
659 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
660 boolean visible = (Boolean) value;
661 cb.setSelected(visible);
662 cb.setToolTipText(visible ? tr("layer is currently visible (click to hide layer)") : tr("layer is currently hidden (click to show layer)"));
663 return cb;
664 }
665 }
666
667 private static class LayerNameCellRenderer extends DefaultTableCellRenderer {
668
669 protected boolean isActiveLayer(Layer layer) {
670 if (Main.map == null) return false;
671 if (Main.map.mapView == null) return false;
672 return Main.map.mapView.getActiveLayer() == layer;
673 }
674
675 @Override
676 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
677 Layer layer = (Layer)value;
678 JLabel label = (JLabel)super.getTableCellRendererComponent(table,
679 layer.getName(), isSelected, hasFocus, row, column);
680 if (isActiveLayer(layer)) {
681 label.setFont(label.getFont().deriveFont(Font.BOLD));
682 }
683 label.setIcon(layer.getIcon());
684 label.setToolTipText(layer.getToolTipText());
685 return label;
686 }
687 }
688
689 private static class LayerNameCellEditor extends DefaultCellEditor {
690 public LayerNameCellEditor(JTextField tf) {
691 super(tf);
692 }
693
694 @Override
695 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
696 JTextField tf = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
697 Layer l = (Layer) value;
698 tf.setText(l.getName());
699 return tf;
700 }
701 }
702
703 class PopupMenuHandler extends PopupMenuLauncher {
704 @Override
705 public void launch(MouseEvent evt) {
706 Point p = evt.getPoint();
707 int index = layerList.rowAtPoint(p);
708 if (index < 0) return;
709 if (!layerList.getCellRect(index, 2, false).contains(evt.getPoint()))
710 return;
711 if (!layerList.isRowSelected(index)) {
712 layerList.setRowSelectionInterval(index, index);
713 }
714 Layer layer = model.getLayer(index);
715 LayerListPopup menu = new LayerListPopup(getModel().getSelectedLayers(), layer);
716 menu.show(LayerListDialog.this, p.x, p.y-3);
717 }
718 }
719
720 /**
721 * The action to move up the currently selected entries in the list.
722 */
723 class MoveUpAction extends AbstractAction implements IEnabledStateUpdating{
724 public MoveUpAction() {
725 putValue(SMALL_ICON, ImageProvider.get("dialogs", "up"));
726 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row up."));
727 updateEnabledState();
728 }
729
730 @Override
731 public void updateEnabledState() {
732 setEnabled(model.canMoveUp());
733 }
734
735 @Override
736 public void actionPerformed(ActionEvent e) {
737 model.moveUp();
738 }
739 }
740
741 /**
742 * The action to move down the currently selected entries in the list.
743 */
744 class MoveDownAction extends AbstractAction implements IEnabledStateUpdating {
745 public MoveDownAction() {
746 putValue(SMALL_ICON, ImageProvider.get("dialogs", "down"));
747 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row down."));
748 updateEnabledState();
749 }
750
751 @Override
752 public void updateEnabledState() {
753 setEnabled(model.canMoveDown());
754 }
755
756 @Override
757 public void actionPerformed(ActionEvent e) {
758 model.moveDown();
759 }
760 }
761
762 /**
763 * Observer interface to be implemented by views using {@see LayerListModel}
764 *
765 */
766 public interface LayerListModelListener {
767 public void makeVisible(int index, Layer layer);
768 public void refresh();
769 }
770
771 /**
772 * The layer list model. The model manages a list of layers and provides methods for
773 * moving layers up and down, for toggling their visibility, and for activating a layer.
774 *
775 * The model is a {@see TableModel} and it provides a {@see ListSelectionModel}. It expects
776 * to be configured with a {@see DefaultListSelectionModel}. The selection model is used
777 * to update the selection state of views depending on messages sent to the model.
778 *
779 * The model manages a list of {@see LayerListModelListener} which are mainly notified if
780 * the model requires views to make a specific list entry visible.
781 *
782 * It also listens to {@see PropertyChangeEvent}s of every {@see Layer} it manages, in particular to
783 * the properties {@see Layer#VISIBLE_PROP} and {@see Layer#NAME_PROP}.
784 */
785 public class LayerListModel extends AbstractTableModel implements MapView.LayerChangeListener, PropertyChangeListener {
786 /** manages list selection state*/
787 private DefaultListSelectionModel selectionModel;
788 private CopyOnWriteArrayList<LayerListModelListener> listeners;
789
790 /**
791 * constructor
792 *
793 * @param selectionModel the list selection model
794 */
795 private LayerListModel(DefaultListSelectionModel selectionModel) {
796 this.selectionModel = selectionModel;
797 listeners = new CopyOnWriteArrayList<LayerListModelListener>();
798 }
799
800 /**
801 * Adds a listener to this model
802 *
803 * @param listener the listener
804 */
805 public void addLayerListModelListener(LayerListModelListener listener) {
806 if (listener != null) {
807 listeners.addIfAbsent(listener);
808 }
809 }
810
811 /**
812 * removes a listener from this model
813 * @param listener the listener
814 *
815 */
816 public void removeLayerListModelListener(LayerListModelListener listener) {
817 listeners.remove(listener);
818 }
819
820 /**
821 * Fires a make visible event to listeners
822 *
823 * @param index the index of the row to make visible
824 * @param layer the layer at this index
825 * @see LayerListModelListener#makeVisible(int, Layer)
826 */
827 protected void fireMakeVisible(int index, Layer layer) {
828 for (LayerListModelListener listener : listeners) {
829 listener.makeVisible(index, layer);
830 }
831 }
832
833 /**
834 * Fires a refresh event to listeners of this model
835 *
836 * @see LayerListModelListener#refresh()
837 */
838 protected void fireRefresh() {
839 for (LayerListModelListener listener : listeners) {
840 listener.refresh();
841 }
842 }
843
844 /**
845 * Populates the model with the current layers managed by
846 * {@see MapView}.
847 *
848 */
849 public void populate() {
850 for (Layer layer: getLayers()) {
851 // make sure the model is registered exactly once
852 //
853 layer.removePropertyChangeListener(this);
854 layer.addPropertyChangeListener(this);
855 }
856 fireTableDataChanged();
857 }
858
859 /**
860 * Marks <code>layer</code> as selected layer. Ignored, if
861 * layer is null.
862 *
863 * @param layer the layer.
864 */
865 public void setSelectedLayer(Layer layer) {
866 if (layer == null)
867 return;
868 int idx = getLayers().indexOf(layer);
869 if (idx >= 0) {
870 selectionModel.setSelectionInterval(idx, idx);
871 }
872 ensureSelectedIsVisible();
873 }
874
875 /**
876 * Replies the list of currently selected layers. Never null, but may
877 * be empty.
878 *
879 * @return the list of currently selected layers. Never null, but may
880 * be empty.
881 */
882 public List<Layer> getSelectedLayers() {
883 ArrayList<Layer> selected = new ArrayList<Layer>();
884 for (int i=0; i<getLayers().size(); i++) {
885 if (selectionModel.isSelectedIndex(i)) {
886 selected.add(getLayers().get(i));
887 }
888 }
889 return selected;
890 }
891
892 /**
893 * Replies a the list of indices of the selected rows. Never null,
894 * but may be empty.
895 *
896 * @return the list of indices of the selected rows. Never null,
897 * but may be empty.
898 */
899 public List<Integer> getSelectedRows() {
900 ArrayList<Integer> selected = new ArrayList<Integer>();
901 for (int i=0; i<getLayers().size();i++) {
902 if (selectionModel.isSelectedIndex(i)) {
903 selected.add(i);
904 }
905 }
906 return selected;
907 }
908
909 /**
910 * Invoked if a layer managed by {@see MapView} is removed
911 *
912 * @param layer the layer which is removed
913 */
914 protected void onRemoveLayer(Layer layer) {
915 if (layer == null)
916 return;
917 layer.removePropertyChangeListener(this);
918 int size = getRowCount();
919 List<Integer> rows = getSelectedRows();
920 if (rows.isEmpty() && size > 0) {
921 selectionModel.setSelectionInterval(size-1, size-1);
922 }
923 fireTableDataChanged();
924 fireRefresh();
925 ensureActiveSelected();
926 }
927
928 /**
929 * Invoked when a layer managed by {@see MapView} is added
930 *
931 * @param layer the layer
932 */
933 protected void onAddLayer(Layer layer) {
934 if (layer == null) return;
935 layer.addPropertyChangeListener(this);
936 fireTableDataChanged();
937 int idx = getLayers().indexOf(layer);
938 layerList.setRowHeight(idx, Math.max(16, layer.getIcon().getIconHeight()));
939 selectionModel.setSelectionInterval(idx, idx);
940 ensureSelectedIsVisible();
941 }
942
943 /**
944 * Replies the first layer. Null if no layers are present
945 *
946 * @return the first layer. Null if no layers are present
947 */
948 public Layer getFirstLayer() {
949 if (getRowCount() == 0) return null;
950 return getLayers().get(0);
951 }
952
953 /**
954 * Replies the layer at position <code>index</code>
955 *
956 * @param index the index
957 * @return the layer at position <code>index</code>. Null,
958 * if index is out of range.
959 */
960 public Layer getLayer(int index) {
961 if (index < 0 || index >= getRowCount())
962 return null;
963 return getLayers().get(index);
964 }
965
966 /**
967 * Replies true if the currently selected layers can move up
968 * by one position
969 *
970 * @return true if the currently selected layers can move up
971 * by one position
972 */
973 public boolean canMoveUp() {
974 List<Integer> sel = getSelectedRows();
975 return !sel.isEmpty() && sel.get(0) > 0;
976 }
977
978 /**
979 * Move up the currently selected layers by one position
980 *
981 */
982 public void moveUp() {
983 if (!canMoveUp()) return;
984 List<Integer> sel = getSelectedRows();
985 for (int row : sel) {
986 Layer l1 = getLayers().get(row);
987 Layer l2 = getLayers().get(row-1);
988 Main.map.mapView.moveLayer(l2,row);
989 Main.map.mapView.moveLayer(l1, row-1);
990 }
991 fireTableDataChanged();
992 selectionModel.clearSelection();
993 for (int row : sel) {
994 selectionModel.addSelectionInterval(row-1, row-1);
995 }
996 ensureSelectedIsVisible();
997 }
998
999 /**
1000 * Replies true if the currently selected layers can move down
1001 * by one position
1002 *
1003 * @return true if the currently selected layers can move down
1004 * by one position
1005 */
1006 public boolean canMoveDown() {
1007 List<Integer> sel = getSelectedRows();
1008 return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1;
1009 }
1010
1011 /**
1012 * Move down the currently selected layers by one position
1013 *
1014 */
1015 public void moveDown() {
1016 if (!canMoveDown()) return;
1017 List<Integer> sel = getSelectedRows();
1018 Collections.reverse(sel);
1019 for (int row : sel) {
1020 Layer l1 = getLayers().get(row);
1021 Layer l2 = getLayers().get(row+1);
1022 Main.map.mapView.moveLayer(l1, row+1);
1023 Main.map.mapView.moveLayer(l2, row);
1024 }
1025 fireTableDataChanged();
1026 selectionModel.clearSelection();
1027 for (int row : sel) {
1028 selectionModel.addSelectionInterval(row+1, row+1);
1029 }
1030 ensureSelectedIsVisible();
1031 }
1032
1033 /**
1034 * Make sure the first of the selected layers is visible in the
1035 * views of this model.
1036 *
1037 */
1038 protected void ensureSelectedIsVisible() {
1039 int index = selectionModel.getMinSelectionIndex();
1040 if (index < 0) return;
1041 if (index >= getLayers().size()) return;
1042 Layer layer = getLayers().get(index);
1043 fireMakeVisible(index, layer);
1044 }
1045
1046 /**
1047 * Replies a list of layers which are possible merge targets
1048 * for <code>source</code>
1049 *
1050 * @param source the source layer
1051 * @return a list of layers which are possible merge targets
1052 * for <code>source</code>. Never null, but can be empty.
1053 */
1054 public List<Layer> getPossibleMergeTargets(Layer source) {
1055 ArrayList<Layer> targets = new ArrayList<Layer>();
1056 if (source == null)
1057 return targets;
1058 for (Layer target : getLayers()) {
1059 if (source == target) {
1060 continue;
1061 }
1062 if (target.isMergable(source)) {
1063 targets.add(target);
1064 }
1065 }
1066 return targets;
1067 }
1068
1069 /**
1070 * Replies the list of layers currently managed by {@see MapView}.
1071 * Never null, but can be empty.
1072 *
1073 * @return the list of layers currently managed by {@see MapView}.
1074 * Never null, but can be empty.
1075 */
1076 protected List<Layer> getLayers() {
1077 if (Main.map == null || Main.map.mapView == null)
1078 return Collections.<Layer>emptyList();
1079 return Main.map.mapView.getAllLayersAsList();
1080 }
1081
1082 /**
1083 * Ensures that at least one layer is selected in the layer dialog
1084 *
1085 */
1086 protected void ensureActiveSelected() {
1087 if (getLayers().isEmpty())
1088 return;
1089 if (getActiveLayer() != null) {
1090 // there's an active layer - select it and make it
1091 // visible
1092 int idx = getLayers().indexOf(getActiveLayer());
1093 selectionModel.setSelectionInterval(idx, idx);
1094 ensureSelectedIsVisible();
1095 } else {
1096 // no active layer - select the first one and make
1097 // it visible
1098 selectionModel.setSelectionInterval(0, 0);
1099 ensureSelectedIsVisible();
1100 }
1101 }
1102
1103 /**
1104 * Replies the active layer. null, if no active layer is available
1105 *
1106 * @return the active layer. null, if no active layer is available
1107 */
1108 protected Layer getActiveLayer() {
1109 if (Main.map == null || Main.map.mapView == null) return null;
1110 return Main.map.mapView.getActiveLayer();
1111 }
1112
1113 /* ------------------------------------------------------------------------------ */
1114 /* Interface TableModel */
1115 /* ------------------------------------------------------------------------------ */
1116
1117 @Override
1118 public int getRowCount() {
1119 List<Layer> layers = getLayers();
1120 if (layers == null) return 0;
1121 return layers.size();
1122 }
1123
1124 @Override
1125 public int getColumnCount() {
1126 return 3;
1127 }
1128
1129 @Override
1130 public Object getValueAt(int row, int col) {
1131 switch (col) {
1132 case 0: return getLayers().get(row) == getActiveLayer();
1133 case 1: return getLayers().get(row).isVisible();
1134 case 2: return getLayers().get(row);
1135 default: throw new RuntimeException();
1136 }
1137 }
1138
1139 @Override
1140 public boolean isCellEditable(int row, int col) {
1141 if (col == 0 && getActiveLayer() == getLayers().get(row))
1142 return false;
1143 return true;
1144 }
1145
1146 @Override
1147 public void setValueAt(Object value, int row, int col) {
1148 Layer l = getLayers().get(row);
1149 switch (col) {
1150 case 0:
1151 Main.map.mapView.setActiveLayer(l);
1152 l.setVisible(true);
1153 break;
1154 case 1:
1155 l.setVisible((Boolean) value);
1156 break;
1157 case 2:
1158 l.setName((String) value);
1159 break;
1160 default: throw new RuntimeException();
1161 }
1162 fireTableCellUpdated(row, col);
1163 }
1164
1165 /* ------------------------------------------------------------------------------ */
1166 /* Interface LayerChangeListener */
1167 /* ------------------------------------------------------------------------------ */
1168 @Override
1169 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
1170 if (oldLayer != null) {
1171 int idx = getLayers().indexOf(oldLayer);
1172 if (idx >= 0) {
1173 fireTableRowsUpdated(idx,idx);
1174 }
1175 }
1176
1177 if (newLayer != null) {
1178 int idx = getLayers().indexOf(newLayer);
1179 if (idx >= 0) {
1180 fireTableRowsUpdated(idx,idx);
1181 }
1182 }
1183 ensureActiveSelected();
1184 }
1185
1186 @Override
1187 public void layerAdded(Layer newLayer) {
1188 onAddLayer(newLayer);
1189 }
1190
1191 @Override
1192 public void layerRemoved(final Layer oldLayer) {
1193 onRemoveLayer(oldLayer);
1194 }
1195
1196 /* ------------------------------------------------------------------------------ */
1197 /* Interface PropertyChangeListener */
1198 /* ------------------------------------------------------------------------------ */
1199 @Override
1200 public void propertyChange(PropertyChangeEvent evt) {
1201 if (evt.getSource() instanceof Layer) {
1202 Layer layer = (Layer)evt.getSource();
1203 final int idx = getLayers().indexOf(layer);
1204 if (idx < 0) return;
1205 fireRefresh();
1206 }
1207 }
1208 }
1209
1210 static class LayerList extends JTable {
1211 public LayerList(TableModel dataModel) {
1212 super(dataModel);
1213 }
1214
1215 public void scrollToVisible(int row, int col) {
1216 if (!(getParent() instanceof JViewport))
1217 return;
1218 JViewport viewport = (JViewport) getParent();
1219 Rectangle rect = getCellRect(row, col, true);
1220 Point pt = viewport.getViewPosition();
1221 rect.setLocation(rect.x - pt.x, rect.y - pt.y);
1222 viewport.scrollRectToVisible(rect);
1223 }
1224 }
1225
1226 /**
1227 * Creates a {@see ShowHideLayerAction} for <code>layer</code> in the
1228 * context of this {@see LayerListDialog}.
1229 *
1230 * @param layer the layer
1231 * @return the action
1232 */
1233 public ShowHideLayerAction createShowHideLayerAction() {
1234 return new ShowHideLayerAction();
1235 }
1236
1237 /**
1238 * Creates a {@see DeleteLayerAction} for <code>layer</code> in the
1239 * context of this {@see LayerListDialog}.
1240 *
1241 * @param layer the layer
1242 * @return the action
1243 */
1244 public DeleteLayerAction createDeleteLayerAction() {
1245 // the delete layer action doesn't depend on the current layer
1246 return new DeleteLayerAction();
1247 }
1248
1249 /**
1250 * Creates a {@see ActivateLayerAction} for <code>layer</code> in the
1251 * context of this {@see LayerListDialog}.
1252 *
1253 * @param layer the layer
1254 * @return the action
1255 */
1256 public ActivateLayerAction createActivateLayerAction(Layer layer) {
1257 return new ActivateLayerAction(layer);
1258 }
1259
1260 /**
1261 * Creates a {@see MergeLayerAction} for <code>layer</code> in the
1262 * context of this {@see LayerListDialog}.
1263 *
1264 * @param layer the layer
1265 * @return the action
1266 */
1267 public MergeAction createMergeLayerAction(Layer layer) {
1268 return new MergeAction(layer);
1269 }
1270}
Note: See TracBrowser for help on using the repository browser.