// 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:
*
* - if there is at least one {@link OsmDataLayer} the first one
* becomes active
* - otherwise, the top most layer of any type becomes active
*
*
* @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);
}
}
}