001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer;
003
004import java.util.ArrayList;
005import java.util.List;
006import java.util.ListIterator;
007import java.util.concurrent.CopyOnWriteArrayList;
008
009import org.openstreetmap.josm.data.osm.DataSet;
010import org.openstreetmap.josm.gui.util.GuiHelper;
011
012/**
013 * This class extends the layer manager by adding an active and an edit layer.
014 * <p>
015 * The active layer is the layer the user is currently working on.
016 * <p>
017 * The edit layer is an data layer that we currently work with.
018 * @author Michael Zangl
019 * @since 10279
020 */
021public class MainLayerManager extends LayerManager {
022    /**
023     * This listener listens to changes of the active or the edit layer.
024     * @author Michael Zangl
025     *
026     */
027    public interface ActiveLayerChangeListener {
028        /**
029         * Called whenever the active or edit layer changed.
030         * <p>
031         * You can be sure that this layer is still contained in this set.
032         * <p>
033         * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread.
034         * @param e The change event.
035         */
036        void activeOrEditLayerChanged(ActiveLayerChangeEvent e);
037    }
038
039    /**
040     * This event is fired whenever the active or the edit layer changes.
041     * @author Michael Zangl
042     */
043    public class ActiveLayerChangeEvent extends LayerManagerEvent {
044
045        private final OsmDataLayer previousEditLayer;
046
047        private final Layer previousActiveLayer;
048
049        /**
050         * Create a new {@link ActiveLayerChangeEvent}
051         * @param source The source
052         * @param previousEditLayer the previous edit layer
053         * @param previousActiveLayer the previous active layer
054         */
055        ActiveLayerChangeEvent(MainLayerManager source, OsmDataLayer previousEditLayer,
056                Layer previousActiveLayer) {
057            super(source);
058            this.previousEditLayer = previousEditLayer;
059            this.previousActiveLayer = previousActiveLayer;
060        }
061
062        /**
063         * Gets the edit layer that was previously used.
064         * @return The old edit layer, <code>null</code> if there is none.
065         */
066        public OsmDataLayer getPreviousEditLayer() {
067            return previousEditLayer;
068        }
069
070        /**
071         * Gets the active layer that was previously used.
072         * @return The old active layer, <code>null</code> if there is none.
073         */
074        public Layer getPreviousActiveLayer() {
075            return previousActiveLayer;
076        }
077
078        @Override
079        public MainLayerManager getSource() {
080            return (MainLayerManager) super.getSource();
081        }
082    }
083
084    /**
085     * The layer from the layers list that is currently active.
086     */
087    private Layer activeLayer;
088
089    /**
090     * The edit layer is the current active data layer.
091     */
092    private OsmDataLayer editLayer;
093
094    private final List<ActiveLayerChangeListener> activeLayerChangeListeners = new CopyOnWriteArrayList<>();
095
096    /**
097     * Adds a active/edit layer change listener
098     *
099     * @param listener the listener.
100     * @param initialFire fire a fake active-layer-changed-event right after adding
101     * the listener. The previous layers will be null. The listener is notified in the current thread.
102     */
103    public synchronized void addActiveLayerChangeListener(ActiveLayerChangeListener listener, boolean initialFire) {
104        if (activeLayerChangeListeners.contains(listener)) {
105            throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener);
106        }
107        activeLayerChangeListeners.add(listener);
108        if (initialFire) {
109            listener.activeOrEditLayerChanged(new ActiveLayerChangeEvent(this, null, null));
110        }
111    }
112
113    /**
114     * Removes an active/edit layer change listener.
115     * @param listener the listener.
116     */
117    public synchronized void removeActiveLayerChangeListener(ActiveLayerChangeListener listener) {
118        if (!activeLayerChangeListeners.contains(listener)) {
119            throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener);
120        }
121        activeLayerChangeListeners.remove(listener);
122    }
123
124    /**
125     * Set the active layer. If the layer is an OsmDataLayer, the edit layer is also changed.
126     * @param layer The active layer.
127     */
128    public void setActiveLayer(final Layer layer) {
129        // we force this on to the EDT Thread to make events fire from there.
130        // The synchronization lock needs to be held by the EDT.
131        GuiHelper.runInEDTAndWaitWithException(new Runnable() {
132            @Override
133            public void run() {
134                realSetActiveLayer(layer);
135            }
136        });
137    }
138
139    protected synchronized void realSetActiveLayer(final Layer layer) {
140        // to be called in EDT thread
141        checkContainsLayer(layer);
142        setActiveLayer(layer, false);
143    }
144
145    private void setActiveLayer(Layer layer, boolean forceEditLayerUpdate) {
146        ActiveLayerChangeEvent event = new ActiveLayerChangeEvent(this, editLayer, activeLayer);
147        activeLayer = layer;
148        if (activeLayer instanceof OsmDataLayer) {
149            editLayer = (OsmDataLayer) activeLayer;
150        } else if (forceEditLayerUpdate) {
151            editLayer = null;
152        }
153        fireActiveLayerChange(event);
154    }
155
156    private void fireActiveLayerChange(ActiveLayerChangeEvent event) {
157        GuiHelper.assertCallFromEdt();
158        if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousEditLayer() != editLayer) {
159            for (ActiveLayerChangeListener l : activeLayerChangeListeners) {
160                l.activeOrEditLayerChanged(event);
161            }
162        }
163    }
164
165    @Override
166    protected synchronized void realAddLayer(Layer layer) {
167        super.realAddLayer(layer);
168
169        // update the active layer automatically.
170        if (layer instanceof OsmDataLayer || activeLayer == null) {
171            setActiveLayer(layer);
172        }
173    }
174
175    @Override
176    protected synchronized void realRemoveLayer(Layer layer) {
177        if (layer == activeLayer || layer == editLayer) {
178            Layer nextActive = suggestNextActiveLayer(layer);
179            setActiveLayer(nextActive, true);
180        }
181
182        super.realRemoveLayer(layer);
183    }
184
185    /**
186     * Determines the next active data layer according to the following
187     * rules:
188     * <ul>
189     *   <li>if there is at least one {@link OsmDataLayer} the first one
190     *     becomes active</li>
191     *   <li>otherwise, the top most layer of any type becomes active</li>
192     * </ul>
193     *
194     * @param except A layer to ignore.
195     * @return the next active data layer
196     */
197    private Layer suggestNextActiveLayer(Layer except) {
198        List<Layer> layersList = new ArrayList<>(getLayers());
199        layersList.remove(except);
200        // First look for data layer
201        for (Layer layer : layersList) {
202            if (layer instanceof OsmDataLayer) {
203                return layer;
204            }
205        }
206
207        // Then any layer
208        if (!layersList.isEmpty())
209            return layersList.get(0);
210
211        // and then give up
212        return null;
213    }
214
215    /**
216     * Replies the currently active layer
217     *
218     * @return the currently active layer (may be null)
219     */
220    public synchronized Layer getActiveLayer() {
221        return activeLayer;
222    }
223
224    /**
225     * Replies the current edit layer, if any
226     *
227     * @return the current edit layer. May be null.
228     */
229    public synchronized OsmDataLayer getEditLayer() {
230        return editLayer;
231    }
232
233    /**
234     * Gets the data set of the active edit layer.
235     * @return That data set, <code>null</code> if there is no edit layer.
236     */
237    public synchronized DataSet getEditDataSet() {
238        if (editLayer != null) {
239            return editLayer.data;
240        } else {
241            return null;
242        }
243    }
244
245
246    /**
247     * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
248     * first, layer with the highest Z-Order last.
249     * <p>
250     * The active data layer is pulled above all adjacent data layers.
251     *
252     * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
253     * first, layer with the highest Z-Order last.
254     */
255    public synchronized List<Layer> getVisibleLayersInZOrder() {
256        List<Layer> ret = new ArrayList<>();
257        // This is set while we delay the addition of the active layer.
258        boolean activeLayerDelayed = false;
259        List<Layer> layers = getLayers();
260        for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) {
261            Layer l = iterator.previous();
262            if (!l.isVisible()) {
263                // ignored
264            } else if (l == activeLayer && l instanceof OsmDataLayer) {
265                // delay and add after the current block of OsmDataLayer
266                activeLayerDelayed = true;
267            } else {
268                if (activeLayerDelayed && !(l instanceof OsmDataLayer)) {
269                    // add active layer before the current one.
270                    ret.add(activeLayer);
271                    activeLayerDelayed = false;
272                }
273                // Add this layer now
274                ret.add(l);
275            }
276        }
277        if (activeLayerDelayed) {
278            ret.add(activeLayer);
279        }
280        return ret;
281    }
282}