001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer; 003 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.List; 007import java.util.ListIterator; 008import java.util.concurrent.CopyOnWriteArrayList; 009 010import org.openstreetmap.josm.data.osm.DataSet; 011import org.openstreetmap.josm.gui.util.GuiHelper; 012 013/** 014 * This class extends the layer manager by adding an active and an edit layer. 015 * <p> 016 * The active layer is the layer the user is currently working on. 017 * <p> 018 * The edit layer is an data layer that we currently work with. 019 * @author Michael Zangl 020 * @since 10279 021 */ 022public class MainLayerManager extends LayerManager { 023 /** 024 * This listener listens to changes of the active or the edit layer. 025 * @author Michael Zangl 026 * @since 10600 (functional interface) 027 */ 028 @FunctionalInterface 029 public interface ActiveLayerChangeListener { 030 /** 031 * Called whenever the active or edit layer changed. 032 * <p> 033 * You can be sure that this layer is still contained in this set. 034 * <p> 035 * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread. 036 * @param e The change event. 037 */ 038 void activeOrEditLayerChanged(ActiveLayerChangeEvent e); 039 } 040 041 /** 042 * This event is fired whenever the active or the edit layer changes. 043 * @author Michael Zangl 044 */ 045 public static class ActiveLayerChangeEvent extends LayerManagerEvent { 046 047 private final OsmDataLayer previousEditLayer; 048 049 private final Layer previousActiveLayer; 050 051 /** 052 * Create a new {@link ActiveLayerChangeEvent} 053 * @param source The source 054 * @param previousEditLayer the previous edit layer 055 * @param previousActiveLayer the previous active layer 056 */ 057 ActiveLayerChangeEvent(MainLayerManager source, OsmDataLayer previousEditLayer, 058 Layer previousActiveLayer) { 059 super(source); 060 this.previousEditLayer = previousEditLayer; 061 this.previousActiveLayer = previousActiveLayer; 062 } 063 064 /** 065 * Gets the edit layer that was previously used. 066 * @return The old edit layer, <code>null</code> if there is none. 067 */ 068 public OsmDataLayer getPreviousEditLayer() { 069 return previousEditLayer; 070 } 071 072 /** 073 * Gets the active layer that was previously used. 074 * @return The old active layer, <code>null</code> if there is none. 075 */ 076 public Layer getPreviousActiveLayer() { 077 return previousActiveLayer; 078 } 079 080 /** 081 * Gets the data set that was previously used. 082 * @return The data set of {@link #getPreviousEditLayer()}. 083 */ 084 public DataSet getPreviousEditDataSet() { 085 if (previousEditLayer != null) { 086 return previousEditLayer.data; 087 } else { 088 return null; 089 } 090 } 091 092 @Override 093 public MainLayerManager getSource() { 094 return (MainLayerManager) super.getSource(); 095 } 096 } 097 098 /** 099 * This event is fired for {@link LayerAvailabilityListener} 100 * @author Michael Zangl 101 * @since 10508 102 */ 103 public static class LayerAvailabilityEvent extends LayerManagerEvent { 104 private final boolean hasLayers; 105 106 LayerAvailabilityEvent(LayerManager source, boolean hasLayers) { 107 super(source); 108 this.hasLayers = hasLayers; 109 } 110 111 /** 112 * Checks if this layer manager will have layers afterwards 113 * @return true if layers will be added. 114 */ 115 public boolean hasLayers() { 116 return hasLayers; 117 } 118 } 119 120 /** 121 * A listener that gets informed before any layer is displayed and after all layers are removed. 122 * @author Michael Zangl 123 * @since 10508 124 */ 125 public interface LayerAvailabilityListener { 126 /** 127 * This method is called in the UI thread right before the first layer is added. 128 * @param e The event. 129 */ 130 void beforeFirstLayerAdded(LayerAvailabilityEvent e); 131 132 /** 133 * This method is called in the UI thread after the last layer was removed. 134 * @param e The event. 135 */ 136 void afterLastLayerRemoved(LayerAvailabilityEvent e); 137 } 138 139 /** 140 * The layer from the layers list that is currently active. 141 */ 142 private Layer activeLayer; 143 144 /** 145 * The edit layer is the current active data layer. 146 */ 147 private OsmDataLayer editLayer; 148 149 private final List<ActiveLayerChangeListener> activeLayerChangeListeners = new CopyOnWriteArrayList<>(); 150 private final List<LayerAvailabilityListener> layerAvailabilityListeners = new CopyOnWriteArrayList<>(); 151 152 /** 153 * Adds a active/edit layer change listener 154 * 155 * @param listener the listener. 156 */ 157 public synchronized void addActiveLayerChangeListener(ActiveLayerChangeListener listener) { 158 if (activeLayerChangeListeners.contains(listener)) { 159 throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener); 160 } 161 activeLayerChangeListeners.add(listener); 162 } 163 164 /** 165 * Adds a active/edit layer change listener. Fire a fake active-layer-changed-event right after adding 166 * the listener. The previous layers will be null. The listener is notified in the current thread. 167 * @param listener the listener. 168 */ 169 public synchronized void addAndFireActiveLayerChangeListener(ActiveLayerChangeListener listener) { 170 addActiveLayerChangeListener(listener); 171 listener.activeOrEditLayerChanged(new ActiveLayerChangeEvent(this, null, null)); 172 } 173 174 /** 175 * Removes an active/edit layer change listener. 176 * @param listener the listener. 177 */ 178 public synchronized void removeActiveLayerChangeListener(ActiveLayerChangeListener listener) { 179 if (!activeLayerChangeListeners.contains(listener)) { 180 throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener); 181 } 182 activeLayerChangeListeners.remove(listener); 183 } 184 185 /** 186 * Add a new {@link LayerAvailabilityListener}. 187 * @param listener The listener 188 * @since 10508 189 */ 190 public synchronized void addLayerAvailabilityListener(LayerAvailabilityListener listener) { 191 if (!layerAvailabilityListeners.add(listener)) { 192 throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener); 193 } 194 } 195 196 /** 197 * Remove an {@link LayerAvailabilityListener}. 198 * @param listener The listener 199 * @since 10508 200 */ 201 public synchronized void removeLayerAvailabilityListener(LayerAvailabilityListener listener) { 202 if (!layerAvailabilityListeners.remove(listener)) { 203 throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener); 204 } 205 } 206 207 /** 208 * Set the active layer. If the layer is an OsmDataLayer, the edit layer is also changed. 209 * @param layer The active layer. 210 */ 211 public void setActiveLayer(final Layer layer) { 212 // we force this on to the EDT Thread to make events fire from there. 213 // The synchronization lock needs to be held by the EDT. 214 GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer)); 215 } 216 217 protected synchronized void realSetActiveLayer(final Layer layer) { 218 // to be called in EDT thread 219 checkContainsLayer(layer); 220 setActiveLayer(layer, false); 221 } 222 223 private void setActiveLayer(Layer layer, boolean forceEditLayerUpdate) { 224 ActiveLayerChangeEvent event = new ActiveLayerChangeEvent(this, editLayer, activeLayer); 225 activeLayer = layer; 226 if (activeLayer instanceof OsmDataLayer) { 227 editLayer = (OsmDataLayer) activeLayer; 228 } else if (forceEditLayerUpdate) { 229 editLayer = null; 230 } 231 fireActiveLayerChange(event); 232 } 233 234 private void fireActiveLayerChange(ActiveLayerChangeEvent event) { 235 GuiHelper.assertCallFromEdt(); 236 if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousEditLayer() != editLayer) { 237 for (ActiveLayerChangeListener l : activeLayerChangeListeners) { 238 l.activeOrEditLayerChanged(event); 239 } 240 } 241 } 242 243 @Override 244 protected synchronized void realAddLayer(Layer layer) { 245 if (getLayers().isEmpty()) { 246 LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, true); 247 for (LayerAvailabilityListener l : layerAvailabilityListeners) { 248 l.beforeFirstLayerAdded(e); 249 } 250 } 251 super.realAddLayer(layer); 252 253 // update the active layer automatically. 254 if (layer instanceof OsmDataLayer || activeLayer == null) { 255 setActiveLayer(layer); 256 } 257 } 258 259 @Override 260 protected Collection<Layer> realRemoveSingleLayer(Layer layer) { 261 if (layer == activeLayer || layer == editLayer) { 262 Layer nextActive = suggestNextActiveLayer(layer); 263 setActiveLayer(nextActive, true); 264 } 265 266 Collection<Layer> toDelete = super.realRemoveSingleLayer(layer); 267 if (getLayers().isEmpty()) { 268 LayerAvailabilityEvent e = new LayerAvailabilityEvent(this, false); 269 for (LayerAvailabilityListener l : layerAvailabilityListeners) { 270 l.afterLastLayerRemoved(e); 271 } 272 } 273 return toDelete; 274 } 275 276 /** 277 * Determines the next active data layer according to the following 278 * rules: 279 * <ul> 280 * <li>if there is at least one {@link OsmDataLayer} the first one 281 * becomes active</li> 282 * <li>otherwise, the top most layer of any type becomes active</li> 283 * </ul> 284 * 285 * @param except A layer to ignore. 286 * @return the next active data layer 287 */ 288 private Layer suggestNextActiveLayer(Layer except) { 289 List<Layer> layersList = new ArrayList<>(getLayers()); 290 layersList.remove(except); 291 // First look for data layer 292 for (Layer layer : layersList) { 293 if (layer instanceof OsmDataLayer) { 294 return layer; 295 } 296 } 297 298 // Then any layer 299 if (!layersList.isEmpty()) 300 return layersList.get(0); 301 302 // and then give up 303 return null; 304 } 305 306 /** 307 * Replies the currently active layer 308 * 309 * @return the currently active layer (may be null) 310 */ 311 public synchronized Layer getActiveLayer() { 312 return activeLayer; 313 } 314 315 /** 316 * Replies the current edit layer, if any 317 * 318 * @return the current edit layer. May be null. 319 */ 320 public synchronized OsmDataLayer getEditLayer() { 321 return editLayer; 322 } 323 324 /** 325 * Gets the data set of the active edit layer. 326 * @return That data set, <code>null</code> if there is no edit layer. 327 */ 328 public synchronized DataSet getEditDataSet() { 329 if (editLayer != null) { 330 return editLayer.data; 331 } else { 332 return null; 333 } 334 } 335 336 /** 337 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order 338 * first, layer with the highest Z-Order last. 339 * <p> 340 * The active data layer is pulled above all adjacent data layers. 341 * 342 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order 343 * first, layer with the highest Z-Order last. 344 */ 345 public synchronized List<Layer> getVisibleLayersInZOrder() { 346 List<Layer> ret = new ArrayList<>(); 347 // This is set while we delay the addition of the active layer. 348 boolean activeLayerDelayed = false; 349 List<Layer> layers = getLayers(); 350 for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) { 351 Layer l = iterator.previous(); 352 if (!l.isVisible()) { 353 // ignored 354 } else if (l == activeLayer && l instanceof OsmDataLayer) { 355 // delay and add after the current block of OsmDataLayer 356 activeLayerDelayed = true; 357 } else { 358 if (activeLayerDelayed && !(l instanceof OsmDataLayer)) { 359 // add active layer before the current one. 360 ret.add(activeLayer); 361 activeLayerDelayed = false; 362 } 363 // Add this layer now 364 ret.add(l); 365 } 366 } 367 if (activeLayerDelayed) { 368 ret.add(activeLayer); 369 } 370 return ret; 371 } 372 373 @Override 374 protected synchronized void realResetState() { 375 // active and edit layer are unset automatically 376 super.realResetState(); 377 378 activeLayerChangeListeners.clear(); 379 layerAvailabilityListeners.clear(); 380 } 381}