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