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

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

see #15653, see #8509 - add headless check for unit tests

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