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

Last change on this file since 13257 was 13219, checked in by Don-vip, 6 years ago

see #15653, see #8509 - fix unit test causing mayhem

File size: 16.6 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.List;
10import java.util.ListIterator;
11import java.util.concurrent.CopyOnWriteArrayList;
12
13import javax.swing.JOptionPane;
14
15import org.openstreetmap.josm.data.osm.DataSet;
16import org.openstreetmap.josm.gui.MainApplication;
17import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask;
18import org.openstreetmap.josm.gui.util.GuiHelper;
19import org.openstreetmap.josm.tools.Logging;
20
21/**
22 * This class extends the layer manager by adding an active and an edit layer.
23 * <p>
24 * The active layer is the layer the user is currently working on.
25 * <p>
26 * The edit layer is an data layer that we currently work with.
27 * @author Michael Zangl
28 * @since 10279
29 */
30public class MainLayerManager extends LayerManager {
31 /**
32 * This listener listens to changes of the active or the edit layer.
33 * @author Michael Zangl
34 * @since 10600 (functional interface)
35 */
36 @FunctionalInterface
37 public interface ActiveLayerChangeListener {
38 /**
39 * Called whenever the active or edit layer changed.
40 * <p>
41 * You can be sure that this layer is still contained in this set.
42 * <p>
43 * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread.
44 * @param e The change event.
45 */
46 void activeOrEditLayerChanged(ActiveLayerChangeEvent e);
47 }
48
49 /**
50 * This event is fired whenever the active or the edit layer changes.
51 * @author Michael Zangl
52 */
53 public static class ActiveLayerChangeEvent extends LayerManagerEvent {
54
55 private final OsmDataLayer previousEditLayer;
56
57 private final Layer previousActiveLayer;
58
59 /**
60 * Create a new {@link ActiveLayerChangeEvent}
61 * @param source The source
62 * @param previousEditLayer the previous edit layer
63 * @param previousActiveLayer the previous active layer
64 */
65 ActiveLayerChangeEvent(MainLayerManager source, OsmDataLayer previousEditLayer,
66 Layer previousActiveLayer) {
67 super(source);
68 this.previousEditLayer = previousEditLayer;
69 this.previousActiveLayer = previousActiveLayer;
70 }
71
72 /**
73 * Gets the edit layer that was previously used.
74 * @return The old edit layer, <code>null</code> if there is none.
75 */
76 public OsmDataLayer getPreviousEditLayer() {
77 return previousEditLayer;
78 }
79
80 /**
81 * Gets the active layer that was previously used.
82 * @return The old active layer, <code>null</code> if there is none.
83 */
84 public Layer getPreviousActiveLayer() {
85 return previousActiveLayer;
86 }
87
88 /**
89 * Gets the data set that was previously used.
90 * @return The data set of {@link #getPreviousEditLayer()}.
91 */
92 public DataSet getPreviousEditDataSet() {
93 if (previousEditLayer != null) {
94 return previousEditLayer.data;
95 } else {
96 return null;
97 }
98 }
99
100 @Override
101 public MainLayerManager getSource() {
102 return (MainLayerManager) super.getSource();
103 }
104 }
105
106 /**
107 * This event is fired for {@link LayerAvailabilityListener}
108 * @author Michael Zangl
109 * @since 10508
110 */
111 public static class LayerAvailabilityEvent extends LayerManagerEvent {
112 private final boolean hasLayers;
113
114 LayerAvailabilityEvent(LayerManager source, boolean hasLayers) {
115 super(source);
116 this.hasLayers = hasLayers;
117 }
118
119 /**
120 * Checks if this layer manager will have layers afterwards
121 * @return true if layers will be added.
122 */
123 public boolean hasLayers() {
124 return hasLayers;
125 }
126 }
127
128 /**
129 * A listener that gets informed before any layer is displayed and after all layers are removed.
130 * @author Michael Zangl
131 * @since 10508
132 */
133 public interface LayerAvailabilityListener {
134 /**
135 * This method is called in the UI thread right before the first layer is added.
136 * @param e The event.
137 */
138 void beforeFirstLayerAdded(LayerAvailabilityEvent e);
139
140 /**
141 * This method is called in the UI thread after the last layer was removed.
142 * @param e The event.
143 */
144 void afterLastLayerRemoved(LayerAvailabilityEvent e);
145 }
146
147 /**
148 * The layer from the layers list that is currently active.
149 */
150 private Layer activeLayer;
151
152 /**
153 * The edit layer is the current active data layer.
154 */
155 private OsmDataLayer editLayer;
156
157 private final List<ActiveLayerChangeListener> activeLayerChangeListeners = new CopyOnWriteArrayList<>();
158 private final List<LayerAvailabilityListener> layerAvailabilityListeners = new CopyOnWriteArrayList<>();
159
160 /**
161 * Adds a active/edit layer change listener
162 *
163 * @param listener the listener.
164 */
165 public synchronized void addActiveLayerChangeListener(ActiveLayerChangeListener listener) {
166 if (activeLayerChangeListeners.contains(listener)) {
167 throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener);
168 }
169 activeLayerChangeListeners.add(listener);
170 }
171
172 /**
173 * Adds a active/edit layer change listener. Fire a fake active-layer-changed-event right after adding
174 * the listener. The previous layers will be null. The listener is notified in the current thread.
175 * @param listener the listener.
176 */
177 public synchronized void addAndFireActiveLayerChangeListener(ActiveLayerChangeListener listener) {
178 addActiveLayerChangeListener(listener);
179 listener.activeOrEditLayerChanged(new ActiveLayerChangeEvent(this, null, null));
180 }
181
182 /**
183 * Removes an active/edit layer change listener.
184 * @param listener the listener.
185 */
186 public synchronized void removeActiveLayerChangeListener(ActiveLayerChangeListener listener) {
187 if (!activeLayerChangeListeners.contains(listener)) {
188 throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener);
189 }
190 activeLayerChangeListeners.remove(listener);
191 }
192
193 /**
194 * Add a new {@link LayerAvailabilityListener}.
195 * @param listener The listener
196 * @since 10508
197 */
198 public synchronized void addLayerAvailabilityListener(LayerAvailabilityListener listener) {
199 if (!layerAvailabilityListeners.add(listener)) {
200 throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener);
201 }
202 }
203
204 /**
205 * Remove an {@link LayerAvailabilityListener}.
206 * @param listener The listener
207 * @since 10508
208 */
209 public synchronized void removeLayerAvailabilityListener(LayerAvailabilityListener listener) {
210 if (!layerAvailabilityListeners.remove(listener)) {
211 throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener);
212 }
213 }
214
215 /**
216 * Set the active layer, unless the layer is read-only.
217 * If the layer is an OsmDataLayer, the edit layer is also changed.
218 * @param layer The active layer.
219 */
220 public void setActiveLayer(final Layer layer) {
221 // we force this on to the EDT Thread to make events fire from there.
222 // The synchronization lock needs to be held by the EDT.
223 if (layer instanceof OsmDataLayer && ((OsmDataLayer) layer).isReadOnly()) {
224 GuiHelper.runInEDT(() ->
225 JOptionPane.showMessageDialog(
226 MainApplication.parent,
227 tr("Trying to set a read only data layer as edit layer"),
228 tr("Warning"),
229 JOptionPane.WARNING_MESSAGE));
230 } else {
231 GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer));
232 }
233 }
234
235 protected synchronized void realSetActiveLayer(final Layer layer) {
236 // to be called in EDT thread
237 checkContainsLayer(layer);
238 setActiveLayer(layer, false);
239 }
240
241 private void setActiveLayer(Layer layer, boolean forceEditLayerUpdate) {
242 ActiveLayerChangeEvent event = new ActiveLayerChangeEvent(this, editLayer, activeLayer);
243 activeLayer = layer;
244 if (activeLayer instanceof OsmDataLayer) {
245 editLayer = (OsmDataLayer) activeLayer;
246 } else if (forceEditLayerUpdate) {
247 editLayer = null;
248 }
249 fireActiveLayerChange(event);
250 }
251
252 private void fireActiveLayerChange(ActiveLayerChangeEvent event) {
253 GuiHelper.assertCallFromEdt();
254 if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousEditLayer() != editLayer) {
255 for (ActiveLayerChangeListener l : activeLayerChangeListeners) {
256 l.activeOrEditLayerChanged(event);
257 }
258 }
259 }
260
261 @Override
262 protected synchronized void realAddLayer(Layer layer, boolean initialZoom) {
263 if (getLayers().isEmpty()) {
264 LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, true);
265 for (LayerAvailabilityListener l : layerAvailabilityListeners) {
266 l.beforeFirstLayerAdded(e);
267 }
268 }
269 super.realAddLayer(layer, initialZoom);
270
271 // update the active layer automatically.
272 if (layer instanceof OsmDataLayer || activeLayer == null) {
273 setActiveLayer(layer);
274 }
275 }
276
277 @Override
278 protected Collection<Layer> realRemoveSingleLayer(Layer layer) {
279 if ((layer instanceof OsmDataLayer) && (((OsmDataLayer) layer).isReadOnly())) {
280 GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.parent,
281 tr("Trying to delete the layer with background upload. Please wait until the upload is finished.")));
282
283 // Return an empty collection for allowing to delete other layers
284 return new ArrayList<>();
285 }
286
287 if (layer == activeLayer || layer == editLayer) {
288 Layer nextActive = suggestNextActiveLayer(layer);
289 setActiveLayer(nextActive, true);
290 }
291
292 Collection<Layer> toDelete = super.realRemoveSingleLayer(layer);
293 if (getLayers().isEmpty()) {
294 LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, false);
295 for (LayerAvailabilityListener l : layerAvailabilityListeners) {
296 l.afterLastLayerRemoved(e);
297 }
298 }
299 return toDelete;
300 }
301
302 /**
303 * Determines the next active data layer according to the following
304 * rules:
305 * <ul>
306 * <li>if there is at least one {@link OsmDataLayer} the first one
307 * becomes active</li>
308 * <li>otherwise, the top most layer of any type becomes active</li>
309 * </ul>
310 *
311 * @param except A layer to ignore.
312 * @return the next active data layer
313 */
314 private Layer suggestNextActiveLayer(Layer except) {
315 List<Layer> layersList = new ArrayList<>(getLayers());
316 layersList.remove(except);
317 // First look for data layer
318 for (Layer layer : layersList) {
319 if (layer instanceof OsmDataLayer) {
320 return layer;
321 }
322 }
323
324 // Then any layer
325 if (!layersList.isEmpty())
326 return layersList.get(0);
327
328 // and then give up
329 return null;
330 }
331
332 /**
333 * Replies the currently active layer
334 *
335 * @return the currently active layer (may be null)
336 */
337 public synchronized Layer getActiveLayer() {
338 if (activeLayer instanceof OsmDataLayer) {
339 if (!((OsmDataLayer) activeLayer).isReadOnly()) {
340 return activeLayer;
341 } else {
342 return null;
343 }
344 } else {
345 return activeLayer;
346 }
347 }
348
349 /**
350 * Replies the current edit layer, if present and not readOnly
351 *
352 * @return the current edit layer. May be null.
353 */
354 public synchronized OsmDataLayer getEditLayer() {
355 if (editLayer != null && !editLayer.isReadOnly())
356 return editLayer;
357 else
358 return null;
359 }
360
361 /**
362 * Gets the data set of the active edit layer.
363 * @return That data set, <code>null</code> if there is no edit layer.
364 */
365 public synchronized DataSet getEditDataSet() {
366 if (editLayer != null) {
367 return editLayer.data;
368 } else {
369 return null;
370 }
371 }
372
373 /**
374 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
375 * first, layer with the highest Z-Order last.
376 * <p>
377 * The active data layer is pulled above all adjacent data layers.
378 *
379 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
380 * first, layer with the highest Z-Order last.
381 */
382 public synchronized List<Layer> getVisibleLayersInZOrder() {
383 List<Layer> ret = new ArrayList<>();
384 // This is set while we delay the addition of the active layer.
385 boolean activeLayerDelayed = false;
386 List<Layer> layers = getLayers();
387 for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) {
388 Layer l = iterator.previous();
389 if (!l.isVisible()) {
390 // ignored
391 } else if (l == activeLayer && l instanceof OsmDataLayer) {
392 // delay and add after the current block of OsmDataLayer
393 activeLayerDelayed = true;
394 } else {
395 if (activeLayerDelayed && !(l instanceof OsmDataLayer)) {
396 // add active layer before the current one.
397 ret.add(activeLayer);
398 activeLayerDelayed = false;
399 }
400 // Add this layer now
401 ret.add(l);
402 }
403 }
404 if (activeLayerDelayed) {
405 ret.add(activeLayer);
406 }
407 return ret;
408 }
409
410 /**
411 * Invalidates current edit layer, if any. Does nothing if there is no edit layer.
412 * @since 13150
413 */
414 public void invalidateEditLayer() {
415 if (editLayer != null) {
416 editLayer.invalidate();
417 }
418 }
419
420 @Override
421 protected synchronized void realResetState() {
422 // Reset state if no asynchronous upload is under progress
423 if (!AsynchronousUploadPrimitivesTask.getCurrentAsynchronousUploadTask().isPresent()) {
424 // active and edit layer are unset automatically
425 super.realResetState();
426
427 activeLayerChangeListeners.clear();
428 layerAvailabilityListeners.clear();
429 } else {
430 String msg = tr("A background upload is already in progress. Cannot reset state until the upload is finished.");
431 Logging.warn(msg);
432 if (!GraphicsEnvironment.isHeadless()) {
433 GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.parent, msg));
434 }
435 }
436 }
437
438 /**
439 * Prepares an OsmDataLayer for upload. The layer to be uploaded is locked and
440 * if the layer to be uploaded is the current editLayer then editLayer is reset
441 * to null for disallowing any changes to the layer. An ActiveLayerChangeEvent
442 * is fired to notify the listeners
443 *
444 * @param layer The OsmDataLayer to be uploaded
445 */
446 public void prepareLayerForUpload(OsmDataLayer layer) {
447
448 GuiHelper.assertCallFromEdt();
449 layer.setReadOnly();
450
451 // Reset only the edit layer as empty
452 if (editLayer == layer) {
453 ActiveLayerChangeEvent activeLayerChangeEvent = new ActiveLayerChangeEvent(this, editLayer, activeLayer);
454 editLayer = null;
455 fireActiveLayerChange(activeLayerChangeEvent);
456 }
457 }
458
459 /**
460 * Post upload processing of the OsmDataLayer.
461 * If the current edit layer is empty this function sets the layer uploaded as the
462 * current editLayer. An ActiveLayerChangeEvent is fired to notify the listeners
463 *
464 * @param layer The OsmDataLayer uploaded
465 */
466 public void processLayerAfterUpload(OsmDataLayer layer) {
467 GuiHelper.assertCallFromEdt();
468 layer.unsetReadOnly();
469
470 // Set the layer as edit layer if the edit layer is empty.
471 if (editLayer == null) {
472 ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, editLayer, activeLayer);
473 editLayer = layer;
474 fireActiveLayerChange(layerChangeEvent);
475 }
476 }
477}
Note: See TracBrowser for help on using the repository browser.