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