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.parent, 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) layer; 254 } 255 if (activeLayer instanceof OsmDataLayer) { 256 osmDataLayer = (OsmDataLayer) activeLayer; 257 } else if (forceEditLayerUpdate) { 258 osmDataLayer = null; 259 } 260 fireActiveLayerChange(event); 261 } 262 263 private void fireActiveLayerChange(ActiveLayerChangeEvent event) { 264 GuiHelper.assertCallFromEdt(); 265 if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousDataLayer() != osmDataLayer) { 266 for (ActiveLayerChangeListener l : activeLayerChangeListeners) { 267 l.activeOrEditLayerChanged(event); 268 } 269 } 270 } 271 272 @Override 273 protected synchronized void realAddLayer(Layer layer, boolean initialZoom) { 274 if (getLayers().isEmpty()) { 275 LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, true); 276 for (LayerAvailabilityListener l : layerAvailabilityListeners) { 277 l.beforeFirstLayerAdded(e); 278 } 279 } 280 super.realAddLayer(layer, initialZoom); 281 282 // update the active layer automatically. 283 if (layer instanceof OsmDataLayer || activeLayer == null) { 284 setActiveLayer(layer); 285 } 286 } 287 288 @Override 289 protected Collection<Layer> realRemoveSingleLayer(Layer layer) { 290 if ((layer instanceof OsmDataLayer) && (((OsmDataLayer) layer).isUploadInProgress())) { 291 GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.parent, 292 tr("Trying to delete the layer with background upload. Please wait until the upload is finished."))); 293 294 // Return an empty collection for allowing to delete other layers 295 return new ArrayList<>(); 296 } 297 298 if (layer == activeLayer || layer == osmDataLayer) { 299 Layer nextActive = suggestNextActiveLayer(layer); 300 setActiveLayer(nextActive, true); 301 } 302 303 Collection<Layer> toDelete = super.realRemoveSingleLayer(layer); 304 if (getLayers().isEmpty()) { 305 LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, false); 306 for (LayerAvailabilityListener l : layerAvailabilityListeners) { 307 l.afterLastLayerRemoved(e); 308 } 309 } 310 return toDelete; 311 } 312 313 /** 314 * Determines the next active data layer according to the following 315 * rules: 316 * <ul> 317 * <li>if there is at least one {@link OsmDataLayer} the first one 318 * becomes active</li> 319 * <li>otherwise, the top most layer of any type becomes active</li> 320 * </ul> 321 * 322 * @param except A layer to ignore. 323 * @return the next active data layer 324 */ 325 private Layer suggestNextActiveLayer(Layer except) { 326 List<Layer> layersList = new ArrayList<>(getLayers()); 327 layersList.remove(except); 328 // First look for data layer 329 for (Layer layer : layersList) { 330 if (layer instanceof OsmDataLayer) { 331 return layer; 332 } 333 } 334 335 // Then any layer 336 if (!layersList.isEmpty()) 337 return layersList.get(0); 338 339 // and then give up 340 return null; 341 } 342 343 /** 344 * Replies the currently active layer 345 * 346 * @return the currently active layer (may be null) 347 */ 348 public synchronized Layer getActiveLayer() { 349 if (activeLayer instanceof OsmDataLayer) { 350 if (!((OsmDataLayer) activeLayer).isUploadInProgress()) { 351 return activeLayer; 352 } else { 353 return null; 354 } 355 } else { 356 return activeLayer; 357 } 358 } 359 360 /** 361 * Replies the current edit layer, if present and not readOnly 362 * 363 * @return the current edit layer. May be null. 364 * @see #getActiveDataLayer 365 */ 366 public synchronized OsmDataLayer getEditLayer() { 367 if (osmDataLayer != null && !osmDataLayer.isLocked()) 368 return osmDataLayer; 369 else 370 return null; 371 } 372 373 /** 374 * Replies the active data layer. The layer can be read-only. 375 * 376 * @return the current data layer. May be null or read-only. 377 * @see #getEditLayer 378 * @since 13434 379 */ 380 public synchronized OsmDataLayer getActiveDataLayer() { 381 if (osmDataLayer != null) 382 return osmDataLayer; 383 else 384 return null; 385 } 386 387 /** 388 * Gets the data set of the active edit layer, if not readOnly. 389 * @return That data set, <code>null</code> if there is no edit layer. 390 * @see #getActiveDataSet 391 */ 392 public synchronized DataSet getEditDataSet() { 393 if (osmDataLayer != null && !osmDataLayer.isLocked()) { 394 return osmDataLayer.getDataSet(); 395 } else { 396 return null; 397 } 398 } 399 400 /** 401 * Gets the data set of the active data layer. The dataset can be read-only. 402 * @return That data set, <code>null</code> if there is no active data layer. 403 * @since 13926 404 */ 405 public synchronized OsmData<?, ?, ?, ?> getActiveData() { 406 if (dataLayer != null) { 407 return dataLayer.getDataSet(); 408 } else { 409 return null; 410 } 411 } 412 413 /** 414 * Gets the data set of the active {@link OsmDataLayer}. The dataset can be read-only. 415 * @return That data set, <code>null</code> if there is no active data layer. 416 * @see #getEditDataSet 417 * @since 13434 418 */ 419 public synchronized DataSet getActiveDataSet() { 420 if (osmDataLayer != null) { 421 return osmDataLayer.getDataSet(); 422 } else { 423 return null; 424 } 425 } 426 427 /** 428 * Returns the unique note layer, if present. 429 * @return the unique note layer, or null 430 * @since 13437 431 */ 432 public NoteLayer getNoteLayer() { 433 List<NoteLayer> col = getLayersOfType(NoteLayer.class); 434 return col.isEmpty() ? null : col.get(0); 435 } 436 437 /** 438 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order 439 * first, layer with the highest Z-Order last. 440 * <p> 441 * The active data layer is pulled above all adjacent data layers. 442 * 443 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order 444 * first, layer with the highest Z-Order last. 445 */ 446 public synchronized List<Layer> getVisibleLayersInZOrder() { 447 List<Layer> ret = new ArrayList<>(); 448 // This is set while we delay the addition of the active layer. 449 boolean activeLayerDelayed = false; 450 List<Layer> layers = getLayers(); 451 for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) { 452 Layer l = iterator.previous(); 453 if (!l.isVisible()) { 454 // ignored 455 } else if (l == activeLayer && l instanceof OsmDataLayer) { 456 // delay and add after the current block of OsmDataLayer 457 activeLayerDelayed = true; 458 } else { 459 if (activeLayerDelayed && !(l instanceof OsmDataLayer)) { 460 // add active layer before the current one. 461 ret.add(activeLayer); 462 activeLayerDelayed = false; 463 } 464 // Add this layer now 465 ret.add(l); 466 } 467 } 468 if (activeLayerDelayed) { 469 ret.add(activeLayer); 470 } 471 return ret; 472 } 473 474 /** 475 * Invalidates current edit layer, if any. Does nothing if there is no edit layer. 476 * @since 13150 477 */ 478 public void invalidateEditLayer() { 479 if (osmDataLayer != null) { 480 osmDataLayer.invalidate(); 481 } 482 } 483 484 @Override 485 protected synchronized void realResetState() { 486 // Reset state if no asynchronous upload is under progress 487 if (!AsynchronousUploadPrimitivesTask.getCurrentAsynchronousUploadTask().isPresent()) { 488 // active and edit layer are unset automatically 489 super.realResetState(); 490 491 activeLayerChangeListeners.clear(); 492 layerAvailabilityListeners.clear(); 493 } else { 494 String msg = tr("A background upload is already in progress. Cannot reset state until the upload is finished."); 495 Logging.warn(msg); 496 if (!GraphicsEnvironment.isHeadless()) { 497 GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.parent, msg)); 498 } 499 } 500 } 501 502 /** 503 * Prepares an OsmDataLayer for upload. The layer to be uploaded is locked and 504 * if the layer to be uploaded is the current editLayer then editLayer is reset 505 * to null for disallowing any changes to the layer. An ActiveLayerChangeEvent 506 * is fired to notify the listeners 507 * 508 * @param layer The OsmDataLayer to be uploaded 509 */ 510 public synchronized void prepareLayerForUpload(OsmDataLayer layer) { 511 GuiHelper.assertCallFromEdt(); 512 layer.setUploadInProgress(); 513 layer.lock(); 514 515 // Reset only the edit layer as empty 516 if (osmDataLayer == layer) { 517 ActiveLayerChangeEvent activeLayerChangeEvent = new ActiveLayerChangeEvent(this, osmDataLayer, activeLayer); 518 osmDataLayer = null; 519 fireActiveLayerChange(activeLayerChangeEvent); 520 } 521 } 522 523 /** 524 * Post upload processing of the OsmDataLayer. 525 * If the current edit layer is empty this function sets the layer uploaded as the 526 * current editLayer. An ActiveLayerChangeEvent is fired to notify the listeners 527 * 528 * @param layer The OsmDataLayer uploaded 529 */ 530 public synchronized void processLayerAfterUpload(OsmDataLayer layer) { 531 GuiHelper.assertCallFromEdt(); 532 layer.unlock(); 533 layer.unsetUploadInProgress(); 534 535 // Set the layer as edit layer if the edit layer is empty. 536 if (osmDataLayer == null) { 537 ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, osmDataLayer, activeLayer); 538 osmDataLayer = layer; 539 fireActiveLayerChange(layerChangeEvent); 540 } 541 } 542}