001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.GraphicsEnvironment;
007import java.util.ArrayList;
008import java.util.Collection;
009import java.util.List;
010import java.util.ListIterator;
011import java.util.concurrent.CopyOnWriteArrayList;
012
013import javax.swing.JOptionPane;
014
015import org.openstreetmap.josm.data.osm.DataSet;
016import org.openstreetmap.josm.data.osm.OsmData;
017import org.openstreetmap.josm.gui.MainApplication;
018import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask;
019import org.openstreetmap.josm.gui.util.GuiHelper;
020import org.openstreetmap.josm.tools.Logging;
021
022/**
023 * This class extends the layer manager by adding an active and an edit layer.
024 * <p>
025 * The active layer is the layer the user is currently working on.
026 * <p>
027 * The edit layer is an data layer that we currently work with.
028 * @author Michael Zangl
029 * @since 10279
030 */
031public class MainLayerManager extends LayerManager {
032    /**
033     * This listener listens to changes of the active or the edit layer.
034     * @author Michael Zangl
035     * @since 10600 (functional interface)
036     */
037    @FunctionalInterface
038    public interface ActiveLayerChangeListener {
039        /**
040         * Called whenever the active or edit layer changed.
041         * <p>
042         * You can be sure that this layer is still contained in this set.
043         * <p>
044         * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread.
045         * @param e The change event.
046         */
047        void activeOrEditLayerChanged(ActiveLayerChangeEvent e);
048    }
049
050    /**
051     * This event is fired whenever the active or the data layer changes.
052     * @author Michael Zangl
053     */
054    public static class ActiveLayerChangeEvent extends LayerManagerEvent {
055
056        private final OsmDataLayer previousDataLayer;
057
058        private final Layer previousActiveLayer;
059
060        /**
061         * Create a new {@link ActiveLayerChangeEvent}
062         * @param source The source
063         * @param previousDataLayer the previous data layer
064         * @param previousActiveLayer the previous active layer
065         */
066        ActiveLayerChangeEvent(MainLayerManager source, OsmDataLayer previousDataLayer,
067                Layer previousActiveLayer) {
068            super(source);
069            this.previousDataLayer = previousDataLayer;
070            this.previousActiveLayer = previousActiveLayer;
071        }
072
073        /**
074         * Gets the data layer that was previously used.
075         * @return The old data layer, <code>null</code> if there is none.
076         * @since 13434
077         */
078        public OsmDataLayer getPreviousDataLayer() {
079            return previousDataLayer;
080        }
081
082        /**
083         * Gets the active layer that was previously used.
084         * @return The old active layer, <code>null</code> if there is none.
085         */
086        public Layer getPreviousActiveLayer() {
087            return previousActiveLayer;
088        }
089
090        /**
091         * Gets the data set that was previously used.
092         * @return The data set of {@link #getPreviousDataLayer()}.
093         * @since 13434
094         */
095        public DataSet getPreviousDataSet() {
096            if (previousDataLayer != null) {
097                return previousDataLayer.getDataSet();
098            } else {
099                return null;
100            }
101        }
102
103        @Override
104        public MainLayerManager getSource() {
105            return (MainLayerManager) super.getSource();
106        }
107    }
108
109    /**
110     * This event is fired for {@link LayerAvailabilityListener}
111     * @author Michael Zangl
112     * @since 10508
113     */
114    public static class LayerAvailabilityEvent extends LayerManagerEvent {
115        private final boolean hasLayers;
116
117        LayerAvailabilityEvent(LayerManager source, boolean hasLayers) {
118            super(source);
119            this.hasLayers = hasLayers;
120        }
121
122        /**
123         * Checks if this layer manager will have layers afterwards
124         * @return true if layers will be added.
125         */
126        public boolean hasLayers() {
127            return hasLayers;
128        }
129    }
130
131    /**
132     * A listener that gets informed before any layer is displayed and after all layers are removed.
133     * @author Michael Zangl
134     * @since 10508
135     */
136    public interface LayerAvailabilityListener {
137        /**
138         * This method is called in the UI thread right before the first layer is added.
139         * @param e The event.
140         */
141        void beforeFirstLayerAdded(LayerAvailabilityEvent e);
142
143        /**
144         * This method is called in the UI thread after the last layer was removed.
145         * @param e The event.
146         */
147        void afterLastLayerRemoved(LayerAvailabilityEvent e);
148    }
149
150    /**
151     * The layer from the layers list that is currently active.
152     */
153    private Layer activeLayer;
154
155    /**
156     * The current active data layer. It might be editable or not, based on its read-only status.
157     */
158    private AbstractOsmDataLayer dataLayer;
159
160    /**
161     * The current active OSM data layer. It might be editable or not, based on its read-only status.
162     */
163    private OsmDataLayer osmDataLayer;
164
165    private final List<ActiveLayerChangeListener> activeLayerChangeListeners = new CopyOnWriteArrayList<>();
166    private final List<LayerAvailabilityListener> layerAvailabilityListeners = new CopyOnWriteArrayList<>();
167
168    /**
169     * Adds a active/edit layer change listener
170     *
171     * @param listener the listener.
172     */
173    public synchronized void addActiveLayerChangeListener(ActiveLayerChangeListener listener) {
174        if (activeLayerChangeListeners.contains(listener)) {
175            throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener);
176        }
177        activeLayerChangeListeners.add(listener);
178    }
179
180    /**
181     * Adds a active/edit layer change listener. Fire a fake active-layer-changed-event right after adding
182     * the listener. The previous layers will be null. The listener is notified in the current thread.
183     * @param listener the listener.
184     */
185    public synchronized void addAndFireActiveLayerChangeListener(ActiveLayerChangeListener listener) {
186        addActiveLayerChangeListener(listener);
187        listener.activeOrEditLayerChanged(new ActiveLayerChangeEvent(this, null, null));
188    }
189
190    /**
191     * Removes an active/edit layer change listener.
192     * @param listener the listener.
193     */
194    public synchronized void removeActiveLayerChangeListener(ActiveLayerChangeListener listener) {
195        if (!activeLayerChangeListeners.contains(listener)) {
196            throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener);
197        }
198        activeLayerChangeListeners.remove(listener);
199    }
200
201    /**
202     * Add a new {@link LayerAvailabilityListener}.
203     * @param listener The listener
204     * @since 10508
205     */
206    public synchronized void addLayerAvailabilityListener(LayerAvailabilityListener listener) {
207        if (!layerAvailabilityListeners.add(listener)) {
208            throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener);
209        }
210    }
211
212    /**
213     * Remove an {@link LayerAvailabilityListener}.
214     * @param listener The listener
215     * @since 10508
216     */
217    public synchronized void removeLayerAvailabilityListener(LayerAvailabilityListener listener) {
218        if (!layerAvailabilityListeners.remove(listener)) {
219            throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener);
220        }
221    }
222
223    /**
224     * Set the active layer, unless the layer is being uploaded.
225     * If the layer is an OsmDataLayer, the edit layer is also changed.
226     * @param layer The active layer.
227     */
228    public void setActiveLayer(final Layer layer) {
229        // we force this on to the EDT Thread to make events fire from there.
230        // The synchronization lock needs to be held by the EDT.
231        if (layer instanceof OsmDataLayer && ((OsmDataLayer) layer).isUploadInProgress()) {
232            GuiHelper.runInEDT(() ->
233                    JOptionPane.showMessageDialog(
234                            MainApplication.parent,
235                            tr("Trying to set a read only data layer as edit layer"),
236                            tr("Warning"),
237                            JOptionPane.WARNING_MESSAGE));
238        } else {
239            GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer));
240        }
241    }
242
243    protected synchronized void realSetActiveLayer(final Layer layer) {
244        // to be called in EDT thread
245        checkContainsLayer(layer);
246        setActiveLayer(layer, false);
247    }
248
249    private void setActiveLayer(Layer layer, boolean forceEditLayerUpdate) {
250        ActiveLayerChangeEvent event = new ActiveLayerChangeEvent(this, osmDataLayer, activeLayer);
251        activeLayer = layer;
252        if (activeLayer instanceof AbstractOsmDataLayer) {
253            dataLayer = (AbstractOsmDataLayer) layer;
254        }
255        if (activeLayer instanceof OsmDataLayer) {
256            osmDataLayer = (OsmDataLayer) activeLayer;
257        } else if (forceEditLayerUpdate) {
258            osmDataLayer = null;
259        }
260        fireActiveLayerChange(event);
261    }
262
263    private void fireActiveLayerChange(ActiveLayerChangeEvent event) {
264        GuiHelper.assertCallFromEdt();
265        if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousDataLayer() != osmDataLayer) {
266            for (ActiveLayerChangeListener l : activeLayerChangeListeners) {
267                l.activeOrEditLayerChanged(event);
268            }
269        }
270    }
271
272    @Override
273    protected synchronized void realAddLayer(Layer layer, boolean initialZoom) {
274        if (getLayers().isEmpty()) {
275            LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, true);
276            for (LayerAvailabilityListener l : layerAvailabilityListeners) {
277                l.beforeFirstLayerAdded(e);
278            }
279        }
280        super.realAddLayer(layer, initialZoom);
281
282        // update the active layer automatically.
283        if (layer instanceof OsmDataLayer || activeLayer == null) {
284            setActiveLayer(layer);
285        }
286    }
287
288    @Override
289    protected Collection<Layer> realRemoveSingleLayer(Layer layer) {
290        if ((layer instanceof OsmDataLayer) && (((OsmDataLayer) layer).isUploadInProgress())) {
291            GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.parent,
292                    tr("Trying to delete the layer with background upload. Please wait until the upload is finished.")));
293
294            // Return an empty collection for allowing to delete other layers
295            return new ArrayList<>();
296        }
297
298        if (layer == activeLayer || layer == osmDataLayer) {
299            Layer nextActive = suggestNextActiveLayer(layer);
300            setActiveLayer(nextActive, true);
301        }
302
303        Collection<Layer> toDelete = super.realRemoveSingleLayer(layer);
304        if (getLayers().isEmpty()) {
305            LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, false);
306            for (LayerAvailabilityListener l : layerAvailabilityListeners) {
307                l.afterLastLayerRemoved(e);
308            }
309        }
310        return toDelete;
311    }
312
313    /**
314     * Determines the next active data layer according to the following
315     * rules:
316     * <ul>
317     *   <li>if there is at least one {@link OsmDataLayer} the first one
318     *     becomes active</li>
319     *   <li>otherwise, the top most layer of any type becomes active</li>
320     * </ul>
321     *
322     * @param except A layer to ignore.
323     * @return the next active data layer
324     */
325    private Layer suggestNextActiveLayer(Layer except) {
326        List<Layer> layersList = new ArrayList<>(getLayers());
327        layersList.remove(except);
328        // First look for data layer
329        for (Layer layer : layersList) {
330            if (layer instanceof OsmDataLayer) {
331                return layer;
332            }
333        }
334
335        // Then any layer
336        if (!layersList.isEmpty())
337            return layersList.get(0);
338
339        // and then give up
340        return null;
341    }
342
343    /**
344     * Replies the currently active layer
345     *
346     * @return the currently active layer (may be null)
347     */
348    public synchronized Layer getActiveLayer() {
349        if (activeLayer instanceof OsmDataLayer) {
350            if (!((OsmDataLayer) activeLayer).isUploadInProgress()) {
351                return activeLayer;
352            } else {
353                return null;
354            }
355        } else {
356            return activeLayer;
357        }
358    }
359
360    /**
361     * Replies the current edit layer, if present and not readOnly
362     *
363     * @return the current edit layer. May be null.
364     * @see #getActiveDataLayer
365     */
366    public synchronized OsmDataLayer getEditLayer() {
367        if (osmDataLayer != null && !osmDataLayer.isLocked())
368            return osmDataLayer;
369        else
370            return null;
371    }
372
373    /**
374     * Replies the active data layer. The layer can be read-only.
375     *
376     * @return the current data layer. May be null or read-only.
377     * @see #getEditLayer
378     * @since 13434
379     */
380    public synchronized OsmDataLayer getActiveDataLayer() {
381        if (osmDataLayer != null)
382            return osmDataLayer;
383        else
384            return null;
385    }
386
387    /**
388     * Gets the data set of the active edit layer, if not readOnly.
389     * @return That data set, <code>null</code> if there is no edit layer.
390     * @see #getActiveDataSet
391     */
392    public synchronized DataSet getEditDataSet() {
393        if (osmDataLayer != null && !osmDataLayer.isLocked()) {
394            return osmDataLayer.getDataSet();
395        } else {
396            return null;
397        }
398    }
399
400    /**
401     * Gets the data set of the active data layer. The dataset can be read-only.
402     * @return That data set, <code>null</code> if there is no active data layer.
403     * @since 13926
404     */
405    public synchronized OsmData<?, ?, ?, ?> getActiveData() {
406        if (dataLayer != null) {
407            return dataLayer.getDataSet();
408        } else {
409            return null;
410        }
411    }
412
413    /**
414     * Gets the data set of the active {@link OsmDataLayer}. The dataset can be read-only.
415     * @return That data set, <code>null</code> if there is no active data layer.
416     * @see #getEditDataSet
417     * @since 13434
418     */
419    public synchronized DataSet getActiveDataSet() {
420        if (osmDataLayer != null) {
421            return osmDataLayer.getDataSet();
422        } else {
423            return null;
424        }
425    }
426
427    /**
428     * Returns the unique note layer, if present.
429     * @return the unique note layer, or null
430     * @since 13437
431     */
432    public NoteLayer getNoteLayer() {
433        List<NoteLayer> col = getLayersOfType(NoteLayer.class);
434        return col.isEmpty() ? null : col.get(0);
435    }
436
437    /**
438     * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
439     * first, layer with the highest Z-Order last.
440     * <p>
441     * The active data layer is pulled above all adjacent data layers.
442     *
443     * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
444     * first, layer with the highest Z-Order last.
445     */
446    public synchronized List<Layer> getVisibleLayersInZOrder() {
447        List<Layer> ret = new ArrayList<>();
448        // This is set while we delay the addition of the active layer.
449        boolean activeLayerDelayed = false;
450        List<Layer> layers = getLayers();
451        for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) {
452            Layer l = iterator.previous();
453            if (!l.isVisible()) {
454                // ignored
455            } else if (l == activeLayer && l instanceof OsmDataLayer) {
456                // delay and add after the current block of OsmDataLayer
457                activeLayerDelayed = true;
458            } else {
459                if (activeLayerDelayed && !(l instanceof OsmDataLayer)) {
460                    // add active layer before the current one.
461                    ret.add(activeLayer);
462                    activeLayerDelayed = false;
463                }
464                // Add this layer now
465                ret.add(l);
466            }
467        }
468        if (activeLayerDelayed) {
469            ret.add(activeLayer);
470        }
471        return ret;
472    }
473
474    /**
475     * Invalidates current edit layer, if any. Does nothing if there is no edit layer.
476     * @since 13150
477     */
478    public void invalidateEditLayer() {
479        if (osmDataLayer != null) {
480            osmDataLayer.invalidate();
481        }
482    }
483
484    @Override
485    protected synchronized void realResetState() {
486        // Reset state if no asynchronous upload is under progress
487        if (!AsynchronousUploadPrimitivesTask.getCurrentAsynchronousUploadTask().isPresent()) {
488            // active and edit layer are unset automatically
489            super.realResetState();
490
491            activeLayerChangeListeners.clear();
492            layerAvailabilityListeners.clear();
493        } else {
494            String msg = tr("A background upload is already in progress. Cannot reset state until the upload is finished.");
495            Logging.warn(msg);
496            if (!GraphicsEnvironment.isHeadless()) {
497                GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.parent, msg));
498            }
499        }
500    }
501
502    /**
503     * Prepares an OsmDataLayer for upload. The layer to be uploaded is locked and
504     * if the layer to be uploaded is the current editLayer then editLayer is reset
505     * to null for disallowing any changes to the layer. An ActiveLayerChangeEvent
506     * is fired to notify the listeners
507     *
508     * @param layer The OsmDataLayer to be uploaded
509     */
510    public synchronized void prepareLayerForUpload(OsmDataLayer layer) {
511        GuiHelper.assertCallFromEdt();
512        layer.setUploadInProgress();
513        layer.lock();
514
515        // Reset only the edit layer as empty
516        if (osmDataLayer == layer) {
517            ActiveLayerChangeEvent activeLayerChangeEvent = new ActiveLayerChangeEvent(this, osmDataLayer, activeLayer);
518            osmDataLayer = null;
519            fireActiveLayerChange(activeLayerChangeEvent);
520        }
521    }
522
523    /**
524     * Post upload processing of the OsmDataLayer.
525     * If the current edit layer is empty this function sets the layer uploaded as the
526     * current editLayer. An ActiveLayerChangeEvent is fired to notify the listeners
527     *
528     * @param layer The OsmDataLayer uploaded
529     */
530    public synchronized void processLayerAfterUpload(OsmDataLayer layer) {
531        GuiHelper.assertCallFromEdt();
532        layer.unlock();
533        layer.unsetUploadInProgress();
534
535        // Set the layer as edit layer if the edit layer is empty.
536        if (osmDataLayer == null) {
537            ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, osmDataLayer, activeLayer);
538            osmDataLayer = layer;
539            fireActiveLayerChange(layerChangeEvent);
540        }
541    }
542}