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.gui.MainApplication; 017import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask; 018import org.openstreetmap.josm.gui.util.GuiHelper; 019import org.openstreetmap.josm.tools.Logging; 020 021/** 022 * This class extends the layer manager by adding an active and an edit layer. 023 * <p> 024 * The active layer is the layer the user is currently working on. 025 * <p> 026 * The edit layer is an data layer that we currently work with. 027 * @author Michael Zangl 028 * @since 10279 029 */ 030public class MainLayerManager extends LayerManager { 031 /** 032 * This listener listens to changes of the active or the edit layer. 033 * @author Michael Zangl 034 * @since 10600 (functional interface) 035 */ 036 @FunctionalInterface 037 public interface ActiveLayerChangeListener { 038 /** 039 * Called whenever the active or edit layer changed. 040 * <p> 041 * You can be sure that this layer is still contained in this set. 042 * <p> 043 * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread. 044 * @param e The change event. 045 */ 046 void activeOrEditLayerChanged(ActiveLayerChangeEvent e); 047 } 048 049 /** 050 * This event is fired whenever the active or the data layer changes. 051 * @author Michael Zangl 052 */ 053 public static class ActiveLayerChangeEvent extends LayerManagerEvent { 054 055 private final OsmDataLayer previousDataLayer; 056 057 private final Layer previousActiveLayer; 058 059 /** 060 * Create a new {@link ActiveLayerChangeEvent} 061 * @param source The source 062 * @param previousDataLayer the previous data layer 063 * @param previousActiveLayer the previous active layer 064 */ 065 ActiveLayerChangeEvent(MainLayerManager source, OsmDataLayer previousDataLayer, 066 Layer previousActiveLayer) { 067 super(source); 068 this.previousDataLayer = previousDataLayer; 069 this.previousActiveLayer = previousActiveLayer; 070 } 071 072 /** 073 * Gets the data layer that was previously used. 074 * @return The old data layer, <code>null</code> if there is none. 075 * @deprecated use {@link #getPreviousDataLayer} 076 */ 077 @Deprecated 078 public OsmDataLayer getPreviousEditLayer() { 079 return getPreviousDataLayer(); 080 } 081 082 /** 083 * Gets the data layer that was previously used. 084 * @return The old data layer, <code>null</code> if there is none. 085 * @since 13434 086 */ 087 public OsmDataLayer getPreviousDataLayer() { 088 return previousDataLayer; 089 } 090 091 /** 092 * Gets the active layer that was previously used. 093 * @return The old active layer, <code>null</code> if there is none. 094 */ 095 public Layer getPreviousActiveLayer() { 096 return previousActiveLayer; 097 } 098 099 /** 100 * Gets the data set that was previously used. 101 * @return The data set of {@link #getPreviousDataLayer()}. 102 * @deprecated use {@link #getPreviousDataSet} 103 */ 104 @Deprecated 105 public DataSet getPreviousEditDataSet() { 106 return getPreviousDataSet(); 107 } 108 109 /** 110 * Gets the data set that was previously used. 111 * @return The data set of {@link #getPreviousDataLayer()}. 112 * @since 13434 113 */ 114 public DataSet getPreviousDataSet() { 115 if (previousDataLayer != null) { 116 return previousDataLayer.data; 117 } else { 118 return null; 119 } 120 } 121 122 @Override 123 public MainLayerManager getSource() { 124 return (MainLayerManager) super.getSource(); 125 } 126 } 127 128 /** 129 * This event is fired for {@link LayerAvailabilityListener} 130 * @author Michael Zangl 131 * @since 10508 132 */ 133 public static class LayerAvailabilityEvent extends LayerManagerEvent { 134 private final boolean hasLayers; 135 136 LayerAvailabilityEvent(LayerManager source, boolean hasLayers) { 137 super(source); 138 this.hasLayers = hasLayers; 139 } 140 141 /** 142 * Checks if this layer manager will have layers afterwards 143 * @return true if layers will be added. 144 */ 145 public boolean hasLayers() { 146 return hasLayers; 147 } 148 } 149 150 /** 151 * A listener that gets informed before any layer is displayed and after all layers are removed. 152 * @author Michael Zangl 153 * @since 10508 154 */ 155 public interface LayerAvailabilityListener { 156 /** 157 * This method is called in the UI thread right before the first layer is added. 158 * @param e The event. 159 */ 160 void beforeFirstLayerAdded(LayerAvailabilityEvent e); 161 162 /** 163 * This method is called in the UI thread after the last layer was removed. 164 * @param e The event. 165 */ 166 void afterLastLayerRemoved(LayerAvailabilityEvent e); 167 } 168 169 /** 170 * The layer from the layers list that is currently active. 171 */ 172 private Layer activeLayer; 173 174 /** 175 * The current active data layer. It might be editable or not, based on its read-only status. 176 */ 177 private OsmDataLayer dataLayer; 178 179 private final List<ActiveLayerChangeListener> activeLayerChangeListeners = new CopyOnWriteArrayList<>(); 180 private final List<LayerAvailabilityListener> layerAvailabilityListeners = new CopyOnWriteArrayList<>(); 181 182 /** 183 * Adds a active/edit layer change listener 184 * 185 * @param listener the listener. 186 */ 187 public synchronized void addActiveLayerChangeListener(ActiveLayerChangeListener listener) { 188 if (activeLayerChangeListeners.contains(listener)) { 189 throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener); 190 } 191 activeLayerChangeListeners.add(listener); 192 } 193 194 /** 195 * Adds a active/edit layer change listener. Fire a fake active-layer-changed-event right after adding 196 * the listener. The previous layers will be null. The listener is notified in the current thread. 197 * @param listener the listener. 198 */ 199 public synchronized void addAndFireActiveLayerChangeListener(ActiveLayerChangeListener listener) { 200 addActiveLayerChangeListener(listener); 201 listener.activeOrEditLayerChanged(new ActiveLayerChangeEvent(this, null, null)); 202 } 203 204 /** 205 * Removes an active/edit layer change listener. 206 * @param listener the listener. 207 */ 208 public synchronized void removeActiveLayerChangeListener(ActiveLayerChangeListener listener) { 209 if (!activeLayerChangeListeners.contains(listener)) { 210 throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener); 211 } 212 activeLayerChangeListeners.remove(listener); 213 } 214 215 /** 216 * Add a new {@link LayerAvailabilityListener}. 217 * @param listener The listener 218 * @since 10508 219 */ 220 public synchronized void addLayerAvailabilityListener(LayerAvailabilityListener listener) { 221 if (!layerAvailabilityListeners.add(listener)) { 222 throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener); 223 } 224 } 225 226 /** 227 * Remove an {@link LayerAvailabilityListener}. 228 * @param listener The listener 229 * @since 10508 230 */ 231 public synchronized void removeLayerAvailabilityListener(LayerAvailabilityListener listener) { 232 if (!layerAvailabilityListeners.remove(listener)) { 233 throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener); 234 } 235 } 236 237 /** 238 * Set the active layer, unless the layer is being uploaded. 239 * If the layer is an OsmDataLayer, the edit layer is also changed. 240 * @param layer The active layer. 241 */ 242 public void setActiveLayer(final Layer layer) { 243 // we force this on to the EDT Thread to make events fire from there. 244 // The synchronization lock needs to be held by the EDT. 245 if (layer instanceof OsmDataLayer && ((OsmDataLayer) layer).isUploadInProgress()) { 246 GuiHelper.runInEDT(() -> 247 JOptionPane.showMessageDialog( 248 MainApplication.parent, 249 tr("Trying to set a read only data layer as edit layer"), 250 tr("Warning"), 251 JOptionPane.WARNING_MESSAGE)); 252 } else { 253 GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer)); 254 } 255 } 256 257 protected synchronized void realSetActiveLayer(final Layer layer) { 258 // to be called in EDT thread 259 checkContainsLayer(layer); 260 setActiveLayer(layer, false); 261 } 262 263 private void setActiveLayer(Layer layer, boolean forceEditLayerUpdate) { 264 ActiveLayerChangeEvent event = new ActiveLayerChangeEvent(this, dataLayer, activeLayer); 265 activeLayer = layer; 266 if (activeLayer instanceof OsmDataLayer) { 267 dataLayer = (OsmDataLayer) activeLayer; 268 } else if (forceEditLayerUpdate) { 269 dataLayer = null; 270 } 271 fireActiveLayerChange(event); 272 } 273 274 private void fireActiveLayerChange(ActiveLayerChangeEvent event) { 275 GuiHelper.assertCallFromEdt(); 276 if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousDataLayer() != dataLayer) { 277 for (ActiveLayerChangeListener l : activeLayerChangeListeners) { 278 l.activeOrEditLayerChanged(event); 279 } 280 } 281 } 282 283 @Override 284 protected synchronized void realAddLayer(Layer layer, boolean initialZoom) { 285 if (getLayers().isEmpty()) { 286 LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, true); 287 for (LayerAvailabilityListener l : layerAvailabilityListeners) { 288 l.beforeFirstLayerAdded(e); 289 } 290 } 291 super.realAddLayer(layer, initialZoom); 292 293 // update the active layer automatically. 294 if (layer instanceof OsmDataLayer || activeLayer == null) { 295 setActiveLayer(layer); 296 } 297 } 298 299 @Override 300 protected Collection<Layer> realRemoveSingleLayer(Layer layer) { 301 if ((layer instanceof OsmDataLayer) && (((OsmDataLayer) layer).isUploadInProgress())) { 302 GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.parent, 303 tr("Trying to delete the layer with background upload. Please wait until the upload is finished."))); 304 305 // Return an empty collection for allowing to delete other layers 306 return new ArrayList<>(); 307 } 308 309 if (layer == activeLayer || layer == dataLayer) { 310 Layer nextActive = suggestNextActiveLayer(layer); 311 setActiveLayer(nextActive, true); 312 } 313 314 Collection<Layer> toDelete = super.realRemoveSingleLayer(layer); 315 if (getLayers().isEmpty()) { 316 LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, false); 317 for (LayerAvailabilityListener l : layerAvailabilityListeners) { 318 l.afterLastLayerRemoved(e); 319 } 320 } 321 return toDelete; 322 } 323 324 /** 325 * Determines the next active data layer according to the following 326 * rules: 327 * <ul> 328 * <li>if there is at least one {@link OsmDataLayer} the first one 329 * becomes active</li> 330 * <li>otherwise, the top most layer of any type becomes active</li> 331 * </ul> 332 * 333 * @param except A layer to ignore. 334 * @return the next active data layer 335 */ 336 private Layer suggestNextActiveLayer(Layer except) { 337 List<Layer> layersList = new ArrayList<>(getLayers()); 338 layersList.remove(except); 339 // First look for data layer 340 for (Layer layer : layersList) { 341 if (layer instanceof OsmDataLayer) { 342 return layer; 343 } 344 } 345 346 // Then any layer 347 if (!layersList.isEmpty()) 348 return layersList.get(0); 349 350 // and then give up 351 return null; 352 } 353 354 /** 355 * Replies the currently active layer 356 * 357 * @return the currently active layer (may be null) 358 */ 359 public synchronized Layer getActiveLayer() { 360 if (activeLayer instanceof OsmDataLayer) { 361 if (!((OsmDataLayer) activeLayer).isUploadInProgress()) { 362 return activeLayer; 363 } else { 364 return null; 365 } 366 } else { 367 return activeLayer; 368 } 369 } 370 371 /** 372 * Replies the current edit layer, if present and not readOnly 373 * 374 * @return the current edit layer. May be null. 375 * @see #getActiveDataLayer 376 */ 377 public synchronized OsmDataLayer getEditLayer() { 378 if (dataLayer != null && !dataLayer.isLocked()) 379 return dataLayer; 380 else 381 return null; 382 } 383 384 /** 385 * Replies the active data layer. The layer can be read-only. 386 * 387 * @return the current data layer. May be null or read-only. 388 * @see #getEditLayer 389 * @since 13434 390 */ 391 public synchronized OsmDataLayer getActiveDataLayer() { 392 if (dataLayer != null) 393 return dataLayer; 394 else 395 return null; 396 } 397 398 /** 399 * Gets the data set of the active edit layer, if not readOnly. 400 * @return That data set, <code>null</code> if there is no edit layer. 401 * @see #getActiveDataSet 402 */ 403 public synchronized DataSet getEditDataSet() { 404 if (dataLayer != null && !dataLayer.isLocked()) { 405 return dataLayer.data; 406 } else { 407 return null; 408 } 409 } 410 411 /** 412 * Gets the data set of the active data layer. The dataset can be read-only. 413 * @return That data set, <code>null</code> if there is no active data layer. 414 * @see #getEditDataSet 415 * @since 13434 416 */ 417 public synchronized DataSet getActiveDataSet() { 418 if (dataLayer != null) { 419 return dataLayer.data; 420 } else { 421 return null; 422 } 423 } 424 425 /** 426 * Returns the unique note layer, if present. 427 * @return the unique note layer, or null 428 * @since 13437 429 */ 430 public NoteLayer getNoteLayer() { 431 List<NoteLayer> col = getLayersOfType(NoteLayer.class); 432 return col.isEmpty() ? null : col.get(0); 433 } 434 435 /** 436 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order 437 * first, layer with the highest Z-Order last. 438 * <p> 439 * The active data layer is pulled above all adjacent data layers. 440 * 441 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order 442 * first, layer with the highest Z-Order last. 443 */ 444 public synchronized List<Layer> getVisibleLayersInZOrder() { 445 List<Layer> ret = new ArrayList<>(); 446 // This is set while we delay the addition of the active layer. 447 boolean activeLayerDelayed = false; 448 List<Layer> layers = getLayers(); 449 for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) { 450 Layer l = iterator.previous(); 451 if (!l.isVisible()) { 452 // ignored 453 } else if (l == activeLayer && l instanceof OsmDataLayer) { 454 // delay and add after the current block of OsmDataLayer 455 activeLayerDelayed = true; 456 } else { 457 if (activeLayerDelayed && !(l instanceof OsmDataLayer)) { 458 // add active layer before the current one. 459 ret.add(activeLayer); 460 activeLayerDelayed = false; 461 } 462 // Add this layer now 463 ret.add(l); 464 } 465 } 466 if (activeLayerDelayed) { 467 ret.add(activeLayer); 468 } 469 return ret; 470 } 471 472 /** 473 * Invalidates current edit layer, if any. Does nothing if there is no edit layer. 474 * @since 13150 475 */ 476 public void invalidateEditLayer() { 477 if (dataLayer != null) { 478 dataLayer.invalidate(); 479 } 480 } 481 482 @Override 483 protected synchronized void realResetState() { 484 // Reset state if no asynchronous upload is under progress 485 if (!AsynchronousUploadPrimitivesTask.getCurrentAsynchronousUploadTask().isPresent()) { 486 // active and edit layer are unset automatically 487 super.realResetState(); 488 489 activeLayerChangeListeners.clear(); 490 layerAvailabilityListeners.clear(); 491 } else { 492 String msg = tr("A background upload is already in progress. Cannot reset state until the upload is finished."); 493 Logging.warn(msg); 494 if (!GraphicsEnvironment.isHeadless()) { 495 GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.parent, msg)); 496 } 497 } 498 } 499 500 /** 501 * Prepares an OsmDataLayer for upload. The layer to be uploaded is locked and 502 * if the layer to be uploaded is the current editLayer then editLayer is reset 503 * to null for disallowing any changes to the layer. An ActiveLayerChangeEvent 504 * is fired to notify the listeners 505 * 506 * @param layer The OsmDataLayer to be uploaded 507 */ 508 public synchronized void prepareLayerForUpload(OsmDataLayer layer) { 509 GuiHelper.assertCallFromEdt(); 510 layer.setUploadInProgress(); 511 layer.lock(); 512 513 // Reset only the edit layer as empty 514 if (dataLayer == layer) { 515 ActiveLayerChangeEvent activeLayerChangeEvent = new ActiveLayerChangeEvent(this, dataLayer, activeLayer); 516 dataLayer = null; 517 fireActiveLayerChange(activeLayerChangeEvent); 518 } 519 } 520 521 /** 522 * Post upload processing of the OsmDataLayer. 523 * If the current edit layer is empty this function sets the layer uploaded as the 524 * current editLayer. An ActiveLayerChangeEvent is fired to notify the listeners 525 * 526 * @param layer The OsmDataLayer uploaded 527 */ 528 public synchronized void processLayerAfterUpload(OsmDataLayer layer) { 529 GuiHelper.assertCallFromEdt(); 530 layer.unlock(); 531 layer.unsetUploadInProgress(); 532 533 // Set the layer as edit layer if the edit layer is empty. 534 if (dataLayer == null) { 535 ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, dataLayer, activeLayer); 536 dataLayer = layer; 537 fireActiveLayerChange(layerChangeEvent); 538 } 539 } 540}