// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.layer; import static org.openstreetmap.josm.tools.I18n.tr; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.ListIterator; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.JOptionPane; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.util.GuiHelper; /** * This class extends the layer manager by adding an active and an edit layer. *

* The active layer is the layer the user is currently working on. *

* 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. *

* You can be sure that this layer is still contained in this set. *

* 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, null if there is none. */ public OsmDataLayer getPreviousEditLayer() { return previousEditLayer; } /** * Gets the active layer that was previously used. * @return The old active layer, null 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 activeLayerChangeListeners = new CopyOnWriteArrayList<>(); private final List 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, unless the layer is read-only. * 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. if (layer instanceof OsmDataLayer && ((OsmDataLayer) layer).isReadOnly()) { GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog( MainApplication.parent, tr("Trying to set a read only data layer as edit layer"), tr("Warning"), JOptionPane.WARNING_MESSAGE)); } else { 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 realRemoveSingleLayer(Layer layer) { if (layer == activeLayer || layer == editLayer) { Layer nextActive = suggestNextActiveLayer(layer); setActiveLayer(nextActive, true); } Collection 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: *

* * @param except A layer to ignore. * @return the next active data layer */ private Layer suggestNextActiveLayer(Layer except) { List 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() { if (activeLayer instanceof OsmDataLayer) { if (!((OsmDataLayer) activeLayer).isReadOnly()) { return activeLayer; } else { return null; } } else { return activeLayer; } } /** * Replies the current edit layer, if present and not readOnly * * @return the current edit layer. May be null. */ public synchronized OsmDataLayer getEditLayer() { if (editLayer != null && !editLayer.isReadOnly()) return editLayer; else return null; } /** * Gets the data set of the active edit layer. * @return That data set, null 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. *

* 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 getVisibleLayersInZOrder() { List ret = new ArrayList<>(); // This is set while we delay the addition of the active layer. boolean activeLayerDelayed = false; List layers = getLayers(); for (ListIterator 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; } /** * Invalidates current edit layer, if any. Does nothing of there is no edit layer. * @since 13150 */ public void invalidateEditLayer() { if (editLayer != null) { editLayer.invalidate(); } } @Override protected synchronized void realResetState() { // active and edit layer are unset automatically super.realResetState(); activeLayerChangeListeners.clear(); layerAvailabilityListeners.clear(); } /** * Prepares an OsmDataLayer for upload. The layer to be uploaded is locked and * if the layer to be uploaded is the current editLayer then editLayer is reset * to null for disallowing any changes to the layer. An ActiveLayerChangeEvent * is fired to notify the listeners * * @param layer The OsmDataLayer to be uploaded */ public void prepareLayerForUpload(OsmDataLayer layer) { GuiHelper.assertCallFromEdt(); layer.setReadOnly(); // Reset only the edit layer as empty if (editLayer == layer) { ActiveLayerChangeEvent activeLayerChangeEvent = new ActiveLayerChangeEvent(this, editLayer, activeLayer); editLayer = null; fireActiveLayerChange(activeLayerChangeEvent); } } /** * Post upload processing of the OsmDataLayer. * If the current edit layer is empty this function sets the layer uploaded as the * current editLayer. An ActiveLayerChangeEvent is fired to notify the listeners * * @param layer The OsmDataLayer uploaded */ public void processLayerAfterUpload(OsmDataLayer layer) { GuiHelper.assertCallFromEdt(); layer.unsetReadOnly(); // Set the layer as edit layer if the edit layer is empty. if (editLayer == null) { ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, editLayer, activeLayer); editLayer = layer; fireActiveLayerChange(layerChangeEvent); } } }