source: josm/trunk/src/org/openstreetmap/josm/gui/layer/MainLayerManager.java

Last change on this file was 19519, checked in by stoecker, 8 weeks ago

unify eol-style

  • Property svn:eol-style set to native
File size: 22.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.GraphicsEnvironment;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.List;
11import java.util.ListIterator;
12import java.util.concurrent.CopyOnWriteArrayList;
13
14import javax.swing.JOptionPane;
15
16import org.openstreetmap.josm.data.gpx.GpxData;
17import org.openstreetmap.josm.data.osm.DataSet;
18import org.openstreetmap.josm.data.osm.OsmData;
19import org.openstreetmap.josm.gui.MainApplication;
20import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask;
21import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
22import org.openstreetmap.josm.gui.util.GuiHelper;
23import org.openstreetmap.josm.tools.Logging;
24
25/**
26 * This class extends the layer manager by adding an active and an edit layer.
27 * <p>
28 * The active layer is the layer the user is currently working on.
29 * <p>
30 * The edit layer is a data layer that we currently work with.
31 * @author Michael Zangl
32 * @since 10279
33 */
34public class MainLayerManager extends LayerManager {
35 /**
36 * This listener listens to changes of the active or the edit layer.
37 * @author Michael Zangl
38 * @since 10600 (functional interface)
39 */
40 @FunctionalInterface
41 public interface ActiveLayerChangeListener {
42 /**
43 * Called whenever the active or edit layer changed.
44 * <p>
45 * You can be sure that this layer is still contained in this set.
46 * <p>
47 * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread.
48 * @param e The change event.
49 */
50 void activeOrEditLayerChanged(ActiveLayerChangeEvent e);
51 }
52
53 /**
54 * This event is fired whenever the active or the data layer changes.
55 * @author Michael Zangl
56 */
57 public static class ActiveLayerChangeEvent extends LayerManagerEvent {
58
59 private final OsmDataLayer previousDataLayer;
60
61 private final Layer previousActiveLayer;
62
63 /**
64 * Create a new {@link ActiveLayerChangeEvent}
65 * @param source The source
66 * @param previousDataLayer the previous data layer
67 * @param previousActiveLayer the previous active layer
68 */
69 ActiveLayerChangeEvent(MainLayerManager source, OsmDataLayer previousDataLayer,
70 Layer previousActiveLayer) {
71 super(source);
72 this.previousDataLayer = previousDataLayer;
73 this.previousActiveLayer = previousActiveLayer;
74 }
75
76 /**
77 * Gets the data layer that was previously used.
78 * @return The old data layer, <code>null</code> if there is none.
79 * @since 13434
80 */
81 public OsmDataLayer getPreviousDataLayer() {
82 return previousDataLayer;
83 }
84
85 /**
86 * Gets the active layer that was previously used.
87 * @return The old active layer, <code>null</code> if there is none.
88 */
89 public Layer getPreviousActiveLayer() {
90 return previousActiveLayer;
91 }
92
93 /**
94 * Gets the data set that was previously used.
95 * @return The data set of {@link #getPreviousDataLayer()}.
96 * @since 13434
97 */
98 public DataSet getPreviousDataSet() {
99 if (previousDataLayer != null) {
100 return previousDataLayer.getDataSet();
101 } else {
102 return null;
103 }
104 }
105
106 @Override
107 public MainLayerManager getSource() {
108 return (MainLayerManager) super.getSource();
109 }
110 }
111
112 /**
113 * This event is fired for {@link LayerAvailabilityListener}
114 * @author Michael Zangl
115 * @since 10508
116 */
117 public static class LayerAvailabilityEvent extends LayerManagerEvent {
118 private final boolean hasLayers;
119
120 LayerAvailabilityEvent(LayerManager source, boolean hasLayers) {
121 super(source);
122 this.hasLayers = hasLayers;
123 }
124
125 /**
126 * Checks if this layer manager will have layers afterwards
127 * @return true if layers will be added.
128 */
129 public boolean hasLayers() {
130 return hasLayers;
131 }
132 }
133
134 /**
135 * A listener that gets informed before any layer is displayed and after all layers are removed.
136 * @author Michael Zangl
137 * @since 10508
138 */
139 public interface LayerAvailabilityListener {
140 /**
141 * This method is called in the UI thread right before the first layer is added.
142 * @param e The event.
143 */
144 void beforeFirstLayerAdded(LayerAvailabilityEvent e);
145
146 /**
147 * This method is called in the UI thread after the last layer was removed.
148 * @param e The event.
149 */
150 void afterLastLayerRemoved(LayerAvailabilityEvent e);
151 }
152
153 /**
154 * The layer from the layers list that is currently active.
155 */
156 private Layer activeLayer;
157
158 /**
159 * The current active data layer. It might be editable or not, based on its read-only status.
160 */
161 private AbstractOsmDataLayer dataLayer;
162
163 /**
164 * The current active OSM data layer. It might be editable or not, based on its read-only status.
165 */
166 private OsmDataLayer osmDataLayer;
167
168 private final List<ActiveLayerChangeListener> activeLayerChangeListeners = new CopyOnWriteArrayList<>();
169 private final List<LayerAvailabilityListener> layerAvailabilityListeners = new CopyOnWriteArrayList<>();
170
171 /**
172 * Adds an active/edit layer change listener
173 *
174 * @param listener the listener.
175 */
176 public synchronized void addActiveLayerChangeListener(ActiveLayerChangeListener listener) {
177 for (ActiveLayerChangeListener activeLayerChangeListener : activeLayerChangeListeners) {
178 if (activeLayerChangeListener == listener) {
179 Logging.error("");
180 Logging.error("Attempted to add listener that was already in list: " + listener);
181 showStackTrace(Thread.currentThread().getStackTrace());
182 return;
183 }
184 }
185 activeLayerChangeListeners.add(listener);
186 }
187
188 /**
189 * Adds multiple active/edit layer change listeners. Either all listeners are added or none are added.
190 *
191 * @param listeners the listeners.
192 * @return {@code false} if the listener list did not change
193 * @since 18691
194 */
195 public synchronized boolean addActiveLayerChangeListeners(Collection<? extends ActiveLayerChangeListener> listeners) {
196 for (ActiveLayerChangeListener activeLayerChangeListener : activeLayerChangeListeners) {
197 if (listeners.contains(activeLayerChangeListener)) {
198 Logging.error("");
199 Logging.error("Attempted to add listener that was already in list: " + listeners);
200 showStackTrace(Thread.currentThread().getStackTrace());
201 return false;
202 }
203 }
204 return activeLayerChangeListeners.addAll(listeners);
205 }
206
207 private static void showStackTrace(StackTraceElement[] stackTrace) {
208 for (StackTraceElement st : stackTrace) {
209 Logging.error("\tat " + st);
210 }
211 }
212
213 /**
214 * Adds an active/edit layer change listener. Fire a fake active-layer-changed-event right after adding
215 * the listener. The previous layers will be null. The listener is notified in the current thread.
216 * @param listener the listener.
217 */
218 public synchronized void addAndFireActiveLayerChangeListener(ActiveLayerChangeListener listener) {
219 addActiveLayerChangeListener(listener);
220 listener.activeOrEditLayerChanged(new ActiveLayerChangeEvent(this, null, null));
221 }
222
223 /**
224 * Removes an active/edit layer change listener.
225 * @param listener the listener.
226 */
227 public synchronized void removeActiveLayerChangeListener(ActiveLayerChangeListener listener) {
228 int old = -1;
229 for (int i = 0; i < activeLayerChangeListeners.size(); i++) {
230 if (activeLayerChangeListeners.get(i) == listener) {
231 old = i;
232 break;
233 }
234 }
235 if (old < 0) {
236 Logging.error("");
237 Logging.error("Attempted to remove listener that was not in list: " + listener);
238 showStackTrace(Thread.currentThread().getStackTrace());
239 return;
240 }
241 activeLayerChangeListeners.remove(old);
242 }
243
244 /**
245 * Add a new {@link LayerAvailabilityListener}.
246 * @param listener The listener
247 * @since 10508
248 */
249 public synchronized void addLayerAvailabilityListener(LayerAvailabilityListener listener) {
250 layerAvailabilityListeners.add(listener);
251 }
252
253 /**
254 * Remove an {@link LayerAvailabilityListener}.
255 * @param listener The listener
256 * @since 10508
257 */
258 public synchronized void removeLayerAvailabilityListener(LayerAvailabilityListener listener) {
259 if (!layerAvailabilityListeners.remove(listener)) {
260 throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener);
261 }
262 }
263
264 /**
265 * Set the active layer, unless the layer is being uploaded.
266 * If the layer is an OsmDataLayer, the edit layer is also changed.
267 * @param layer The active layer.
268 */
269 public void setActiveLayer(final Layer layer) {
270 // we force this on to the EDT Thread to make events fire from there.
271 // The synchronization lock needs to be held by the EDT.
272 if (layer instanceof OsmDataLayer && ((OsmDataLayer) layer).isUploadInProgress()) {
273 GuiHelper.runInEDT(() ->
274 JOptionPane.showMessageDialog(
275 MainApplication.getMainFrame(),
276 tr("Trying to set a read only data layer as edit layer"),
277 tr("Warning"),
278 JOptionPane.WARNING_MESSAGE));
279 } else {
280 GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer));
281 }
282 }
283
284 protected synchronized void realSetActiveLayer(final Layer layer) {
285 // to be called in EDT thread
286 checkContainsLayer(layer);
287 setActiveLayer(layer, false);
288 }
289
290 private void setActiveLayer(Layer layer, boolean forceEditLayerUpdate) {
291 ActiveLayerChangeEvent event = new ActiveLayerChangeEvent(this, osmDataLayer, activeLayer);
292 activeLayer = layer;
293 if (activeLayer instanceof AbstractOsmDataLayer) {
294 dataLayer = (AbstractOsmDataLayer) activeLayer;
295 } else if (forceEditLayerUpdate) {
296 dataLayer = null;
297 }
298 if (activeLayer instanceof OsmDataLayer) {
299 osmDataLayer = (OsmDataLayer) activeLayer;
300 } else if (forceEditLayerUpdate) {
301 osmDataLayer = null;
302 }
303 fireActiveLayerChange(event);
304 }
305
306 private void fireActiveLayerChange(ActiveLayerChangeEvent event) {
307 GuiHelper.assertCallFromEdt();
308 if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousDataLayer() != osmDataLayer) {
309 for (ActiveLayerChangeListener l : activeLayerChangeListeners) {
310 try {
311 l.activeOrEditLayerChanged(event);
312 } catch (RuntimeException e) {
313 Logging.logWithStackTrace(Logging.LEVEL_ERROR, "Error in layer change listener", e);
314 }
315 }
316 }
317 }
318
319 @Override
320 protected synchronized void realAddLayer(Layer layer, boolean initialZoom) {
321 if (getLayers().isEmpty()) {
322 LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, true);
323 for (LayerAvailabilityListener l : layerAvailabilityListeners) {
324 l.beforeFirstLayerAdded(e);
325 }
326 }
327 super.realAddLayer(layer, initialZoom);
328
329 // update the active layer automatically.
330 if (layer instanceof OsmDataLayer || activeLayer == null) {
331 setActiveLayer(layer);
332 }
333 }
334
335 @Override
336 protected Collection<Layer> realRemoveSingleLayer(Layer layer) {
337 if (layer instanceof OsmDataLayer && ((OsmDataLayer) layer).isUploadInProgress()) {
338 GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
339 tr("Trying to delete the layer with background upload. Please wait until the upload is finished.")));
340
341 // Return an empty collection for allowing to delete other layers
342 return new ArrayList<>();
343 }
344
345 if (layer == activeLayer || layer == osmDataLayer) {
346 Layer nextActive = suggestNextActiveLayer(layer);
347 setActiveLayer(nextActive, true);
348 }
349
350 Collection<Layer> toDelete = super.realRemoveSingleLayer(layer);
351 if (getLayers().isEmpty()) {
352 LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, false);
353 for (LayerAvailabilityListener l : layerAvailabilityListeners) {
354 l.afterLastLayerRemoved(e);
355 }
356 }
357 return toDelete;
358 }
359
360 /**
361 * Determines the next active data layer.
362 * <p>
363 * The layer becomes active, which has the next highest index (closer to bottom) relative to {@code except} parameter
364 * in the following order:
365 * <ol>
366 * <li>{@link OsmDataLayer} and visible, or if there is none</li>
367 * <li>{@link OsmDataLayer} and hidden, or if there is none</li>
368 * <li>any type</li>
369 * </ol>
370 *
371 * @param except A layer to ignore.
372 * @return the next active data layer
373 */
374 private Layer suggestNextActiveLayer(Layer except) {
375 List<Layer> layersList = new ArrayList<>(getLayers());
376
377 // construct a new list with decreasing priority
378 int indexOfExcept = layersList.indexOf(except);
379 List<Layer> remainingLayers = new ArrayList<>(layersList.subList(indexOfExcept, layersList.size()));
380 List<Layer> previousLayers = new ArrayList<>(layersList.subList(0, indexOfExcept));
381 Collections.reverse(previousLayers);
382 remainingLayers.addAll(previousLayers);
383 remainingLayers.remove(except);
384
385 // First look for visible data layer (and store first data layer for later)
386 Layer osmlayer = null;
387 for (Layer layer : remainingLayers) {
388 if (layer instanceof OsmDataLayer) {
389 if (layer.isVisible()) {
390 return layer;
391 } else if (osmlayer == null) {
392 osmlayer = layer;
393 }
394 }
395 }
396
397 // Then any data layer
398 if (osmlayer != null)
399 return osmlayer;
400
401 // Then any layer
402 for (Layer layer : layersList) {
403 if (layer != except) {
404 return layer;
405 }
406 }
407
408 // and then give up
409 return null;
410 }
411
412 /**
413 * Replies the currently active layer
414 *
415 * @return the currently active layer (may be null)
416 */
417 public synchronized Layer getActiveLayer() {
418 if (activeLayer instanceof OsmDataLayer) {
419 if (!((OsmDataLayer) activeLayer).isUploadInProgress()) {
420 return activeLayer;
421 } else {
422 return null;
423 }
424 } else {
425 return activeLayer;
426 }
427 }
428
429 /**
430 * Replies the current edit layer, if present and not readOnly
431 *
432 * @return the current edit layer. May be null.
433 * @see #getActiveDataLayer
434 */
435 public synchronized OsmDataLayer getEditLayer() {
436 if (osmDataLayer != null && !osmDataLayer.isLocked())
437 return osmDataLayer;
438 else
439 return null;
440 }
441
442 /**
443 * Replies the active data layer. The layer can be read-only.
444 *
445 * @return the current data layer. May be null or read-only.
446 * @see #getEditLayer
447 * @since 13434
448 */
449 public synchronized OsmDataLayer getActiveDataLayer() {
450 if (osmDataLayer != null)
451 return osmDataLayer;
452 else
453 return null;
454 }
455
456 /**
457 * Gets the data set of the active edit layer, if not readOnly.
458 * @return That data set, <code>null</code> if there is no edit layer.
459 * @see #getActiveDataSet
460 */
461 public synchronized DataSet getEditDataSet() {
462 if (osmDataLayer != null && !osmDataLayer.isLocked()) {
463 return osmDataLayer.getDataSet();
464 } else {
465 return null;
466 }
467 }
468
469 /**
470 * Gets the data set of the active data layer. The dataset can be read-only.
471 * @return That data set, <code>null</code> if there is no active data layer.
472 * @since 13926
473 */
474 public synchronized OsmData<?, ?, ?, ?> getActiveData() {
475 if (dataLayer != null) {
476 return dataLayer.getDataSet();
477 } else {
478 return null;
479 }
480 }
481
482 /**
483 * Gets the data set of the active {@link OsmDataLayer}. The dataset can be read-only.
484 * @return That data set, <code>null</code> if there is no active data layer.
485 * @see #getEditDataSet
486 * @since 13434
487 */
488 public synchronized DataSet getActiveDataSet() {
489 if (osmDataLayer != null) {
490 return osmDataLayer.getDataSet();
491 } else {
492 return null;
493 }
494 }
495
496 /**
497 * Returns the unique note layer, if present.
498 * @return the unique note layer, or null
499 * @since 13437
500 */
501 public NoteLayer getNoteLayer() {
502 List<NoteLayer> col = getLayersOfType(NoteLayer.class);
503 return col.isEmpty() ? null : col.get(0);
504 }
505
506 /**
507 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
508 * first, layer with the highest Z-Order last.
509 * <p>
510 * The active data layer is pulled above all adjacent data layers.
511 *
512 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
513 * first, layer with the highest Z-Order last.
514 */
515 public synchronized List<Layer> getVisibleLayersInZOrder() {
516 List<Layer> ret = new ArrayList<>();
517 // This is set while we delay the addition of the active layer.
518 boolean activeLayerDelayed = false;
519 List<Layer> layers = getLayers();
520 for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) {
521 Layer l = iterator.previous();
522 if (!l.isVisible()) {
523 // ignored
524 } else if (l == activeLayer && l instanceof OsmDataLayer) {
525 // delay and add after the current block of OsmDataLayer
526 activeLayerDelayed = true;
527 } else {
528 if (activeLayerDelayed && !(l instanceof OsmDataLayer)) {
529 // add active layer before the current one.
530 ret.add(activeLayer);
531 activeLayerDelayed = false;
532 }
533 // Add this layer now
534 ret.add(l);
535 }
536 }
537 if (activeLayerDelayed) {
538 ret.add(activeLayer);
539 }
540 return ret;
541 }
542
543 /**
544 * Invalidates current edit layer, if any. Does nothing if there is no edit layer.
545 * @since 13150
546 */
547 public void invalidateEditLayer() {
548 if (osmDataLayer != null) {
549 osmDataLayer.invalidate();
550 }
551 }
552
553 @Override
554 protected synchronized void realResetState() {
555 // Reset state if no asynchronous upload is under progress
556 if (!AsynchronousUploadPrimitivesTask.getCurrentAsynchronousUploadTask().isPresent()) {
557 // active and edit layer are unset automatically
558 super.realResetState();
559
560 activeLayerChangeListeners.clear();
561 layerAvailabilityListeners.clear();
562 } else {
563 String msg = tr("A background upload is already in progress. Cannot reset state until the upload is finished.");
564 Logging.warn(msg);
565 if (!GraphicsEnvironment.isHeadless()) {
566 GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.getMainFrame(), msg));
567 }
568 }
569 }
570
571 /**
572 * Prepares an OsmDataLayer for upload. The layer to be uploaded is locked and
573 * if the layer to be uploaded is the current editLayer then editLayer is reset
574 * to null for disallowing any changes to the layer. An ActiveLayerChangeEvent
575 * is fired to notify the listeners
576 *
577 * @param layer The OsmDataLayer to be uploaded
578 */
579 public synchronized void prepareLayerForUpload(OsmDataLayer layer) {
580 GuiHelper.assertCallFromEdt();
581 layer.setUploadInProgress();
582 layer.lock();
583
584 // Reset only the edit layer as empty
585 if (osmDataLayer == layer) {
586 ActiveLayerChangeEvent activeLayerChangeEvent = new ActiveLayerChangeEvent(this, osmDataLayer, activeLayer);
587 osmDataLayer = null;
588 fireActiveLayerChange(activeLayerChangeEvent);
589 }
590 }
591
592 /**
593 * Post upload processing of the OsmDataLayer.
594 * If the current edit layer is empty this function sets the layer uploaded as the
595 * current editLayer. An ActiveLayerChangeEvent is fired to notify the listeners
596 *
597 * @param layer The OsmDataLayer uploaded
598 */
599 public synchronized void processLayerAfterUpload(OsmDataLayer layer) {
600 GuiHelper.assertCallFromEdt();
601 layer.unlock();
602 layer.unsetUploadInProgress();
603
604 // Set the layer as edit layer if the edit layer is empty.
605 if (osmDataLayer == null) {
606 ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, osmDataLayer, activeLayer);
607 osmDataLayer = layer;
608 fireActiveLayerChange(layerChangeEvent);
609 }
610 }
611
612 /**
613 * Returns all {@link GpxData} we can get from current layers.
614 * @return all {@code GpxData} we can get from current layers
615 * @since 14802
616 */
617 public List<GpxData> getAllGpxData() {
618 List<GpxData> result = new ArrayList<>();
619 for (Layer layer : getLayers()) {
620 if (layer instanceof GpxLayer) {
621 result.add(((GpxLayer) layer).data);
622 } else if (layer instanceof GeoImageLayer) {
623 result.add(((GeoImageLayer) layer).getFauxGpxData());
624 }
625 }
626 return result;
627 }
628}
Note: See TracBrowser for help on using the repository browser.