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}