package org.openstreetmap.josm.gui.dialogs; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.AbstractAction; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.DefaultListSelectionModel; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.UIManager; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.DuplicateLayerAction; import org.openstreetmap.josm.actions.MergeLayerAction; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.SideButton; import org.openstreetmap.josm.gui.help.HelpUtil; import org.openstreetmap.josm.gui.io.SaveLayersDialog; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; import org.openstreetmap.josm.tools.ImageProvider.OverlayPosition; /** * This is a toggle dialog which displays the list of layers. Actions allow to * change the ordering of the layers, to hide/show layers, to activate layers, * and to delete layers. * */ public class LayerListDialog extends ToggleDialog { //static private final Logger logger = Logger.getLogger(LayerListDialog.class.getName()); /** the unique instance of the dialog */ static private LayerListDialog instance; /** * Creates the instance of the dialog. It's connected to the map frame <code>mapFrame</code> * * @param mapFrame the map frame */ static public void createInstance(MapFrame mapFrame) { if (instance != null) throw new IllegalStateException("Dialog was already created"); instance = new LayerListDialog(mapFrame); } /** * Replies the instance of the dialog * * @return the instance of the dialog * @throws IllegalStateException thrown, if the dialog is not created yet * @see #createInstance(MapFrame) */ static public LayerListDialog getInstance() throws IllegalStateException { if (instance == null) throw new IllegalStateException("Dialog not created yet. Invoke createInstance() first"); return instance; } /** the model for the layer list */ private LayerListModel model; /** the selection model */ private DefaultListSelectionModel selectionModel; /** the list of layers */ private LayerList layerList; ActivateLayerAction activateLayerAction; protected JPanel createButtonPanel() { JPanel buttonPanel = getButtonPanel(5); // -- move up action MoveUpAction moveUpAction = new MoveUpAction(); adaptTo(moveUpAction, model); adaptTo(moveUpAction,selectionModel); buttonPanel.add(new SideButton(moveUpAction)); // -- move down action MoveDownAction moveDownAction = new MoveDownAction(); adaptTo(moveDownAction, model); adaptTo(moveDownAction,selectionModel); buttonPanel.add(new SideButton(moveDownAction)); // -- activate action activateLayerAction = new ActivateLayerAction(); adaptTo(activateLayerAction, selectionModel); buttonPanel.add(new SideButton(activateLayerAction)); // -- show hide action ShowHideLayerAction showHideLayerAction = new ShowHideLayerAction(); adaptTo(showHideLayerAction, selectionModel); buttonPanel.add(new SideButton(showHideLayerAction)); // -- merge layer action MergeAction mergeLayerAction = new MergeAction(); adaptTo(mergeLayerAction, model); adaptTo(mergeLayerAction,selectionModel); buttonPanel.add(new SideButton(mergeLayerAction)); // -- duplicate layer action DuplicateAction duplicateLayerAction = new DuplicateAction(); adaptTo(duplicateLayerAction, model); adaptTo(duplicateLayerAction, selectionModel); buttonPanel.add(new SideButton(duplicateLayerAction)); //-- delete layer action DeleteLayerAction deleteLayerAction = new DeleteLayerAction(); layerList.getInputMap(JComponent.WHEN_FOCUSED).put( KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"deleteLayer" ); layerList.getActionMap().put("deleteLayer", deleteLayerAction); adaptTo(deleteLayerAction, selectionModel); buttonPanel.add(new SideButton(deleteLayerAction, false)); return buttonPanel; } /** * Create an layer list and attach it to the given mapView. */ protected LayerListDialog(MapFrame mapFrame) { super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."), Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L, Shortcut.GROUP_LAYER), 100, true); // create the models // selectionModel = new DefaultListSelectionModel(); selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); model = new LayerListModel(selectionModel); // create the list control // layerList = new LayerList(model); layerList.setSelectionModel(selectionModel); layerList.addMouseListener(new DblClickAdapter()); layerList.addMouseListener(new PopupMenuHandler()); layerList.setBackground(UIManager.getColor("Button.background")); layerList.setCellRenderer(new LayerListCellRenderer()); add(new JScrollPane(layerList), BorderLayout.CENTER); // init the model // final MapView mapView = mapFrame.mapView; model.populate(); model.setSelectedLayer(mapView.getActiveLayer()); model.addLayerListModelListener( new LayerListModelListener() { public void makeVisible(int index, Layer layer) { layerList.ensureIndexIsVisible(index); } public void refresh() { layerList.repaint(); } } ); add(createButtonPanel(), BorderLayout.SOUTH); } @Override public void showNotify() { MapView.addLayerChangeListener(activateLayerAction); MapView.addLayerChangeListener(model); model.populate(); } @Override public void hideNotify() { MapView.removeLayerChangeListener(model); MapView.removeLayerChangeListener(activateLayerAction); } public LayerListModel getModel() { return model; } private interface IEnabledStateUpdating { void updateEnabledState(); } /** * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that * <code>listener</code> receives a {@see IEnabledStateUpdating#updateEnabledState()} * on every {@see ListSelectionEvent}. * * @param listener the listener * @param listSelectionModel the source emitting {@see ListSelectionEvent}s */ protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) { listSelectionModel.addListSelectionListener( new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { listener.updateEnabledState(); } } ); } /** * Wires <code>listener</code> to <code>listModel</code> in such a way, that * <code>listener</code> receives a {@see IEnabledStateUpdating#updateEnabledState()} * on every {@see ListDataEvent}. * * @param listener the listener * @param listSelectionModel the source emitting {@see ListDataEvent}s */ protected void adaptTo(final IEnabledStateUpdating listener, ListModel listModel) { listModel.addListDataListener( new ListDataListener() { public void contentsChanged(ListDataEvent e) { listener.updateEnabledState(); } public void intervalAdded(ListDataEvent e) { listener.updateEnabledState(); } public void intervalRemoved(ListDataEvent e) { listener.updateEnabledState(); } } ); } @Override public void destroy() { super.destroy(); instance = null; } /** * The action to delete the currently selected layer */ public final class DeleteLayerAction extends AbstractAction implements IEnabledStateUpdating { /** * Creates a {@see DeleteLayerAction} which will delete the currently * selected layers in the layer dialog. * */ public DeleteLayerAction() { putValue(SMALL_ICON,ImageProvider.get("dialogs", "delete")); putValue(SHORT_DESCRIPTION, tr("Delete the selected layers.")); putValue(NAME, tr("Delete")); putValue("help", HelpUtil.ht("/Dialog/LayerDialog#DeleteLayer")); updateEnabledState(); } protected boolean enforceUploadOrSaveModifiedData(List<Layer> selectedLayers) { SaveLayersDialog dialog = new SaveLayersDialog(Main.parent); List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>(); for (Layer l: selectedLayers) { if (! (l instanceof OsmDataLayer)) { continue; } OsmDataLayer odl = (OsmDataLayer)l; if ((odl.requiresSaveToFile() || odl.requiresUploadToServer()) && odl.data.isModified()) { layersWithUnmodifiedChanges.add(odl); } } dialog.prepareForSavingAndUpdatingLayersBeforeDelete(); if (!layersWithUnmodifiedChanges.isEmpty()) { dialog.getModel().populate(layersWithUnmodifiedChanges); dialog.setVisible(true); switch(dialog.getUserAction()) { case CANCEL: return false; case PROCEED: return true; default: return false; } } return true; } public void actionPerformed(ActionEvent e) { List<Layer> selectedLayers = getModel().getSelectedLayers(); if (selectedLayers.isEmpty()) return; if (! enforceUploadOrSaveModifiedData(selectedLayers)) return; for(Layer l: selectedLayers) { Main.main.removeLayer(l); } } public void updateEnabledState() { setEnabled(! getModel().getSelectedLayers().isEmpty()); } } public final class ShowHideLayerAction extends AbstractAction implements IEnabledStateUpdating { private Layer layer; /** * Creates a {@see ShowHideLayerAction} which toggle the visibility of * a specific layer. * * @param layer the layer. Must not be null. * @exception IllegalArgumentException thrown, if layer is null */ public ShowHideLayerAction(Layer layer) throws IllegalArgumentException { this(); CheckParameterUtil.ensureParameterNotNull(layer, "layer"); this.layer = layer; putValue(NAME, tr("Show/Hide")); updateEnabledState(); } /** * Creates a {@see ShowHideLayerAction} which will toggle the visibility of * the currently selected layers * */ public ShowHideLayerAction() { putValue(SMALL_ICON, ImageProvider.get("dialogs", "showhide")); putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the selected layer.")); putValue("help", HelpUtil.ht("/Dialog/LayerDialog#ShowHideLayer")); updateEnabledState(); } public void actionPerformed(ActionEvent e) { if (layer != null) { layer.toggleVisible(); } else { for(Layer layer: model.getSelectedLayers()) { layer.toggleVisible(); } } } public void updateEnabledState() { if (layer == null) { setEnabled(! getModel().getSelectedLayers().isEmpty()); } else { setEnabled(true); } } } /** * The action to activate the currently selected layer */ public final class ActivateLayerAction extends AbstractAction implements IEnabledStateUpdating, MapView.LayerChangeListener{ private Layer layer; public ActivateLayerAction(Layer layer) { this(); CheckParameterUtil.ensureParameterNotNull(layer, "layer"); this.layer = layer; putValue(NAME, tr("Activate")); updateEnabledState(); } public ActivateLayerAction() { putValue(SMALL_ICON, ImageProvider.get("dialogs", "activate")); putValue(SHORT_DESCRIPTION, tr("Activate the selected layer")); putValue("help", HelpUtil.ht("/Dialog/LayerDialog#ActivateLayer")); updateEnabledState(); } public void actionPerformed(ActionEvent e) { Layer toActivate; if (layer != null) { toActivate = layer; } else { toActivate = model.getSelectedLayers().get(0); } // model is going to be updated via LayerChangeListener // and PropertyChangeEvents Main.map.mapView.setActiveLayer(toActivate); toActivate.setVisible(true); } protected boolean isActiveLayer(Layer layer) { if (Main.map == null) return false; if (Main.map.mapView == null) return false; return Main.map.mapView.getActiveLayer() == layer; } public void updateEnabledState() { if (layer == null) { if (getModel().getSelectedLayers().size() != 1) { setEnabled(false); return; } Layer selectedLayer = getModel().getSelectedLayers().get(0); setEnabled(!isActiveLayer(selectedLayer)); } else { setEnabled(!isActiveLayer(layer)); } } public void activeLayerChange(Layer oldLayer, Layer newLayer) { updateEnabledState(); } public void layerAdded(Layer newLayer) { updateEnabledState(); } public void layerRemoved(Layer oldLayer) { updateEnabledState(); } } /** * The action to merge the currently selected layer into another layer. */ public final class MergeAction extends AbstractAction implements IEnabledStateUpdating { private Layer layer; public MergeAction(Layer layer) throws IllegalArgumentException { this(); CheckParameterUtil.ensureParameterNotNull(layer, "layer"); this.layer = layer; putValue(NAME, tr("Merge")); updateEnabledState(); } public MergeAction() { putValue(SMALL_ICON, ImageProvider.get("dialogs", "mergedown")); putValue(SHORT_DESCRIPTION, tr("Merge this layer into another layer")); putValue("help", HelpUtil.ht("/Dialog/LayerDialog#MergeLayer")); updateEnabledState(); } public void actionPerformed(ActionEvent e) { if (layer != null) { new MergeLayerAction().merge(layer); } else { Layer selectedLayer = getModel().getSelectedLayers().get(0); new MergeLayerAction().merge(selectedLayer); } } protected boolean isActiveLayer(Layer layer) { if (Main.map == null) return false; if (Main.map.mapView == null) return false; return Main.map.mapView.getActiveLayer() == layer; } public void updateEnabledState() { if (layer == null) { if (getModel().getSelectedLayers().size() != 1) { setEnabled(false); return; } Layer selectedLayer = getModel().getSelectedLayers().get(0); List<Layer> targets = getModel().getPossibleMergeTargets(selectedLayer); setEnabled(!targets.isEmpty()); } else { List<Layer> targets = getModel().getPossibleMergeTargets(layer); setEnabled(!targets.isEmpty()); } } } /** * The action to merge the currently selected layer into another layer. */ public final class DuplicateAction extends AbstractAction implements IEnabledStateUpdating { private Layer layer; public DuplicateAction(Layer layer) throws IllegalArgumentException { this(); CheckParameterUtil.ensureParameterNotNull(layer, "layer"); this.layer = layer; putValue(NAME, tr("Duplicate")); updateEnabledState(); } public DuplicateAction() { putValue(SMALL_ICON, ImageProvider.get("dialogs", "duplicatelayer")); putValue(SHORT_DESCRIPTION, tr("Duplicate this layer")); putValue("help", HelpUtil.ht("/Dialog/LayerDialog#DuplicateLayer")); updateEnabledState(); } public void actionPerformed(ActionEvent e) { if (layer != null) { new DuplicateLayerAction().duplicate(layer); } else { Layer selectedLayer = getModel().getSelectedLayers().get(0); new DuplicateLayerAction().duplicate(selectedLayer); } } protected boolean isActiveLayer(Layer layer) { if (Main.map == null) return false; if (Main.map.mapView == null) return false; return Main.map.mapView.getActiveLayer() == layer; } public void updateEnabledState() { if (layer == null) { if (getModel().getSelectedLayers().size() == 1) { setEnabled(DuplicateLayerAction.canDuplicate(getModel().getSelectedLayers().get(0))); } else { setEnabled(false); } } else { setEnabled(DuplicateLayerAction.canDuplicate(layer)); } } } /** * the list cell renderer used to render layer list entries * */ static class LayerListCellRenderer extends DefaultListCellRenderer { protected boolean isActiveLayer(Layer layer) { if (Main.map == null) return false; if (Main.map.mapView == null) return false; return Main.map.mapView.getActiveLayer() == layer; } @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Layer layer = (Layer)value; JLabel label = (JLabel)super.getListCellRendererComponent(list, layer.getName(), index, isSelected, cellHasFocus); Icon icon = layer.getIcon(); if (isActiveLayer(layer)) { icon = ImageProvider.overlay(icon, "overlay/active", OverlayPosition.SOUTHWEST); } if (!layer.isVisible()) { icon = ImageProvider.overlay(icon, "overlay/invisiblenew", OverlayPosition.SOUTHEAST); } label.setIcon(icon); label.setToolTipText(layer.getToolTipText()); return label; } } class PopupMenuHandler extends PopupMenuLauncher { @Override public void launch(MouseEvent evt) { Point p = evt.getPoint(); int index = layerList.locationToIndex(p); if (index < 0) return; if (!layerList.getCellBounds(index, index).contains(evt.getPoint())) return; if (!layerList.isSelectedIndex(index)) { layerList.setSelectedIndex(index); } Layer layer = model.getLayer(index); LayerListPopup menu = new LayerListPopup(layerList, layer); menu.show(LayerListDialog.this, p.x, p.y-3); } } class DblClickAdapter extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { int index = layerList.locationToIndex(e.getPoint()); if (!layerList.getCellBounds(index, index).contains(e.getPoint())) return; Layer layer = model.getLayer(index); String current = Main.pref.get("marker.show "+layer.getName(),"show"); Main.pref.put("marker.show "+layer.getName(), current.equalsIgnoreCase("show") ? "hide" : "show"); layer.toggleVisible(); } } } /** * The action to move up the currently selected entries in the list. */ class MoveUpAction extends AbstractAction implements IEnabledStateUpdating{ public MoveUpAction() { putValue(SMALL_ICON, ImageProvider.get("dialogs", "up")); putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row up.")); updateEnabledState(); } public void updateEnabledState() { setEnabled(model.canMoveUp()); } public void actionPerformed(ActionEvent e) { model.moveUp(); } } /** * The action to move down the currently selected entries in the list. */ class MoveDownAction extends AbstractAction implements IEnabledStateUpdating { public MoveDownAction() { putValue(SMALL_ICON, ImageProvider.get("dialogs", "down")); putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row down.")); updateEnabledState(); } public void updateEnabledState() { setEnabled(model.canMoveDown()); } public void actionPerformed(ActionEvent e) { model.moveDown(); } } /** * Observer interface to be implemented by views using {@see LayerListModel} * */ public interface LayerListModelListener { public void makeVisible(int index, Layer layer); public void refresh(); } /** * The layer list model. The model manages a list of layers and provides methods for * moving layers up and down, for toggling their visibility, and for activating a layer. * * The model is a {@see ListModel} and it provides a {@see ListSelectionModel}. It expectes * to be configured with a {@see DefaultListSelectionModel}. The selection model is used * to update the selection state of views depending on messages sent to the model. * * The model manages a list of {@see LayerListModelListener} which are mainly notified if * the model requires views to make a specific list entry visible. * * It also listens to {@see PropertyChangeEvent}s of every {@see Layer} it manages, in particular to * the properties {@see Layer#VISIBLE_PROP} and {@see Layer#NAME_PROP}. */ public static class LayerListModel extends DefaultListModel implements MapView.LayerChangeListener, PropertyChangeListener{ /** manages list selection state*/ private DefaultListSelectionModel selectionModel; private CopyOnWriteArrayList<LayerListModelListener> listeners; /** * constructor * * @param selectionModel the list selection model */ private LayerListModel(DefaultListSelectionModel selectionModel) { this.selectionModel = selectionModel; listeners = new CopyOnWriteArrayList<LayerListModelListener>(); } /** * Adds a listener to this model * * @param listener the listener */ public void addLayerListModelListener(LayerListModelListener listener) { if (listener != null) { listeners.addIfAbsent(listener); } } /** * removes a listener from this model * @param listener the listener * */ public void removeLayerListModelListener(LayerListModelListener listener) { listeners.remove(listener); } /** * Fires a make visible event to listeners * * @param index the index of the row to make visible * @param layer the layer at this index * @see LayerListModelListener#makeVisible(int, Layer) */ protected void fireMakeVisible(int index, Layer layer) { for (LayerListModelListener listener : listeners) { listener.makeVisible(index, layer); } } /** * Fires a refresh event to listeners of this model * * @see LayerListModelListener#refresh() */ protected void fireRefresh() { for (LayerListModelListener listener : listeners) { listener.refresh(); } } /** * Populates the model with the current layers managed by * {@see MapView}. * */ public void populate() { for (Layer layer: getLayers()) { // make sure the model is registered exactly once // layer.removePropertyChangeListener(this); layer.addPropertyChangeListener(this); } fireContentsChanged(this, 0, getSize()); } /** * Marks <code>layer</code> as selected layer. Ignored, if * layer is null. * * @param layer the layer. */ public void setSelectedLayer(Layer layer) { if (layer == null) return; int idx = getLayers().indexOf(layer); if (idx >= 0) { selectionModel.setSelectionInterval(idx, idx); } fireContentsChanged(this, 0, getSize()); ensureSelectedIsVisible(); } /** * Replies the list of currently selected layers. Never null, but may * be empty. * * @return the list of currently selected layers. Never null, but may * be empty. */ public List<Layer> getSelectedLayers() { ArrayList<Layer> selected = new ArrayList<Layer>(); for (int i=0; i<getLayers().size(); i++) { if (selectionModel.isSelectedIndex(i)) { selected.add(getLayers().get(i)); } } return selected; } /** * Replies a the list of indices of the selected rows. Never null, * but may be empty. * * @return the list of indices of the selected rows. Never null, * but may be empty. */ public List<Integer> getSelectedRows() { ArrayList<Integer> selected = new ArrayList<Integer>(); for (int i=0; i<getLayers().size();i++) { if (selectionModel.isSelectedIndex(i)) { selected.add(i); } } return selected; } /** * Invoked if a layer managed by {@see MapView} is removed * * @param layer the layer which is removed */ protected void onRemoveLayer(Layer layer) { if (layer == null) return; layer.removePropertyChangeListener(this); int size = getSize(); List<Integer> rows = getSelectedRows(); if (rows.isEmpty() && size > 0) { selectionModel.setSelectionInterval(size-1, size-1); } fireRefresh(); ensureActiveSelected(); } /** * Invoked when a layer managed by {@see MapView} is added * * @param layer the layer */ protected void onAddLayer(Layer layer) { if (layer == null) return; layer.addPropertyChangeListener(this); fireContentsChanged(this, 0, getSize()); int idx = getLayers().indexOf(layer); selectionModel.setSelectionInterval(idx, idx); ensureSelectedIsVisible(); } /** * Replies the first layer. Null if no layers are present * * @return the first layer. Null if no layers are present */ public Layer getFirstLayer() { if (getSize() == 0) return null; return getLayers().get(0); } /** * Replies the layer at position <code>index</code> * * @param index the index * @return the layer at position <code>index</code>. Null, * if index is out of range. */ public Layer getLayer(int index) { if (index < 0 || index >= getSize()) return null; return getLayers().get(index); } /** * Replies true if the currently selected layers can move up * by one position * * @return true if the currently selected layers can move up * by one position */ public boolean canMoveUp() { List<Integer> sel = getSelectedRows(); return !sel.isEmpty() && sel.get(0) > 0; } /** * Move up the currently selected layers by one position * */ public void moveUp() { if (!canMoveUp()) return; List<Integer> sel = getSelectedRows(); for (int row: sel) { Layer l1 = getLayers().get(row); Layer l2 = getLayers().get(row-1); Main.map.mapView.moveLayer(l2,row); Main.map.mapView.moveLayer(l1, row-1); } fireContentsChanged(this, 0, getSize()); selectionModel.clearSelection(); for(int row: sel) { selectionModel.addSelectionInterval(row-1, row-1); } ensureSelectedIsVisible(); } /** * Replies true if the currently selected layers can move down * by one position * * @return true if the currently selected layers can move down * by one position */ public boolean canMoveDown() { List<Integer> sel = getSelectedRows(); return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1; } /** * Move down the currently selected layers by one position * */ public void moveDown() { if (!canMoveDown()) return; List<Integer> sel = getSelectedRows(); Collections.reverse(sel); for (int row: sel) { Layer l1 = getLayers().get(row); Layer l2 = getLayers().get(row+1); Main.map.mapView.moveLayer(l1, row+1); Main.map.mapView.moveLayer(l2, row); } fireContentsChanged(this, 0, getSize()); selectionModel.clearSelection(); for(int row: sel) { selectionModel.addSelectionInterval(row+1, row+1); } ensureSelectedIsVisible(); } /** * Make sure the first of the selected layers is visible in the * views of this model. * */ protected void ensureSelectedIsVisible() { int index = selectionModel.getMinSelectionIndex(); if (index <0 )return; if (index >= getLayers().size()) return; Layer layer = getLayers().get(index); fireMakeVisible(index, layer); } /** * Replies a list of layers which are possible merge targets * for <code>source</code> * * @param source the source layer * @return a list of layers which are possible merge targets * for <code>source</code>. Never null, but can be empty. */ public List<Layer> getPossibleMergeTargets(Layer source) { ArrayList<Layer> targets = new ArrayList<Layer>(); if (source == null) return targets; for(Layer target: getLayers()) { if (source == target) { continue; } if (target.isMergable(source)) { targets.add(target); } } return targets; } /** * Replies the list of layers currently managed by {@see MapView}. * Never null, but can be empty. * * @return the list of layers currently managed by {@see MapView}. * Never null, but can be empty. */ protected List<Layer> getLayers() { if (Main.map == null || Main.map.mapView == null) return Collections.<Layer>emptyList(); return Main.map.mapView.getAllLayersAsList(); } /** * Ensures that at least one layer is selected in the layer dialog * */ protected void ensureActiveSelected() { if (getLayers().size() == 0) return; if (getActiveLayer() != null) { // there's an active layer - select it and make it // visible int idx = getLayers().indexOf(getActiveLayer()); selectionModel.setSelectionInterval(idx, idx); ensureSelectedIsVisible(); } else { // no active layer - select the first one and make // it visible selectionModel.setSelectionInterval(0, 0); ensureSelectedIsVisible(); } } /** * Replies the active layer. null, if no active layer is available * * @return the active layer. null, if no active layer is available */ protected Layer getActiveLayer() { if (Main.map == null || Main.map.mapView == null) return null; return Main.map.mapView.getActiveLayer(); } /* ------------------------------------------------------------------------------ */ /* Interface ListModel */ /* ------------------------------------------------------------------------------ */ @Override public Object getElementAt(int index) { return getLayers().get(index); } @Override public int getSize() { List<Layer> layers = getLayers(); if (layers == null) return 0; return layers.size(); } /* ------------------------------------------------------------------------------ */ /* Interface LayerChangeListener */ /* ------------------------------------------------------------------------------ */ public void activeLayerChange(Layer oldLayer, Layer newLayer) { if (oldLayer != null) { int idx = getLayers().indexOf(oldLayer); if (idx >= 0) { fireContentsChanged(this, idx,idx); } } if (newLayer != null) { int idx = getLayers().indexOf(newLayer); if (idx >= 0) { fireContentsChanged(this, idx,idx); } } ensureActiveSelected(); } public void layerAdded(Layer newLayer) { onAddLayer(newLayer); } public void layerRemoved(final Layer oldLayer) { onRemoveLayer(oldLayer); } /* ------------------------------------------------------------------------------ */ /* Interface PropertyChangeListener */ /* ------------------------------------------------------------------------------ */ public void propertyChange(PropertyChangeEvent evt) { if (evt.getSource() instanceof Layer) { Layer layer = (Layer)evt.getSource(); final int idx = getLayers().indexOf(layer); if (idx < 0) return; fireRefresh(); } } } static class LayerList extends JList { public LayerList(ListModel dataModel) { super(dataModel); } @Override protected void processMouseEvent(MouseEvent e) { // if the layer list is embedded in a detached dialog, the last row is // selected if a user clicks in the empty space *below* the last row. // This mouse event filter prevents this. // int idx = locationToIndex(e.getPoint()); // sometimes bounds can be null, see #3539 Rectangle bounds = getCellBounds(idx,idx); if (bounds != null && bounds.contains(e.getPoint())) { super.processMouseEvent(e); } } } /** * Creates a {@see ShowHideLayerAction} for <code>layer</code> in the * context of this {@see LayerListDialog}. * * @param layer the layer * @return the action */ public ShowHideLayerAction createShowHideLayerAction(Layer layer) { return new ShowHideLayerAction(layer); } /** * Creates a {@see DeleteLayerAction} for <code>layer</code> in the * context of this {@see LayerListDialog}. * * @param layer the layer * @return the action */ public DeleteLayerAction createDeleteLayerAction(Layer layer) { // the delete layer action doesn't depend on the current layer return new DeleteLayerAction(); } /** * Creates a {@see ActivateLayerAction} for <code>layer</code> in the * context of this {@see LayerListDialog}. * * @param layer the layer * @return the action */ public ActivateLayerAction createActivateLayerAction(Layer layer) { return new ActivateLayerAction(layer); } /** * Creates a {@see MergeLayerAction} for <code>layer</code> in the * context of this {@see LayerListDialog}. * * @param layer the layer * @return the action */ public MergeAction createMergeLayerAction(Layer layer) { return new MergeAction(layer); } }