001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.List;
007import java.util.ListIterator;
008import java.util.concurrent.CopyOnWriteArrayList;
009
010import org.openstreetmap.josm.data.osm.DataSet;
011import org.openstreetmap.josm.gui.util.GuiHelper;
012
013/**
014 * This class extends the layer manager by adding an active and an edit layer.
015 * <p>
016 * The active layer is the layer the user is currently working on.
017 * <p>
018 * The edit layer is an data layer that we currently work with.
019 * @author Michael Zangl
020 * @since 10279
021 */
022public class MainLayerManager extends LayerManager {
023    /**
024     * This listener listens to changes of the active or the edit layer.
025     * @author Michael Zangl
026     * @since 10600 (functional interface)
027     */
028    @FunctionalInterface
029    public interface ActiveLayerChangeListener {
030        /**
031         * Called whenever the active or edit layer changed.
032         * <p>
033         * You can be sure that this layer is still contained in this set.
034         * <p>
035         * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread.
036         * @param e The change event.
037         */
038        void activeOrEditLayerChanged(ActiveLayerChangeEvent e);
039    }
040
041    /**
042     * This event is fired whenever the active or the edit layer changes.
043     * @author Michael Zangl
044     */
045    public static class ActiveLayerChangeEvent extends LayerManagerEvent {
046
047        private final OsmDataLayer previousEditLayer;
048
049        private final Layer previousActiveLayer;
050
051        /**
052         * Create a new {@link ActiveLayerChangeEvent}
053         * @param source The source
054         * @param previousEditLayer the previous edit layer
055         * @param previousActiveLayer the previous active layer
056         */
057        ActiveLayerChangeEvent(MainLayerManager source, OsmDataLayer previousEditLayer,
058                Layer previousActiveLayer) {
059            super(source);
060            this.previousEditLayer = previousEditLayer;
061            this.previousActiveLayer = previousActiveLayer;
062        }
063
064        /**
065         * Gets the edit layer that was previously used.
066         * @return The old edit layer, <code>null</code> if there is none.
067         */
068        public OsmDataLayer getPreviousEditLayer() {
069            return previousEditLayer;
070        }
071
072        /**
073         * Gets the active layer that was previously used.
074         * @return The old active layer, <code>null</code> if there is none.
075         */
076        public Layer getPreviousActiveLayer() {
077            return previousActiveLayer;
078        }
079
080        /**
081         * Gets the data set that was previously used.
082         * @return The data set of {@link #getPreviousEditLayer()}.
083         */
084        public DataSet getPreviousEditDataSet() {
085            if (previousEditLayer != null) {
086                return previousEditLayer.data;
087            } else {
088                return null;
089            }
090        }
091
092        @Override
093        public MainLayerManager getSource() {
094            return (MainLayerManager) super.getSource();
095        }
096    }
097
098    /**
099     * This event is fired for {@link LayerAvailabilityListener}
100     * @author Michael Zangl
101     * @since 10508
102     */
103    public static class LayerAvailabilityEvent extends LayerManagerEvent {
104        private final boolean hasLayers;
105
106        LayerAvailabilityEvent(LayerManager source, boolean hasLayers) {
107            super(source);
108            this.hasLayers = hasLayers;
109        }
110
111        /**
112         * Checks if this layer manager will have layers afterwards
113         * @return true if layers will be added.
114         */
115        public boolean hasLayers() {
116            return hasLayers;
117        }
118    }
119
120    /**
121     * A listener that gets informed before any layer is displayed and after all layers are removed.
122     * @author Michael Zangl
123     * @since 10508
124     */
125    public interface LayerAvailabilityListener {
126        /**
127         * This method is called in the UI thread right before the first layer is added.
128         * @param e The event.
129         */
130        void beforeFirstLayerAdded(LayerAvailabilityEvent e);
131
132        /**
133         * This method is called in the UI thread after the last layer was removed.
134         * @param e The event.
135         */
136        void afterLastLayerRemoved(LayerAvailabilityEvent e);
137    }
138
139    /**
140     * The layer from the layers list that is currently active.
141     */
142    private Layer activeLayer;
143
144    /**
145     * The edit layer is the current active data layer.
146     */
147    private OsmDataLayer editLayer;
148
149    private final List<ActiveLayerChangeListener> activeLayerChangeListeners = new CopyOnWriteArrayList<>();
150    private final List<LayerAvailabilityListener> layerAvailabilityListeners = new CopyOnWriteArrayList<>();
151
152    /**
153     * Adds a active/edit layer change listener
154     *
155     * @param listener the listener.
156     */
157    public synchronized void addActiveLayerChangeListener(ActiveLayerChangeListener listener) {
158        if (activeLayerChangeListeners.contains(listener)) {
159            throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener);
160        }
161        activeLayerChangeListeners.add(listener);
162    }
163
164    /**
165     * Adds a active/edit layer change listener. Fire a fake active-layer-changed-event right after adding
166     * the listener. The previous layers will be null. The listener is notified in the current thread.
167     * @param listener the listener.
168     */
169    public synchronized void addAndFireActiveLayerChangeListener(ActiveLayerChangeListener listener) {
170        addActiveLayerChangeListener(listener);
171        listener.activeOrEditLayerChanged(new ActiveLayerChangeEvent(this, null, null));
172    }
173
174    /**
175     * Removes an active/edit layer change listener.
176     * @param listener the listener.
177     */
178    public synchronized void removeActiveLayerChangeListener(ActiveLayerChangeListener listener) {
179        if (!activeLayerChangeListeners.contains(listener)) {
180            throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener);
181        }
182        activeLayerChangeListeners.remove(listener);
183    }
184
185    /**
186     * Add a new {@link LayerAvailabilityListener}.
187     * @param listener The listener
188     * @since 10508
189     */
190    public synchronized void addLayerAvailabilityListener(LayerAvailabilityListener listener) {
191        if (!layerAvailabilityListeners.add(listener)) {
192            throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener);
193        }
194    }
195
196    /**
197     * Remove an {@link LayerAvailabilityListener}.
198     * @param listener The listener
199     * @since 10508
200     */
201    public synchronized void removeLayerAvailabilityListener(LayerAvailabilityListener listener) {
202        if (!layerAvailabilityListeners.remove(listener)) {
203            throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener);
204        }
205    }
206
207    /**
208     * Set the active layer. If the layer is an OsmDataLayer, the edit layer is also changed.
209     * @param layer The active layer.
210     */
211    public void setActiveLayer(final Layer layer) {
212        // we force this on to the EDT Thread to make events fire from there.
213        // The synchronization lock needs to be held by the EDT.
214        GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer));
215    }
216
217    protected synchronized void realSetActiveLayer(final Layer layer) {
218        // to be called in EDT thread
219        checkContainsLayer(layer);
220        setActiveLayer(layer, false);
221    }
222
223    private void setActiveLayer(Layer layer, boolean forceEditLayerUpdate) {
224        ActiveLayerChangeEvent event = new ActiveLayerChangeEvent(this, editLayer, activeLayer);
225        activeLayer = layer;
226        if (activeLayer instanceof OsmDataLayer) {
227            editLayer = (OsmDataLayer) activeLayer;
228        } else if (forceEditLayerUpdate) {
229            editLayer = null;
230        }
231        fireActiveLayerChange(event);
232    }
233
234    private void fireActiveLayerChange(ActiveLayerChangeEvent event) {
235        GuiHelper.assertCallFromEdt();
236        if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousEditLayer() != editLayer) {
237            for (ActiveLayerChangeListener l : activeLayerChangeListeners) {
238                l.activeOrEditLayerChanged(event);
239            }
240        }
241    }
242
243    @Override
244    protected synchronized void realAddLayer(Layer layer) {
245        if (getLayers().isEmpty()) {
246            LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, true);
247            for (LayerAvailabilityListener l : layerAvailabilityListeners) {
248                l.beforeFirstLayerAdded(e);
249            }
250        }
251        super.realAddLayer(layer);
252
253        // update the active layer automatically.
254        if (layer instanceof OsmDataLayer || activeLayer == null) {
255            setActiveLayer(layer);
256        }
257    }
258
259    @Override
260    protected Collection<Layer> realRemoveSingleLayer(Layer layer) {
261        if (layer == activeLayer || layer == editLayer) {
262            Layer nextActive = suggestNextActiveLayer(layer);
263            setActiveLayer(nextActive, true);
264        }
265
266        Collection<Layer> toDelete = super.realRemoveSingleLayer(layer);
267        if (getLayers().isEmpty()) {
268            LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, false);
269            for (LayerAvailabilityListener l : layerAvailabilityListeners) {
270                l.afterLastLayerRemoved(e);
271            }
272        }
273        return toDelete;
274    }
275
276    /**
277     * Determines the next active data layer according to the following
278     * rules:
279     * <ul>
280     *   <li>if there is at least one {@link OsmDataLayer} the first one
281     *     becomes active</li>
282     *   <li>otherwise, the top most layer of any type becomes active</li>
283     * </ul>
284     *
285     * @param except A layer to ignore.
286     * @return the next active data layer
287     */
288    private Layer suggestNextActiveLayer(Layer except) {
289        List<Layer> layersList = new ArrayList<>(getLayers());
290        layersList.remove(except);
291        // First look for data layer
292        for (Layer layer : layersList) {
293            if (layer instanceof OsmDataLayer) {
294                return layer;
295            }
296        }
297
298        // Then any layer
299        if (!layersList.isEmpty())
300            return layersList.get(0);
301
302        // and then give up
303        return null;
304    }
305
306    /**
307     * Replies the currently active layer
308     *
309     * @return the currently active layer (may be null)
310     */
311    public synchronized Layer getActiveLayer() {
312        return activeLayer;
313    }
314
315    /**
316     * Replies the current edit layer, if any
317     *
318     * @return the current edit layer. May be null.
319     */
320    public synchronized OsmDataLayer getEditLayer() {
321        return editLayer;
322    }
323
324    /**
325     * Gets the data set of the active edit layer.
326     * @return That data set, <code>null</code> if there is no edit layer.
327     */
328    public synchronized DataSet getEditDataSet() {
329        if (editLayer != null) {
330            return editLayer.data;
331        } else {
332            return null;
333        }
334    }
335
336    /**
337     * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
338     * first, layer with the highest Z-Order last.
339     * <p>
340     * The active data layer is pulled above all adjacent data layers.
341     *
342     * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
343     * first, layer with the highest Z-Order last.
344     */
345    public synchronized List<Layer> getVisibleLayersInZOrder() {
346        List<Layer> ret = new ArrayList<>();
347        // This is set while we delay the addition of the active layer.
348        boolean activeLayerDelayed = false;
349        List<Layer> layers = getLayers();
350        for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) {
351            Layer l = iterator.previous();
352            if (!l.isVisible()) {
353                // ignored
354            } else if (l == activeLayer && l instanceof OsmDataLayer) {
355                // delay and add after the current block of OsmDataLayer
356                activeLayerDelayed = true;
357            } else {
358                if (activeLayerDelayed && !(l instanceof OsmDataLayer)) {
359                    // add active layer before the current one.
360                    ret.add(activeLayer);
361                    activeLayerDelayed = false;
362                }
363                // Add this layer now
364                ret.add(l);
365            }
366        }
367        if (activeLayerDelayed) {
368            ret.add(activeLayer);
369        }
370        return ret;
371    }
372
373    @Override
374    protected synchronized void realResetState() {
375        // active and edit layer are unset automatically
376        super.realResetState();
377
378        activeLayerChangeListeners.clear();
379        layerAvailabilityListeners.clear();
380    }
381}