001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer; 003 004import java.util.ArrayList; 005import java.util.List; 006import java.util.ListIterator; 007import java.util.concurrent.CopyOnWriteArrayList; 008 009import org.openstreetmap.josm.data.osm.DataSet; 010import org.openstreetmap.josm.gui.util.GuiHelper; 011 012/** 013 * This class extends the layer manager by adding an active and an edit layer. 014 * <p> 015 * The active layer is the layer the user is currently working on. 016 * <p> 017 * The edit layer is an data layer that we currently work with. 018 * @author Michael Zangl 019 * @since 10279 020 */ 021public class MainLayerManager extends LayerManager { 022 /** 023 * This listener listens to changes of the active or the edit layer. 024 * @author Michael Zangl 025 * 026 */ 027 public interface ActiveLayerChangeListener { 028 /** 029 * Called whenever the active or edit layer changed. 030 * <p> 031 * You can be sure that this layer is still contained in this set. 032 * <p> 033 * Listeners are called in the EDT thread and you can manipulate the layer manager in the current thread. 034 * @param e The change event. 035 */ 036 void activeOrEditLayerChanged(ActiveLayerChangeEvent e); 037 } 038 039 /** 040 * This event is fired whenever the active or the edit layer changes. 041 * @author Michael Zangl 042 */ 043 public class ActiveLayerChangeEvent extends LayerManagerEvent { 044 045 private final OsmDataLayer previousEditLayer; 046 047 private final Layer previousActiveLayer; 048 049 /** 050 * Create a new {@link ActiveLayerChangeEvent} 051 * @param source The source 052 * @param previousEditLayer the previous edit layer 053 * @param previousActiveLayer the previous active layer 054 */ 055 ActiveLayerChangeEvent(MainLayerManager source, OsmDataLayer previousEditLayer, 056 Layer previousActiveLayer) { 057 super(source); 058 this.previousEditLayer = previousEditLayer; 059 this.previousActiveLayer = previousActiveLayer; 060 } 061 062 /** 063 * Gets the edit layer that was previously used. 064 * @return The old edit layer, <code>null</code> if there is none. 065 */ 066 public OsmDataLayer getPreviousEditLayer() { 067 return previousEditLayer; 068 } 069 070 /** 071 * Gets the active layer that was previously used. 072 * @return The old active layer, <code>null</code> if there is none. 073 */ 074 public Layer getPreviousActiveLayer() { 075 return previousActiveLayer; 076 } 077 078 @Override 079 public MainLayerManager getSource() { 080 return (MainLayerManager) super.getSource(); 081 } 082 } 083 084 /** 085 * The layer from the layers list that is currently active. 086 */ 087 private Layer activeLayer; 088 089 /** 090 * The edit layer is the current active data layer. 091 */ 092 private OsmDataLayer editLayer; 093 094 private final List<ActiveLayerChangeListener> activeLayerChangeListeners = new CopyOnWriteArrayList<>(); 095 096 /** 097 * Adds a active/edit layer change listener 098 * 099 * @param listener the listener. 100 * @param initialFire fire a fake active-layer-changed-event right after adding 101 * the listener. The previous layers will be null. The listener is notified in the current thread. 102 */ 103 public synchronized void addActiveLayerChangeListener(ActiveLayerChangeListener listener, boolean initialFire) { 104 if (activeLayerChangeListeners.contains(listener)) { 105 throw new IllegalArgumentException("Attempted to add listener that was already in list: " + listener); 106 } 107 activeLayerChangeListeners.add(listener); 108 if (initialFire) { 109 listener.activeOrEditLayerChanged(new ActiveLayerChangeEvent(this, null, null)); 110 } 111 } 112 113 /** 114 * Removes an active/edit layer change listener. 115 * @param listener the listener. 116 */ 117 public synchronized void removeActiveLayerChangeListener(ActiveLayerChangeListener listener) { 118 if (!activeLayerChangeListeners.contains(listener)) { 119 throw new IllegalArgumentException("Attempted to remove listener that was not in list: " + listener); 120 } 121 activeLayerChangeListeners.remove(listener); 122 } 123 124 /** 125 * Set the active layer. If the layer is an OsmDataLayer, the edit layer is also changed. 126 * @param layer The active layer. 127 */ 128 public void setActiveLayer(final Layer layer) { 129 // we force this on to the EDT Thread to make events fire from there. 130 // The synchronization lock needs to be held by the EDT. 131 GuiHelper.runInEDTAndWaitWithException(new Runnable() { 132 @Override 133 public void run() { 134 realSetActiveLayer(layer); 135 } 136 }); 137 } 138 139 protected synchronized void realSetActiveLayer(final Layer layer) { 140 // to be called in EDT thread 141 checkContainsLayer(layer); 142 setActiveLayer(layer, false); 143 } 144 145 private void setActiveLayer(Layer layer, boolean forceEditLayerUpdate) { 146 ActiveLayerChangeEvent event = new ActiveLayerChangeEvent(this, editLayer, activeLayer); 147 activeLayer = layer; 148 if (activeLayer instanceof OsmDataLayer) { 149 editLayer = (OsmDataLayer) activeLayer; 150 } else if (forceEditLayerUpdate) { 151 editLayer = null; 152 } 153 fireActiveLayerChange(event); 154 } 155 156 private void fireActiveLayerChange(ActiveLayerChangeEvent event) { 157 GuiHelper.assertCallFromEdt(); 158 if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousEditLayer() != editLayer) { 159 for (ActiveLayerChangeListener l : activeLayerChangeListeners) { 160 l.activeOrEditLayerChanged(event); 161 } 162 } 163 } 164 165 @Override 166 protected synchronized void realAddLayer(Layer layer) { 167 super.realAddLayer(layer); 168 169 // update the active layer automatically. 170 if (layer instanceof OsmDataLayer || activeLayer == null) { 171 setActiveLayer(layer); 172 } 173 } 174 175 @Override 176 protected synchronized void realRemoveLayer(Layer layer) { 177 if (layer == activeLayer || layer == editLayer) { 178 Layer nextActive = suggestNextActiveLayer(layer); 179 setActiveLayer(nextActive, true); 180 } 181 182 super.realRemoveLayer(layer); 183 } 184 185 /** 186 * Determines the next active data layer according to the following 187 * rules: 188 * <ul> 189 * <li>if there is at least one {@link OsmDataLayer} the first one 190 * becomes active</li> 191 * <li>otherwise, the top most layer of any type becomes active</li> 192 * </ul> 193 * 194 * @param except A layer to ignore. 195 * @return the next active data layer 196 */ 197 private Layer suggestNextActiveLayer(Layer except) { 198 List<Layer> layersList = new ArrayList<>(getLayers()); 199 layersList.remove(except); 200 // First look for data layer 201 for (Layer layer : layersList) { 202 if (layer instanceof OsmDataLayer) { 203 return layer; 204 } 205 } 206 207 // Then any layer 208 if (!layersList.isEmpty()) 209 return layersList.get(0); 210 211 // and then give up 212 return null; 213 } 214 215 /** 216 * Replies the currently active layer 217 * 218 * @return the currently active layer (may be null) 219 */ 220 public synchronized Layer getActiveLayer() { 221 return activeLayer; 222 } 223 224 /** 225 * Replies the current edit layer, if any 226 * 227 * @return the current edit layer. May be null. 228 */ 229 public synchronized OsmDataLayer getEditLayer() { 230 return editLayer; 231 } 232 233 /** 234 * Gets the data set of the active edit layer. 235 * @return That data set, <code>null</code> if there is no edit layer. 236 */ 237 public synchronized DataSet getEditDataSet() { 238 if (editLayer != null) { 239 return editLayer.data; 240 } else { 241 return null; 242 } 243 } 244 245 246 /** 247 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order 248 * first, layer with the highest Z-Order last. 249 * <p> 250 * The active data layer is pulled above all adjacent data layers. 251 * 252 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order 253 * first, layer with the highest Z-Order last. 254 */ 255 public synchronized List<Layer> getVisibleLayersInZOrder() { 256 List<Layer> ret = new ArrayList<>(); 257 // This is set while we delay the addition of the active layer. 258 boolean activeLayerDelayed = false; 259 List<Layer> layers = getLayers(); 260 for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) { 261 Layer l = iterator.previous(); 262 if (!l.isVisible()) { 263 // ignored 264 } else if (l == activeLayer && l instanceof OsmDataLayer) { 265 // delay and add after the current block of OsmDataLayer 266 activeLayerDelayed = true; 267 } else { 268 if (activeLayerDelayed && !(l instanceof OsmDataLayer)) { 269 // add active layer before the current one. 270 ret.add(activeLayer); 271 activeLayerDelayed = false; 272 } 273 // Add this layer now 274 ret.add(l); 275 } 276 } 277 if (activeLayerDelayed) { 278 ret.add(activeLayer); 279 } 280 return ret; 281 } 282}