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