// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.layer; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.ListIterator; import java.util.concurrent.CopyOnWriteArrayList; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.gui.util.GuiHelper; /** * This class extends the layer manager by adding an active and an edit layer. * <p> * The active layer is the layer the user is currently working on. * <p> * The edit layer is an data layer that we currently work with. * @author Michael Zangl * @since 10279 */ public class MainLayerManager extends LayerManager { /** * This listener listens to changes of the active or the edit layer. * @author Michael Zangl * @since 10600 (functional interface) */ @FunctionalInterface public interface ActiveLayerChangeListener { /** * Called whenever the active or edit layer changed. * <p> * You can be sure that this layer is still contained in this set. * <p> * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread. * @param e The change event. */ void activeOrEditLayerChanged(ActiveLayerChangeEvent e); } /** * This event is fired whenever the active or the edit layer changes. * @author Michael Zangl */ public static class ActiveLayerChangeEvent extends LayerManagerEvent { private final OsmDataLayer previousEditLayer; private final Layer previousActiveLayer; /** * Create a new {@link ActiveLayerChangeEvent} * @param source The source * @param previousEditLayer the previous edit layer * @param previousActiveLayer the previous active layer */ ActiveLayerChangeEvent(MainLayerManager source, OsmDataLayer previousEditLayer, Layer previousActiveLayer) { super(source); this.previousEditLayer = previousEditLayer; this.previousActiveLayer = previousActiveLayer; } /** * Gets the edit layer that was previously used. * @return The old edit layer, <code>null</code> if there is none. */ public OsmDataLayer getPreviousEditLayer() { return previousEditLayer; } /** * Gets the active layer that was previously used. * @return The old active layer, <code>null</code> if there is none. */ public Layer getPreviousActiveLayer() { return previousActiveLayer; } /** * Gets the data set that was previously used. * @return The data set of {@link #getPreviousEditLayer()}. */ public DataSet getPreviousEditDataSet() { if (previousEditLayer != null) { return previousEditLayer.data; } else { return null; } } @Override public MainLayerManager getSource() { return (MainLayerManager) super.getSource(); } } /** * This event is fired for {@link LayerAvailabilityListener} * @author Michael Zangl * @since 10508 */ public static class LayerAvailabilityEvent extends LayerManagerEvent { private final boolean hasLayers; LayerAvailabilityEvent(LayerManager source, boolean hasLayers) { super(source); this.hasLayers = hasLayers; } /** * Checks if this layer manager will have layers afterwards * @return true if layers will be added. */ public boolean hasLayers() { return hasLayers; } } /** * A listener that gets informed before any layer is displayed and after all layers are removed. * @author Michael Zangl * @since 10508 */ public interface LayerAvailabilityListener { /** * This method is called in the UI thread right before the first layer is added. * @param e The event. */ void beforeFirstLayerAdded(LayerAvailabilityEvent e); /** * This method is called in the UI thread after the last layer was removed. * @param e The event. */ void afterLastLayerRemoved(LayerAvailabilityEvent e); } /** * The layer from the layers list that is currently active. */ private Layer activeLayer; /** * The edit layer is the current active data layer. */ private OsmDataLayer editLayer; private final List<ActiveLayerChangeListener> activeLayerChangeListeners = new CopyOnWriteArrayList<>(); private final List<LayerAvailabilityListener> layerAvailabilityListeners = new CopyOnWriteArrayList<>(); /** * Adds a active/edit layer change listener * * @param listener the listener. */ public synchronized void addActiveLayerChangeListener(ActiveLayerChangeListener listener) { if (activeLayerChangeListeners.contains(listener)) { throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener); } activeLayerChangeListeners.add(listener); } /** * Adds a active/edit layer change listener. Fire a fake active-layer-changed-event right after adding * the listener. The previous layers will be null. The listener is notified in the current thread. * @param listener the listener. */ public synchronized void addAndFireActiveLayerChangeListener(ActiveLayerChangeListener listener) { addActiveLayerChangeListener(listener); listener.activeOrEditLayerChanged(new ActiveLayerChangeEvent(this, null, null)); } /** * Removes an active/edit layer change listener. * @param listener the listener. */ public synchronized void removeActiveLayerChangeListener(ActiveLayerChangeListener listener) { if (!activeLayerChangeListeners.contains(listener)) { throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener); } activeLayerChangeListeners.remove(listener); } /** * Add a new {@link LayerAvailabilityListener}. * @param listener The listener * @since 10508 */ public synchronized void addLayerAvailabilityListener(LayerAvailabilityListener listener) { if (!layerAvailabilityListeners.add(listener)) { throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener); } } /** * Remove an {@link LayerAvailabilityListener}. * @param listener The listener * @since 10508 */ public synchronized void removeLayerAvailabilityListener(LayerAvailabilityListener listener) { if (!layerAvailabilityListeners.remove(listener)) { throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener); } } /** * Set the active layer. If the layer is an OsmDataLayer, the edit layer is also changed. * @param layer The active layer. */ public void setActiveLayer(final Layer layer) { // we force this on to the EDT Thread to make events fire from there. // The synchronization lock needs to be held by the EDT. GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer)); } protected synchronized void realSetActiveLayer(final Layer layer) { // to be called in EDT thread checkContainsLayer(layer); setActiveLayer(layer, false); } private void setActiveLayer(Layer layer, boolean forceEditLayerUpdate) { ActiveLayerChangeEvent event = new ActiveLayerChangeEvent(this, editLayer, activeLayer); activeLayer = layer; if (activeLayer instanceof OsmDataLayer) { editLayer = (OsmDataLayer) activeLayer; } else if (forceEditLayerUpdate) { editLayer = null; } fireActiveLayerChange(event); } private void fireActiveLayerChange(ActiveLayerChangeEvent event) { GuiHelper.assertCallFromEdt(); if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousEditLayer() != editLayer) { for (ActiveLayerChangeListener l : activeLayerChangeListeners) { l.activeOrEditLayerChanged(event); } } } @Override protected synchronized void realAddLayer(Layer layer, boolean initialZoom) { if (getLayers().isEmpty()) { LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, true); for (LayerAvailabilityListener l : layerAvailabilityListeners) { l.beforeFirstLayerAdded(e); } } super.realAddLayer(layer, initialZoom); // update the active layer automatically. if (layer instanceof OsmDataLayer || activeLayer == null) { setActiveLayer(layer); } } @Override protected Collection<Layer> realRemoveSingleLayer(Layer layer) { if (layer == activeLayer || layer == editLayer) { Layer nextActive = suggestNextActiveLayer(layer); setActiveLayer(nextActive, true); } Collection<Layer> toDelete = super.realRemoveSingleLayer(layer); if (getLayers().isEmpty()) { LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, false); for (LayerAvailabilityListener l : layerAvailabilityListeners) { l.afterLastLayerRemoved(e); } } return toDelete; } /** * Determines the next active data layer according to the following * rules: * <ul> * <li>if there is at least one {@link OsmDataLayer} the first one * becomes active</li> * <li>otherwise, the top most layer of any type becomes active</li> * </ul> * * @param except A layer to ignore. * @return the next active data layer */ private Layer suggestNextActiveLayer(Layer except) { List<Layer> layersList = new ArrayList<>(getLayers()); layersList.remove(except); // First look for data layer for (Layer layer : layersList) { if (layer instanceof OsmDataLayer) { return layer; } } // Then any layer if (!layersList.isEmpty()) return layersList.get(0); // and then give up return null; } /** * Replies the currently active layer * * @return the currently active layer (may be null) */ public synchronized Layer getActiveLayer() { return activeLayer; } /** * Replies the current edit layer, if any * * @return the current edit layer. May be null. */ public synchronized OsmDataLayer getEditLayer() { return editLayer; } /** * Gets the data set of the active edit layer. * @return That data set, <code>null</code> if there is no edit layer. */ public synchronized DataSet getEditDataSet() { if (editLayer != null) { return editLayer.data; } else { return null; } } /** * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order * first, layer with the highest Z-Order last. * <p> * The active data layer is pulled above all adjacent data layers. * * @return a list of the visible in Z-Order, the layer with the lowest Z-Order * first, layer with the highest Z-Order last. */ public synchronized List<Layer> getVisibleLayersInZOrder() { List<Layer> ret = new ArrayList<>(); // This is set while we delay the addition of the active layer. boolean activeLayerDelayed = false; List<Layer> layers = getLayers(); for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) { Layer l = iterator.previous(); if (!l.isVisible()) { // ignored } else if (l == activeLayer && l instanceof OsmDataLayer) { // delay and add after the current block of OsmDataLayer activeLayerDelayed = true; } else { if (activeLayerDelayed && !(l instanceof OsmDataLayer)) { // add active layer before the current one. ret.add(activeLayer); activeLayerDelayed = false; } // Add this layer now ret.add(l); } } if (activeLayerDelayed) { ret.add(activeLayer); } return ret; } @Override protected synchronized void realResetState() { // active and edit layer are unset automatically super.realResetState(); activeLayerChangeListeners.clear(); layerAvailabilityListeners.clear(); } }