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.getMainFrame(),
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) activeLayer;
254        } else if (forceEditLayerUpdate) {
255            dataLayer = null;
256        }
257        if (activeLayer instanceof OsmDataLayer) {
258            osmDataLayer = (OsmDataLayer) activeLayer;
259        } else if (forceEditLayerUpdate) {
260            osmDataLayer = null;
261        }
262        fireActiveLayerChange(event);
263    }
264
265    private void fireActiveLayerChange(ActiveLayerChangeEvent event) {
266        GuiHelper.assertCallFromEdt();
267        if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousDataLayer() != osmDataLayer) {
268            for (ActiveLayerChangeListener l : activeLayerChangeListeners) {
269                l.activeOrEditLayerChanged(event);
270            }
271        }
272    }
273
274    @Override
275    protected synchronized void realAddLayer(Layer layer, boolean initialZoom) {
276        if (getLayers().isEmpty()) {
277            LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, true);
278            for (LayerAvailabilityListener l : layerAvailabilityListeners) {
279                l.beforeFirstLayerAdded(e);
280            }
281        }
282        super.realAddLayer(layer, initialZoom);
283
284        // update the active layer automatically.
285        if (layer instanceof OsmDataLayer || activeLayer == null) {
286            setActiveLayer(layer);
287        }
288    }
289
290    @Override
291    protected Collection<Layer> realRemoveSingleLayer(Layer layer) {
292        if ((layer instanceof OsmDataLayer) && (((OsmDataLayer) layer).isUploadInProgress())) {
293            GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
294                    tr("Trying to delete the layer with background upload. Please wait until the upload is finished.")));
295
296            // Return an empty collection for allowing to delete other layers
297            return new ArrayList<>();
298        }
299
300        if (layer == activeLayer || layer == osmDataLayer) {
301            Layer nextActive = suggestNextActiveLayer(layer);
302            setActiveLayer(nextActive, true);
303        }
304
305        Collection<Layer> toDelete = super.realRemoveSingleLayer(layer);
306        if (getLayers().isEmpty()) {
307            LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, false);
308            for (LayerAvailabilityListener l : layerAvailabilityListeners) {
309                l.afterLastLayerRemoved(e);
310            }
311        }
312        return toDelete;
313    }
314
315    /**
316     * Determines the next active data layer according to the following
317     * rules:
318     * <ul>
319     *   <li>if there is at least one {@link OsmDataLayer} the first one
320     *     becomes active</li>
321     *   <li>otherwise, the top most layer of any type becomes active</li>
322     * </ul>
323     *
324     * @param except A layer to ignore.
325     * @return the next active data layer
326     */
327    private Layer suggestNextActiveLayer(Layer except) {
328        List<Layer> layersList = new ArrayList<>(getLayers());
329        layersList.remove(except);
330        // First look for data layer
331        for (Layer layer : layersList) {
332            if (layer instanceof OsmDataLayer) {
333                return layer;
334            }
335        }
336
337        // Then any layer
338        if (!layersList.isEmpty())
339            return layersList.get(0);
340
341        // and then give up
342        return null;
343    }
344
345    /**
346     * Replies the currently active layer
347     *
348     * @return the currently active layer (may be null)
349     */
350    public synchronized Layer getActiveLayer() {
351        if (activeLayer instanceof OsmDataLayer) {
352            if (!((OsmDataLayer) activeLayer).isUploadInProgress()) {
353                return activeLayer;
354            } else {
355                return null;
356            }
357        } else {
358            return activeLayer;
359        }
360    }
361
362    /**
363     * Replies the current edit layer, if present and not readOnly
364     *
365     * @return the current edit layer. May be null.
366     * @see #getActiveDataLayer
367     */
368    public synchronized OsmDataLayer getEditLayer() {
369        if (osmDataLayer != null && !osmDataLayer.isLocked())
370            return osmDataLayer;
371        else
372            return null;
373    }
374
375    /**
376     * Replies the active data layer. The layer can be read-only.
377     *
378     * @return the current data layer. May be null or read-only.
379     * @see #getEditLayer
380     * @since 13434
381     */
382    public synchronized OsmDataLayer getActiveDataLayer() {
383        if (osmDataLayer != null)
384            return osmDataLayer;
385        else
386            return null;
387    }
388
389    /**
390     * Gets the data set of the active edit layer, if not readOnly.
391     * @return That data set, <code>null</code> if there is no edit layer.
392     * @see #getActiveDataSet
393     */
394    public synchronized DataSet getEditDataSet() {
395        if (osmDataLayer != null && !osmDataLayer.isLocked()) {
396            return osmDataLayer.getDataSet();
397        } else {
398            return null;
399        }
400    }
401
402    /**
403     * Gets the data set of the active data layer. The dataset can be read-only.
404     * @return That data set, <code>null</code> if there is no active data layer.
405     * @since 13926
406     */
407    public synchronized OsmData<?, ?, ?, ?> getActiveData() {
408        if (dataLayer != null) {
409            return dataLayer.getDataSet();
410        } else {
411            return null;
412        }
413    }
414
415    /**
416     * Gets the data set of the active {@link OsmDataLayer}. The dataset can be read-only.
417     * @return That data set, <code>null</code> if there is no active data layer.
418     * @see #getEditDataSet
419     * @since 13434
420     */
421    public synchronized DataSet getActiveDataSet() {
422        if (osmDataLayer != null) {
423            return osmDataLayer.getDataSet();
424        } else {
425            return null;
426        }
427    }
428
429    /**
430     * Returns the unique note layer, if present.
431     * @return the unique note layer, or null
432     * @since 13437
433     */
434    public NoteLayer getNoteLayer() {
435        List<NoteLayer> col = getLayersOfType(NoteLayer.class);
436        return col.isEmpty() ? null : col.get(0);
437    }
438
439    /**
440     * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
441     * first, layer with the highest Z-Order last.
442     * <p>
443     * The active data layer is pulled above all adjacent data layers.
444     *
445     * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
446     * first, layer with the highest Z-Order last.
447     */
448    public synchronized List<Layer> getVisibleLayersInZOrder() {
449        List<Layer> ret = new ArrayList<>();
450        // This is set while we delay the addition of the active layer.
451        boolean activeLayerDelayed = false;
452        List<Layer> layers = getLayers();
453        for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) {
454            Layer l = iterator.previous();
455            if (!l.isVisible()) {
456                // ignored
457            } else if (l == activeLayer && l instanceof OsmDataLayer) {
458                // delay and add after the current block of OsmDataLayer
459                activeLayerDelayed = true;
460            } else {
461                if (activeLayerDelayed && !(l instanceof OsmDataLayer)) {
462                    // add active layer before the current one.
463                    ret.add(activeLayer);
464                    activeLayerDelayed = false;
465                }
466                // Add this layer now
467                ret.add(l);
468            }
469        }
470        if (activeLayerDelayed) {
471            ret.add(activeLayer);
472        }
473        return ret;
474    }
475
476    /**
477     * Invalidates current edit layer, if any. Does nothing if there is no edit layer.
478     * @since 13150
479     */
480    public void invalidateEditLayer() {
481        if (osmDataLayer != null) {
482            osmDataLayer.invalidate();
483        }
484    }
485
486    @Override
487    protected synchronized void realResetState() {
488        // Reset state if no asynchronous upload is under progress
489        if (!AsynchronousUploadPrimitivesTask.getCurrentAsynchronousUploadTask().isPresent()) {
490            // active and edit layer are unset automatically
491            super.realResetState();
492
493            activeLayerChangeListeners.clear();
494            layerAvailabilityListeners.clear();
495        } else {
496            String msg = tr("A background upload is already in progress. Cannot reset state until the upload is finished.");
497            Logging.warn(msg);
498            if (!GraphicsEnvironment.isHeadless()) {
499                GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.getMainFrame(), msg));
500            }
501        }
502    }
503
504    /**
505     * Prepares an OsmDataLayer for upload. The layer to be uploaded is locked and
506     * if the layer to be uploaded is the current editLayer then editLayer is reset
507     * to null for disallowing any changes to the layer. An ActiveLayerChangeEvent
508     * is fired to notify the listeners
509     *
510     * @param layer The OsmDataLayer to be uploaded
511     */
512    public synchronized void prepareLayerForUpload(OsmDataLayer layer) {
513        GuiHelper.assertCallFromEdt();
514        layer.setUploadInProgress();
515        layer.lock();
516
517        // Reset only the edit layer as empty
518        if (osmDataLayer == layer) {
519            ActiveLayerChangeEvent activeLayerChangeEvent = new ActiveLayerChangeEvent(this, osmDataLayer, activeLayer);
520            osmDataLayer = null;
521            fireActiveLayerChange(activeLayerChangeEvent);
522        }
523    }
524
525    /**
526     * Post upload processing of the OsmDataLayer.
527     * If the current edit layer is empty this function sets the layer uploaded as the
528     * current editLayer. An ActiveLayerChangeEvent is fired to notify the listeners
529     *
530     * @param layer The OsmDataLayer uploaded
531     */
532    public synchronized void processLayerAfterUpload(OsmDataLayer layer) {
533        GuiHelper.assertCallFromEdt();
534        layer.unlock();
535        layer.unsetUploadInProgress();
536
537        // Set the layer as edit layer if the edit layer is empty.
538        if (osmDataLayer == null) {
539            ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, osmDataLayer, activeLayer);
540            osmDataLayer = layer;
541            fireActiveLayerChange(layerChangeEvent);
542        }
543    }
544}