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
RevLine 
[3662]1// License: GPL. For details, see LICENSE file.
[626]2package org.openstreetmap.josm.gui.dialogs;
3
[304]4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
[3661]8import java.awt.Dimension;
9import java.awt.Font;
[304]10import java.awt.Point;
[2159]11import java.awt.Rectangle;
[304]12import java.awt.event.ActionEvent;
13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
[1890]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;
[304]21
22import javax.swing.AbstractAction;
[3661]23import javax.swing.DefaultCellEditor;
[1890]24import javax.swing.DefaultListSelectionModel;
[3661]25import javax.swing.ImageIcon;
26import javax.swing.JCheckBox;
[1917]27import javax.swing.JComponent;
[304]28import javax.swing.JLabel;
[3408]29import javax.swing.JMenuItem;
[304]30import javax.swing.JPanel;
31import javax.swing.JScrollPane;
[3661]32import javax.swing.JTable;
33import javax.swing.JTextField;
34import javax.swing.JViewport;
[1917]35import javax.swing.KeyStroke;
[304]36import javax.swing.ListSelectionModel;
37import javax.swing.UIManager;
38import javax.swing.event.ListSelectionEvent;
39import javax.swing.event.ListSelectionListener;
[3661]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;
[304]46
47import org.openstreetmap.josm.Main;
[2901]48import org.openstreetmap.josm.actions.DuplicateLayerAction;
[1890]49import org.openstreetmap.josm.actions.MergeLayerAction;
[304]50import org.openstreetmap.josm.gui.MapFrame;
51import org.openstreetmap.josm.gui.MapView;
[2710]52import org.openstreetmap.josm.gui.SideButton;
[2697]53import org.openstreetmap.josm.gui.help.HelpUtil;
[2025]54import org.openstreetmap.josm.gui.io.SaveLayersDialog;
[304]55import org.openstreetmap.josm.gui.layer.Layer;
56import org.openstreetmap.josm.gui.layer.OsmDataLayer;
[3408]57import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
[2697]58import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
[2869]59import org.openstreetmap.josm.tools.CheckParameterUtil;
[304]60import org.openstreetmap.josm.tools.ImageProvider;
[1890]61import org.openstreetmap.josm.tools.Shortcut;
[626]62
63/**
[1890]64 * This is a toggle dialog which displays the list of layers. Actions allow to
[2159]65 * change the ordering of the layers, to hide/show layers, to activate layers,
[1890]66 * and to delete layers.
[2224]67 *
[626]68 */
[1890]69public class LayerListDialog extends ToggleDialog {
[2039]70 //static private final Logger logger = Logger.getLogger(LayerListDialog.class.getName());
[626]71
[1890]72 /** the unique instance of the dialog */
73 static private LayerListDialog instance;
74
[1169]75 /**
[1890]76 * Creates the instance of the dialog. It's connected to the map frame <code>mapFrame</code>
[2224]77 *
[1890]78 * @param mapFrame the map frame
[1169]79 */
[1890]80 static public void createInstance(MapFrame mapFrame) {
[2869]81 if (instance != null)
82 throw new IllegalStateException("Dialog was already created");
[1890]83 instance = new LayerListDialog(mapFrame);
84 }
[626]85
[1890]86 /**
87 * Replies the instance of the dialog
[2224]88 *
[1890]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)
[2847]95 throw new IllegalStateException("Dialog not created yet. Invoke createInstance() first");
[1890]96 return instance;
97 }
[626]98
[1890]99 /** the model for the layer list */
100 private LayerListModel model;
[626]101
[1890]102 /** the selection model */
103 private DefaultListSelectionModel selectionModel;
104
[3661]105 /** the list of layers (technically its a JTable, but appears like a list) */
[1890]106 private LayerList layerList;
107
[2621]108 ActivateLayerAction activateLayerAction;
109
[1890]110 protected JPanel createButtonPanel() {
[2710]111 JPanel buttonPanel = getButtonPanel(5);
[1890]112
113 // -- move up action
114 MoveUpAction moveUpAction = new MoveUpAction();
115 adaptTo(moveUpAction, model);
116 adaptTo(moveUpAction,selectionModel);
[2710]117 buttonPanel.add(new SideButton(moveUpAction));
[1890]118
119 // -- move down action
120 MoveDownAction moveDownAction = new MoveDownAction();
121 adaptTo(moveDownAction, model);
122 adaptTo(moveDownAction,selectionModel);
[2710]123 buttonPanel.add(new SideButton(moveDownAction));
[1890]124
125 // -- activate action
[2621]126 activateLayerAction = new ActivateLayerAction();
[1890]127 adaptTo(activateLayerAction, selectionModel);
[2710]128 buttonPanel.add(new SideButton(activateLayerAction));
[1890]129
130 // -- show hide action
131 ShowHideLayerAction showHideLayerAction = new ShowHideLayerAction();
132 adaptTo(showHideLayerAction, selectionModel);
[2710]133 buttonPanel.add(new SideButton(showHideLayerAction));
[1890]134
135 // -- merge layer action
136 MergeAction mergeLayerAction = new MergeAction();
137 adaptTo(mergeLayerAction, model);
138 adaptTo(mergeLayerAction,selectionModel);
[2710]139 buttonPanel.add(new SideButton(mergeLayerAction));
[1890]140
[2901]141 // -- duplicate layer action
142 DuplicateAction duplicateLayerAction = new DuplicateAction();
143 adaptTo(duplicateLayerAction, model);
144 adaptTo(duplicateLayerAction, selectionModel);
145 buttonPanel.add(new SideButton(duplicateLayerAction));
146
[1890]147 //-- delete layer action
148 DeleteLayerAction deleteLayerAction = new DeleteLayerAction();
[1935]149 layerList.getInputMap(JComponent.WHEN_FOCUSED).put(
150 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"deleteLayer"
151 );
152 layerList.getActionMap().put("deleteLayer", deleteLayerAction);
[1890]153 adaptTo(deleteLayerAction, selectionModel);
[2710]154 buttonPanel.add(new SideButton(deleteLayerAction, false));
[1890]155
156 return buttonPanel;
157 }
158
159 /**
[1917]160 * Create an layer list and attach it to the given mapView.
[1890]161 */
162 protected LayerListDialog(MapFrame mapFrame) {
163 super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."),
[2224]164 Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L, Shortcut.GROUP_LAYER), 100, true);
[1890]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);
[2697]176 layerList.addMouseListener(new PopupMenuHandler());
[1890]177 layerList.setBackground(UIManager.getColor("Button.background"));
[3661]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()));
[3664]184 layerList.getColumnModel().getColumn(0).setMaxWidth(12);
185 layerList.getColumnModel().getColumn(0).setPreferredWidth(12);
[3661]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()));
[3664]189 layerList.getColumnModel().getColumn(1).setMaxWidth(16);
190 layerList.getColumnModel().getColumn(1).setPreferredWidth(16);
[3661]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
[1890]195 add(new JScrollPane(layerList), BorderLayout.CENTER);
196
197 // init the model
198 //
199 final MapView mapView = mapFrame.mapView;
[1895]200 model.populate();
[1890]201 model.setSelectedLayer(mapView.getActiveLayer());
202 model.addLayerListModelListener(
203 new LayerListModelListener() {
[3662]204 @Override
[3661]205 public void makeVisible(int row, Layer layer) {
206 System.err.println(Thread.currentThread());
207 layerList.scrollToVisible(row, 0);
208 layerList.repaint();
[1890]209 }
[3662]210 @Override
[1895]211 public void refresh() {
212 layerList.repaint();
213 }
[1890]214 }
215 );
216
217 add(createButtonPanel(), BorderLayout.SOUTH);
218 }
219
[2621]220 @Override
[2869]221 public void showNotify() {
222 MapView.addLayerChangeListener(activateLayerAction);
223 MapView.addLayerChangeListener(model);
[2897]224 model.populate();
[2869]225 }
226
227 @Override
228 public void hideNotify() {
[2621]229 MapView.removeLayerChangeListener(model);
230 MapView.removeLayerChangeListener(activateLayerAction);
231 }
232
[1890]233 public LayerListModel getModel() {
234 return model;
235 }
236
[3661]237 protected interface IEnabledStateUpdating {
[1890]238 void updateEnabledState();
239 }
240
[1917]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}.
[2224]245 *
[1917]246 * @param listener the listener
247 * @param listSelectionModel the source emitting {@see ListSelectionEvent}s
248 */
[1890]249 protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) {
250 listSelectionModel.addListSelectionListener(
251 new ListSelectionListener() {
[3662]252 @Override
[1890]253 public void valueChanged(ListSelectionEvent e) {
254 listener.updateEnabledState();
255 }
256 }
257 );
258 }
259
[1917]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}.
[2224]264 *
[1917]265 * @param listener the listener
266 * @param listSelectionModel the source emitting {@see ListDataEvent}s
267 */
[3661]268 protected void adaptTo(final IEnabledStateUpdating listener, LayerListModel listModel) {
269 listModel.addTableModelListener(
270 new TableModelListener() {
[1890]271
[3661]272 @Override
273 public void tableChanged(TableModelEvent e) {
274 listener.updateEnabledState();
[1890]275 }
[3661]276 }
[1890]277 );
278 }
[1895]279
[2869]280 @Override
281 public void destroy() {
282 super.destroy();
283 instance = null;
284 }
285
[1890]286 /**
287 * The action to delete the currently selected layer
288 */
[3408]289 public final class DeleteLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
[1890]290 /**
291 * Creates a {@see DeleteLayerAction} which will delete the currently
292 * selected layers in the layer dialog.
[2224]293 *
[1890]294 */
295 public DeleteLayerAction() {
296 putValue(SMALL_ICON,ImageProvider.get("dialogs", "delete"));
[2697]297 putValue(SHORT_DESCRIPTION, tr("Delete the selected layers."));
298 putValue(NAME, tr("Delete"));
299 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#DeleteLayer"));
[1890]300 updateEnabledState();
[1169]301 }
[626]302
[2025]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;
[1896]309 }
[2025]310 OsmDataLayer odl = (OsmDataLayer)l;
[2434]311 if ((odl.requiresSaveToFile() || odl.requiresUploadToServer()) && odl.data.isModified()) {
[2025]312 layersWithUnmodifiedChanges.add(odl);
313 }
[1169]314 }
[2025]315 dialog.prepareForSavingAndUpdatingLayersBeforeDelete();
316 if (!layersWithUnmodifiedChanges.isEmpty()) {
317 dialog.getModel().populate(layersWithUnmodifiedChanges);
318 dialog.setVisible(true);
319 switch(dialog.getUserAction()) {
[2434]320 case CANCEL: return false;
321 case PROCEED: return true;
322 default: return false;
[1895]323 }
324 }
[2025]325 return true;
[1895]326 }
327
[3662]328 @Override
[1890]329 public void actionPerformed(ActionEvent e) {
[2697]330 List<Layer> selectedLayers = getModel().getSelectedLayers();
[2025]331 if (selectedLayers.isEmpty())
332 return;
333 if (! enforceUploadOrSaveModifiedData(selectedLayers))
334 return;
335 for(Layer l: selectedLayers) {
336 Main.main.removeLayer(l);
337 }
[1890]338 }
339
[3662]340 @Override
[1890]341 public void updateEnabledState() {
[2697]342 setEnabled(! getModel().getSelectedLayers().isEmpty());
[1169]343 }
[3408]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 }
[1169]364 }
[626]365
[3408]366 public final class ShowHideLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction {
[1890]367 private Layer layer;
[626]368
[1890]369 /**
370 * Creates a {@see ShowHideLayerAction} which toggle the visibility of
371 * a specific layer.
[2224]372 *
[1890]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();
[2847]378 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
[1890]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
[2224]386 *
[1890]387 */
388 public ShowHideLayerAction() {
389 putValue(SMALL_ICON, ImageProvider.get("dialogs", "showhide"));
[1169]390 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the selected layer."));
[2697]391 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#ShowHideLayer"));
[1890]392 updateEnabledState();
[1169]393 }
[626]394
[3662]395 @Override
[1169]396 public void actionPerformed(ActionEvent e) {
[1890]397 if (layer != null) {
398 layer.toggleVisible();
399 } else {
[3662]400 for(Layer l : model.getSelectedLayers()) {
401 l.toggleVisible();
[1890]402 }
403 }
[1169]404 }
[1890]405
[3662]406 @Override
[1890]407 public void updateEnabledState() {
408 if (layer == null) {
409 setEnabled(! getModel().getSelectedLayers().isEmpty());
410 } else {
411 setEnabled(true);
412 }
413 }
[3408]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 }
[1169]434 }
[626]435
[1890]436 /**
437 * The action to activate the currently selected layer
438 */
[626]439
[2621]440 public final class ActivateLayerAction extends AbstractAction implements IEnabledStateUpdating, MapView.LayerChangeListener{
[1890]441 private Layer layer;
442
[2847]443 public ActivateLayerAction(Layer layer) {
[1890]444 this();
[2847]445 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
[1169]446 this.layer = layer;
[1890]447 putValue(NAME, tr("Activate"));
448 updateEnabledState();
[1169]449 }
[626]450
[1890]451 public ActivateLayerAction() {
452 putValue(SMALL_ICON, ImageProvider.get("dialogs", "activate"));
453 putValue(SHORT_DESCRIPTION, tr("Activate the selected layer"));
[2697]454 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#ActivateLayer"));
[1890]455 updateEnabledState();
456 }
457
[3662]458 @Override
[1169]459 public void actionPerformed(ActionEvent e) {
[1890]460 Layer toActivate;
461 if (layer != null) {
462 toActivate = layer;
463 } else {
464 toActivate = model.getSelectedLayers().get(0);
465 }
[1917]466 // model is going to be updated via LayerChangeListener
467 // and PropertyChangeEvents
468 Main.map.mapView.setActiveLayer(toActivate);
469 toActivate.setVisible(true);
[1169]470 }
[1890]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
[3662]478 @Override
[1890]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 }
[2621]491
[3662]492 @Override
[2621]493 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
494 updateEnabledState();
495 }
[3662]496 @Override
[2621]497 public void layerAdded(Layer newLayer) {
498 updateEnabledState();
499 }
[3662]500 @Override
[2621]501 public void layerRemoved(Layer oldLayer) {
502 updateEnabledState();
503 }
[1169]504 }
[626]505
[1169]506 /**
[1890]507 * The action to merge the currently selected layer into another layer.
[1169]508 */
[1890]509 public final class MergeAction extends AbstractAction implements IEnabledStateUpdating {
510 private Layer layer;
511
512 public MergeAction(Layer layer) throws IllegalArgumentException {
513 this();
[2847]514 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
[1890]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"));
[2697]523 putValue("help", HelpUtil.ht("/Dialog/LayerDialog#MergeLayer"));
[1890]524 updateEnabledState();
525 }
526
[3662]527 @Override
[1890]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
[3662]543 @Override
[1890]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
[1169]560 /**
[2901]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
[3662]581 @Override
[2901]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
[3662]597 @Override
[2901]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
[3661]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);
[3664]620 setPressedIcon(ImageProvider.get("dialogs/layerlist", "active-pressed"));
[3661]621 }
622 }
[1890]623
[3661]624 private static class LayerVisibleCheckBox extends JCheckBox {
625 public LayerVisibleCheckBox() {
[3664]626 setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
[3661]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
[1890]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
[3661]675 @Override
676 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
[1890]677 Layer layer = (Layer)value;
[3661]678 JLabel label = (JLabel)super.getTableCellRendererComponent(table,
679 layer.getName(), isSelected, hasFocus, row, column);
[1890]680 if (isActiveLayer(layer)) {
[3661]681 label.setFont(label.getFont().deriveFont(Font.BOLD));
[1890]682 }
[3661]683 label.setIcon(layer.getIcon());
[1890]684 label.setToolTipText(layer.getToolTipText());
685 return label;
686 }
687 }
688
[3661]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
[2697]703 class PopupMenuHandler extends PopupMenuLauncher {
704 @Override
705 public void launch(MouseEvent evt) {
706 Point p = evt.getPoint();
[3661]707 int index = layerList.rowAtPoint(p);
[1890]708 if (index < 0) return;
[3661]709 if (!layerList.getCellRect(index, 2, false).contains(evt.getPoint()))
[1890]710 return;
[3661]711 if (!layerList.isRowSelected(index)) {
712 layerList.setRowSelectionInterval(index, index);
[2602]713 }
[1890]714 Layer layer = model.getLayer(index);
[3408]715 LayerListPopup menu = new LayerListPopup(getModel().getSelectedLayers(), layer);
[1890]716 menu.show(LayerListDialog.this, p.x, p.y-3);
717 }
[2697]718 }
719
[1169]720 /**
[1890]721 * The action to move up the currently selected entries in the list.
[1169]722 */
[1890]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
[3662]730 @Override
[1890]731 public void updateEnabledState() {
732 setEnabled(model.canMoveUp());
733 }
734
[3662]735 @Override
[1890]736 public void actionPerformed(ActionEvent e) {
737 model.moveUp();
738 }
739 }
740
[1169]741 /**
[1890]742 * The action to move down the currently selected entries in the list.
[1169]743 */
[1890]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
[3662]751 @Override
[1890]752 public void updateEnabledState() {
753 setEnabled(model.canMoveDown());
754 }
755
[3662]756 @Override
[1890]757 public void actionPerformed(ActionEvent e) {
758 model.moveDown();
759 }
760 }
761
[1169]762 /**
[1890]763 * Observer interface to be implemented by views using {@see LayerListModel}
764 *
[1169]765 */
[1890]766 public interface LayerListModelListener {
767 public void makeVisible(int index, Layer layer);
[1895]768 public void refresh();
[1890]769 }
[626]770
[1169]771 /**
[1890]772 * The layer list model. The model manages a list of layers and provides methods for
[1917]773 * moving layers up and down, for toggling their visibility, and for activating a layer.
[2224]774 *
[3661]775 * The model is a {@see TableModel} and it provides a {@see ListSelectionModel}. It expects
[1890]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.
[2224]778 *
[1890]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.
[2224]781 *
[1917]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}.
[1169]784 */
[3664]785 public class LayerListModel extends AbstractTableModel implements MapView.LayerChangeListener, PropertyChangeListener {
[1911]786 /** manages list selection state*/
[1890]787 private DefaultListSelectionModel selectionModel;
788 private CopyOnWriteArrayList<LayerListModelListener> listeners;
789
[1911]790 /**
791 * constructor
[2224]792 *
[1911]793 * @param selectionModel the list selection model
794 */
[1890]795 private LayerListModel(DefaultListSelectionModel selectionModel) {
796 this.selectionModel = selectionModel;
797 listeners = new CopyOnWriteArrayList<LayerListModelListener>();
798 }
799
[1911]800 /**
801 * Adds a listener to this model
[2224]802 *
[1911]803 * @param listener the listener
804 */
[1890]805 public void addLayerListModelListener(LayerListModelListener listener) {
[2655]806 if (listener != null) {
807 listeners.addIfAbsent(listener);
[1880]808 }
[1890]809 }
810
[1911]811 /**
812 * removes a listener from this model
813 * @param listener the listener
[2224]814 *
[1911]815 */
[1890]816 public void removeLayerListModelListener(LayerListModelListener listener) {
[2655]817 listeners.remove(listener);
[1890]818 }
[626]819
[1911]820 /**
821 * Fires a make visible event to listeners
[2224]822 *
[1911]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 */
[1890]827 protected void fireMakeVisible(int index, Layer layer) {
828 for (LayerListModelListener listener : listeners) {
829 listener.makeVisible(index, layer);
830 }
831 }
[626]832
[1911]833 /**
834 * Fires a refresh event to listeners of this model
[2224]835 *
[1911]836 * @see LayerListModelListener#refresh()
837 */
[1895]838 protected void fireRefresh() {
839 for (LayerListModelListener listener : listeners) {
840 listener.refresh();
[1890]841 }
[1895]842 }
843
[1911]844 /**
845 * Populates the model with the current layers managed by
846 * {@see MapView}.
[2224]847 *
[1911]848 */
[1895]849 public void populate() {
[1911]850 for (Layer layer: getLayers()) {
851 // make sure the model is registered exactly once
852 //
853 layer.removePropertyChangeListener(this);
854 layer.addPropertyChangeListener(this);
[1895]855 }
[3661]856 fireTableDataChanged();
[1838]857 }
[626]858
[1911]859 /**
860 * Marks <code>layer</code> as selected layer. Ignored, if
861 * layer is null.
[2224]862 *
[1911]863 * @param layer the layer.
864 */
[1890]865 public void setSelectedLayer(Layer layer) {
[1911]866 if (layer == null)
[1895]867 return;
868 int idx = getLayers().indexOf(layer);
[1890]869 if (idx >= 0) {
870 selectionModel.setSelectionInterval(idx, idx);
[1169]871 }
[1890]872 ensureSelectedIsVisible();
873 }
[626]874
[1911]875 /**
876 * Replies the list of currently selected layers. Never null, but may
877 * be empty.
[2224]878 *
[1911]879 * @return the list of currently selected layers. Never null, but may
880 * be empty.
881 */
[1890]882 public List<Layer> getSelectedLayers() {
883 ArrayList<Layer> selected = new ArrayList<Layer>();
[1895]884 for (int i=0; i<getLayers().size(); i++) {
[1890]885 if (selectionModel.isSelectedIndex(i)) {
[1895]886 selected.add(getLayers().get(i));
[1838]887 }
[1169]888 }
[1890]889 return selected;
890 }
891
[1911]892 /**
893 * Replies a the list of indices of the selected rows. Never null,
894 * but may be empty.
[2224]895 *
[1911]896 * @return the list of indices of the selected rows. Never null,
897 * but may be empty.
898 */
[1890]899 public List<Integer> getSelectedRows() {
900 ArrayList<Integer> selected = new ArrayList<Integer>();
[1895]901 for (int i=0; i<getLayers().size();i++) {
[1890]902 if (selectionModel.isSelectedIndex(i)) {
903 selected.add(i);
[1838]904 }
[1169]905 }
[1890]906 return selected;
907 }
908
[1911]909 /**
910 * Invoked if a layer managed by {@see MapView} is removed
[2224]911 *
[1911]912 * @param layer the layer which is removed
913 */
914 protected void onRemoveLayer(Layer layer) {
[1890]915 if (layer == null)
916 return;
[1917]917 layer.removePropertyChangeListener(this);
[3661]918 int size = getRowCount();
[1911]919 List<Integer> rows = getSelectedRows();
920 if (rows.isEmpty() && size > 0) {
921 selectionModel.setSelectionInterval(size-1, size-1);
922 }
[3661]923 fireTableDataChanged();
[1895]924 fireRefresh();
[2677]925 ensureActiveSelected();
[1890]926 }
[626]927
[1911]928 /**
929 * Invoked when a layer managed by {@see MapView} is added
[2224]930 *
[1911]931 * @param layer the layer
932 */
933 protected void onAddLayer(Layer layer) {
[1890]934 if (layer == null) return;
935 layer.addPropertyChangeListener(this);
[3661]936 fireTableDataChanged();
[1917]937 int idx = getLayers().indexOf(layer);
[3664]938 layerList.setRowHeight(idx, Math.max(16, layer.getIcon().getIconHeight()));
[1917]939 selectionModel.setSelectionInterval(idx, idx);
940 ensureSelectedIsVisible();
[1890]941 }
[626]942
[1911]943 /**
944 * Replies the first layer. Null if no layers are present
[2224]945 *
[1911]946 * @return the first layer. Null if no layers are present
947 */
[1890]948 public Layer getFirstLayer() {
[3661]949 if (getRowCount() == 0) return null;
[1895]950 return getLayers().get(0);
[1890]951 }
[626]952
[1911]953 /**
954 * Replies the layer at position <code>index</code>
[2224]955 *
[1911]956 * @param index the index
957 * @return the layer at position <code>index</code>. Null,
958 * if index is out of range.
959 */
[1890]960 public Layer getLayer(int index) {
[3661]961 if (index < 0 || index >= getRowCount())
[1890]962 return null;
[1895]963 return getLayers().get(index);
[1890]964 }
965
[1911]966 /**
[2711]967 * Replies true if the currently selected layers can move up
[1911]968 * by one position
[2224]969 *
[2711]970 * @return true if the currently selected layers can move up
[1911]971 * by one position
972 */
[1890]973 public boolean canMoveUp() {
974 List<Integer> sel = getSelectedRows();
975 return !sel.isEmpty() && sel.get(0) > 0;
976 }
977
[1911]978 /**
979 * Move up the currently selected layers by one position
[2224]980 *
[1911]981 */
[1890]982 public void moveUp() {
983 if (!canMoveUp()) return;
984 List<Integer> sel = getSelectedRows();
[3662]985 for (int row : sel) {
[1895]986 Layer l1 = getLayers().get(row);
987 Layer l2 = getLayers().get(row-1);
988 Main.map.mapView.moveLayer(l2,row);
[1890]989 Main.map.mapView.moveLayer(l1, row-1);
[1169]990 }
[3661]991 fireTableDataChanged();
[1890]992 selectionModel.clearSelection();
[3662]993 for (int row : sel) {
[1890]994 selectionModel.addSelectionInterval(row-1, row-1);
995 }
996 ensureSelectedIsVisible();
997 }
[626]998
[1911]999 /**
1000 * Replies true if the currently selected layers can move down
1001 * by one position
[2224]1002 *
[1911]1003 * @return true if the currently selected layers can move down
1004 * by one position
1005 */
[1890]1006 public boolean canMoveDown() {
1007 List<Integer> sel = getSelectedRows();
[1895]1008 return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1;
[1890]1009 }
[626]1010
[1911]1011 /**
1012 * Move down the currently selected layers by one position
[2224]1013 *
[1911]1014 */
[1890]1015 public void moveDown() {
1016 if (!canMoveDown()) return;
1017 List<Integer> sel = getSelectedRows();
1018 Collections.reverse(sel);
[3662]1019 for (int row : sel) {
[1895]1020 Layer l1 = getLayers().get(row);
1021 Layer l2 = getLayers().get(row+1);
[1890]1022 Main.map.mapView.moveLayer(l1, row+1);
[1895]1023 Main.map.mapView.moveLayer(l2, row);
[1890]1024 }
[3661]1025 fireTableDataChanged();
[1890]1026 selectionModel.clearSelection();
[3662]1027 for (int row : sel) {
[1890]1028 selectionModel.addSelectionInterval(row+1, row+1);
1029 }
1030 ensureSelectedIsVisible();
1031 }
[626]1032
[1911]1033 /**
1034 * Make sure the first of the selected layers is visible in the
1035 * views of this model.
[2224]1036 *
[1911]1037 */
[1890]1038 protected void ensureSelectedIsVisible() {
1039 int index = selectionModel.getMinSelectionIndex();
[3662]1040 if (index < 0) return;
[1895]1041 if (index >= getLayers().size()) return;
1042 Layer layer = getLayers().get(index);
[1890]1043 fireMakeVisible(index, layer);
1044 }
[626]1045
[1911]1046 /**
1047 * Replies a list of layers which are possible merge targets
1048 * for <code>source</code>
[2224]1049 *
[1911]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) {
[1890]1055 ArrayList<Layer> targets = new ArrayList<Layer>();
[1911]1056 if (source == null)
[1890]1057 return targets;
[3662]1058 for (Layer target : getLayers()) {
[1911]1059 if (source == target) {
[1890]1060 continue;
1061 }
[1911]1062 if (target.isMergable(source)) {
[1890]1063 targets.add(target);
1064 }
[1169]1065 }
[1890]1066 return targets;
1067 }
[626]1068
[1911]1069 /**
1070 * Replies the list of layers currently managed by {@see MapView}.
1071 * Never null, but can be empty.
[2224]1072 *
[1911]1073 * @return the list of layers currently managed by {@see MapView}.
1074 * Never null, but can be empty.
1075 */
[1895]1076 protected List<Layer> getLayers() {
[1911]1077 if (Main.map == null || Main.map.mapView == null)
1078 return Collections.<Layer>emptyList();
[1895]1079 return Main.map.mapView.getAllLayersAsList();
1080 }
1081
[1917]1082 /**
1083 * Ensures that at least one layer is selected in the layer dialog
[2224]1084 *
[1917]1085 */
1086 protected void ensureActiveSelected() {
[3661]1087 if (getLayers().isEmpty())
1088 return;
[1917]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
[2224]1105 *
[1917]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
[1890]1113 /* ------------------------------------------------------------------------------ */
[3661]1114 /* Interface TableModel */
[1890]1115 /* ------------------------------------------------------------------------------ */
[626]1116
[1890]1117 @Override
[3661]1118 public int getRowCount() {
[1895]1119 List<Layer> layers = getLayers();
1120 if (layers == null) return 0;
[1890]1121 return layers.size();
1122 }
[1246]1123
[3661]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
[3662]1139 @Override
[3661]1140 public boolean isCellEditable(int row, int col) {
1141 if (col == 0 && getActiveLayer() == getLayers().get(row))
1142 return false;
1143 return true;
1144 }
1145
[3662]1146 @Override
[3661]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
[1890]1165 /* ------------------------------------------------------------------------------ */
1166 /* Interface LayerChangeListener */
1167 /* ------------------------------------------------------------------------------ */
[3661]1168 @Override
[1890]1169 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
1170 if (oldLayer != null) {
[1895]1171 int idx = getLayers().indexOf(oldLayer);
[1890]1172 if (idx >= 0) {
[3661]1173 fireTableRowsUpdated(idx,idx);
[1890]1174 }
1175 }
1176
1177 if (newLayer != null) {
[1895]1178 int idx = getLayers().indexOf(newLayer);
[1890]1179 if (idx >= 0) {
[3661]1180 fireTableRowsUpdated(idx,idx);
[1890]1181 }
1182 }
[1917]1183 ensureActiveSelected();
[1228]1184 }
[626]1185
[3661]1186 @Override
[1890]1187 public void layerAdded(Layer newLayer) {
[1911]1188 onAddLayer(newLayer);
[1890]1189 }
[626]1190
[3661]1191 @Override
[1895]1192 public void layerRemoved(final Layer oldLayer) {
[1911]1193 onRemoveLayer(oldLayer);
[1169]1194 }
[1890]1195
1196 /* ------------------------------------------------------------------------------ */
1197 /* Interface PropertyChangeListener */
1198 /* ------------------------------------------------------------------------------ */
[3661]1199 @Override
[1890]1200 public void propertyChange(PropertyChangeEvent evt) {
1201 if (evt.getSource() instanceof Layer) {
1202 Layer layer = (Layer)evt.getSource();
[1895]1203 final int idx = getLayers().indexOf(layer);
[1890]1204 if (idx < 0) return;
[1895]1205 fireRefresh();
[1890]1206 }
[1838]1207 }
[1169]1208 }
[626]1209
[3661]1210 static class LayerList extends JTable {
1211 public LayerList(TableModel dataModel) {
[1890]1212 super(dataModel);
[1838]1213 }
[1890]1214
[3661]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);
[1890]1223 }
[1169]1224 }
[1890]1225
[1917]1226 /**
1227 * Creates a {@see ShowHideLayerAction} for <code>layer</code> in the
1228 * context of this {@see LayerListDialog}.
[2224]1229 *
[1917]1230 * @param layer the layer
1231 * @return the action
1232 */
[3408]1233 public ShowHideLayerAction createShowHideLayerAction() {
1234 return new ShowHideLayerAction();
[1890]1235 }
1236
[1917]1237 /**
1238 * Creates a {@see DeleteLayerAction} for <code>layer</code> in the
1239 * context of this {@see LayerListDialog}.
[2224]1240 *
[1917]1241 * @param layer the layer
1242 * @return the action
1243 */
[3408]1244 public DeleteLayerAction createDeleteLayerAction() {
[2697]1245 // the delete layer action doesn't depend on the current layer
1246 return new DeleteLayerAction();
[1890]1247 }
1248
[1917]1249 /**
1250 * Creates a {@see ActivateLayerAction} for <code>layer</code> in the
1251 * context of this {@see LayerListDialog}.
[2224]1252 *
[1917]1253 * @param layer the layer
1254 * @return the action
1255 */
[1890]1256 public ActivateLayerAction createActivateLayerAction(Layer layer) {
1257 return new ActivateLayerAction(layer);
1258 }
1259
[1917]1260 /**
1261 * Creates a {@see MergeLayerAction} for <code>layer</code> in the
1262 * context of this {@see LayerListDialog}.
[2224]1263 *
[1917]1264 * @param layer the layer
1265 * @return the action
1266 */
[1890]1267 public MergeAction createMergeLayerAction(Layer layer) {
1268 return new MergeAction(layer);
1269 }
[626]1270}
Note: See TracBrowser for help on using the repository browser.