Ticket #11838: layer-manager.patch

File layer-manager.patch, 60.2 KB (added by michael2402, 11 years ago)
  • src/org/openstreetmap/josm/Main.java

    diff --git a/src/org/openstreetmap/josm/Main.java b/src/org/openstreetmap/josm/Main.java
    index c52a9c2..adabb81 100644
    a b import org.openstreetmap.josm.gui.help.HelpUtil;  
    8989import org.openstreetmap.josm.gui.io.SaveLayersDialog;
    9090import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
    9191import org.openstreetmap.josm.gui.layer.Layer;
     92import org.openstreetmap.josm.gui.layer.LayerManagerWithActive;
    9293import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    9394import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener;
    9495import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
    public abstract class Main {  
    186187
    187188    /**
    188189     * The MapFrame. Use {@link Main#setMapFrame} to set or clear it.
     190     * <p>
     191     * There should be no need to access this to access any map data. Use {@link #layerManager} instead.
    189192     */
    190193    public static MapFrame map;
    191194
    192195    /**
     196     * Provides access to the layers displayed in the main view.
     197     */
     198    public static LayerManagerWithActive layerManager = new LayerManagerWithActive();
     199
     200    /**
    193201     * The toolbar preference control to register new actions.
    194202     */
    195203    public static volatile ToolbarPreferences toolbar;
  • src/org/openstreetmap/josm/gui/MapFrame.java

    diff --git a/src/org/openstreetmap/josm/gui/MapFrame.java b/src/org/openstreetmap/josm/gui/MapFrame.java
    index 723ebd9..a413a3a 100644
    a b import org.openstreetmap.josm.gui.dialogs.UserListDialog;  
    7575import org.openstreetmap.josm.gui.dialogs.ValidatorDialog;
    7676import org.openstreetmap.josm.gui.dialogs.properties.PropertiesDialog;
    7777import org.openstreetmap.josm.gui.layer.Layer;
     78import org.openstreetmap.josm.gui.layer.LayerManager;
    7879import org.openstreetmap.josm.gui.util.AdvancedKeyPressDetector;
    7980import org.openstreetmap.josm.tools.Destroyable;
    8081import org.openstreetmap.josm.tools.GBC;
    public class MapFrame extends JPanel implements Destroyable, LayerChangeListener  
    9798
    9899    /**
    99100     * The view control displayed.
     101     * <p>
     102     * Accessing this is discouraged. Use the {@link LayerManager} to access map data.
    100103     */
    101104    public final MapView mapView;
    102105
    public class MapFrame extends JPanel implements Destroyable, LayerChangeListener  
    174177        setSize(400, 400);
    175178        setLayout(new BorderLayout());
    176179
    177         mapView = new MapView(contentPane, viewportData);
     180        mapView = new MapView(Main.layerManager, contentPane, viewportData);
    178181        if (!GraphicsEnvironment.isHeadless()) {
    179182            new FileDrop(mapView);
    180183        }
    public class MapFrame extends JPanel implements Destroyable, LayerChangeListener  
    724727                mapMode.enterMode();
    725728            }
    726729            // invalidate repaint cache
    727             Main.map.mapView.preferenceChanged(null);
     730            mapView.preferenceChanged(null);
    728731        }
    729732
    730733        // After all listeners notice new layer, some buttons will be disabled/enabled
  • src/org/openstreetmap/josm/gui/MapView.java

    diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java
    index eff7bb6..f7a71da 100644
    a b import java.util.ArrayList;  
    2525import java.util.Arrays;
    2626import java.util.Collection;
    2727import java.util.Collections;
    28 import java.util.EnumSet;
    2928import java.util.LinkedHashSet;
    3029import java.util.List;
    31 import java.util.ListIterator;
    3230import java.util.Set;
    3331import java.util.concurrent.CopyOnWriteArrayList;
    3432
    import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache  
    5755import org.openstreetmap.josm.gui.layer.GpxLayer;
    5856import org.openstreetmap.josm.gui.layer.ImageryLayer;
    5957import org.openstreetmap.josm.gui.layer.Layer;
     58import org.openstreetmap.josm.gui.layer.LayerManager;
     59import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
     60import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
     61import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
     62import org.openstreetmap.josm.gui.layer.LayerManagerWithActive;
     63import org.openstreetmap.josm.gui.layer.LayerManagerWithActive.ActiveLayerChangeEvent;
     64import org.openstreetmap.josm.gui.layer.LayerManagerWithActive.ActiveLayerChangeListener;
    6065import org.openstreetmap.josm.gui.layer.MapViewPaintable;
    6166import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    6267import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
    import org.openstreetmap.josm.tools.Utils;  
    8186 * @author imi
    8287 */
    8388public class MapView extends NavigatableComponent
    84 implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.LayerStateChangeListener {
    85 
     89implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.LayerStateChangeListener,
     90LayerManager.LayerChangeListener, LayerManagerWithActive.ActiveLayerChangeListener {
    8691    /**
    8792     * Interface to notify listeners of a layer change.
    8893     * @author imi
    8994     */
     95    @Deprecated
    9096    public interface LayerChangeListener {
    9197
    9298        /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    112118    /**
    113119     * An interface that needs to be implemented in order to listen for changes to the active edit layer.
    114120     */
     121    @Deprecated
    115122    public interface EditLayerChangeListener {
    116123
    117124        /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    122129        void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer);
    123130    }
    124131
    125     public boolean viewportFollowing = false;
     132    protected static class LayerChangeAdapter implements ActiveLayerChangeListener, LayerManager.LayerChangeListener {
    126133
    127     /**
    128      * the layer listeners
    129      */
    130     private static final CopyOnWriteArrayList<LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<>();
    131     private static final CopyOnWriteArrayList<EditLayerChangeListener> editLayerChangeListeners = new CopyOnWriteArrayList<>();
     134        private final LayerChangeListener wrapped;
     135        private boolean receiveOneInitialFire;
     136
     137        public LayerChangeAdapter(LayerChangeListener wrapped) {
     138            this.wrapped = wrapped;
     139        }
     140
     141        public LayerChangeAdapter(LayerChangeListener wrapped, boolean initialFire) {
     142            this(wrapped);
     143            this.receiveOneInitialFire = initialFire;
     144        }
     145
     146        @Override
     147        public void layerAdded(LayerAddEvent e) {
     148            wrapped.layerAdded(e.getAddedLayer());
     149        }
     150
     151        @Override
     152        public void layerRemoving(LayerRemoveEvent e) {
     153            wrapped.layerRemoved(e.getRemovedLayer());
     154        }
     155
     156        @Override
     157        public void layerOrderChanged(LayerOrderChangeEvent e) {
     158            // not in old API
     159        }
     160
     161        @Override
     162        public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
     163            Layer oldActive = receiveOneInitialFire ? null : e.getPreviousActiveLayer();
     164            Layer newActive = e.getSource().getActiveLayer();
     165            if (oldActive != newActive) {
     166                wrapped.activeLayerChange(oldActive, newActive);
     167            }
     168            receiveOneInitialFire = false;
     169        }
     170
     171        @Override
     172        public int hashCode() {
     173            final int prime = 31;
     174            int result = 1;
     175            result = prime * result + ((wrapped == null) ? 0 : wrapped.hashCode());
     176            return result;
     177        }
     178
     179        @Override
     180        public boolean equals(Object obj) {
     181            if (this == obj)
     182                return true;
     183            if (obj == null)
     184                return false;
     185            if (getClass() != obj.getClass())
     186                return false;
     187            LayerChangeAdapter other = (LayerChangeAdapter) obj;
     188            if (wrapped == null) {
     189                if (other.wrapped != null)
     190                    return false;
     191            } else if (!wrapped.equals(other.wrapped))
     192                return false;
     193            return true;
     194        }
     195
     196        @Override
     197        public String toString() {
     198            return "LayerChangeAdapter [wrapped=" + wrapped + "]";
     199        }
     200
     201    }
     202
     203    protected static class EditLayerChangeAdapter implements ActiveLayerChangeListener {
     204
     205        private final EditLayerChangeListener wrapped;
     206
     207        public EditLayerChangeAdapter(EditLayerChangeListener wrapped) {
     208            this.wrapped = wrapped;
     209        }
     210
     211        @Override
     212        public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
     213            OsmDataLayer oldLayer = e.getPreviousEditLayer();
     214            OsmDataLayer newLayer = e.getSource().getEditLayer();
     215            if (oldLayer != newLayer) {
     216                wrapped.editLayerChanged(oldLayer, newLayer);
     217            }
     218        }
     219
     220        @Override
     221        public int hashCode() {
     222            final int prime = 31;
     223            int result = 1;
     224            result = prime * result + ((wrapped == null) ? 0 : wrapped.hashCode());
     225            return result;
     226        }
     227
     228        @Override
     229        public boolean equals(Object obj) {
     230            if (this == obj)
     231                return true;
     232            if (obj == null)
     233                return false;
     234            if (getClass() != obj.getClass())
     235                return false;
     236            EditLayerChangeAdapter other = (EditLayerChangeAdapter) obj;
     237            if (wrapped == null) {
     238                if (other.wrapped != null)
     239                    return false;
     240            } else if (!wrapped.equals(other.wrapped))
     241                return false;
     242            return true;
     243        }
     244
     245        @Override
     246        public String toString() {
     247            return "EditLayerChangeAdapter [wrapped=" + wrapped + "]";
     248        }
     249
     250    }
    132251
    133252    /**
    134253     * Removes a layer change listener
    135254     *
    136255     * @param listener the listener. Ignored if null or already registered.
    137256     */
     257    @Deprecated
    138258    public static void removeLayerChangeListener(LayerChangeListener listener) {
    139         layerChangeListeners.remove(listener);
     259        LayerChangeAdapter adapter = new LayerChangeAdapter(listener);
     260        Main.layerManager.removeLayerChangeListener(adapter);
     261        Main.layerManager.removeActiveLayerChangeListener(adapter);
    140262    }
    141263
     264    @Deprecated
    142265    public static void removeEditLayerChangeListener(EditLayerChangeListener listener) {
    143         editLayerChangeListeners.remove(listener);
     266        Main.layerManager.removeActiveLayerChangeListener(new EditLayerChangeAdapter(listener));
    144267    }
    145268
    146269    /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    148271     *
    149272     * @param listener the listener. Ignored if null or already registered.
    150273     */
     274    @Deprecated
    151275    public static void addLayerChangeListener(LayerChangeListener listener) {
    152         if (listener != null) {
    153             layerChangeListeners.addIfAbsent(listener);
    154         }
     276        addLayerChangeListener(listener, false);
    155277    }
    156278
    157279    /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    161283     * @param initialFire fire an active-layer-changed-event right after adding
    162284     * the listener in case there is a layer present (should be)
    163285     */
     286    @Deprecated
    164287    public static void addLayerChangeListener(LayerChangeListener listener, boolean initialFire) {
    165         addLayerChangeListener(listener);
    166         if (initialFire && Main.isDisplayingMapView()) {
    167             listener.activeLayerChange(null, Main.map.mapView.getActiveLayer());
     288        if (listener != null) {
     289            initialFire = initialFire && Main.isDisplayingMapView();
     290
     291            LayerChangeAdapter adapter = new LayerChangeAdapter(listener, initialFire);
     292            Main.layerManager.addLayerChangeListener(adapter, false);
     293            Main.layerManager.addActiveLayerChangeListener(adapter, initialFire);
     294            adapter.receiveOneInitialFire = false;
    168295        }
    169296    }
    170297
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    175302     * @param initialFire fire an edit-layer-changed-event right after adding
    176303     * the listener in case there is an edit layer present
    177304     */
     305    @Deprecated
    178306    public static void addEditLayerChangeListener(EditLayerChangeListener listener, boolean initialFire) {
    179         addEditLayerChangeListener(listener);
    180         if (initialFire && Main.isDisplayingMapView() && Main.map.mapView.getEditLayer() != null) {
    181             listener.editLayerChanged(null, Main.map.mapView.getEditLayer());
     307        if (listener != null) {
     308            Main.layerManager.addActiveLayerChangeListener(new EditLayerChangeAdapter(listener), initialFire && Main.isDisplayingMapView() && Main.map.mapView.getEditLayer() != null);
    182309        }
    183310    }
    184311
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    187314     *
    188315     * @param listener the listener. Ignored if null or already registered.
    189316     */
     317    @Deprecated
    190318    public static void addEditLayerChangeListener(EditLayerChangeListener listener) {
    191         if (listener != null) {
    192             editLayerChangeListeners.addIfAbsent(listener);
    193         }
    194     }
    195 
    196     /**
    197      * Calls the {@link LayerChangeListener#activeLayerChange(Layer, Layer)} method of all listeners.
    198      *
    199      * @param oldLayer The old layer
    200      * @param newLayer The new active layer.
    201      */
    202     protected void fireActiveLayerChanged(Layer oldLayer, Layer newLayer) {
    203         for (LayerChangeListener l : layerChangeListeners) {
    204             l.activeLayerChange(oldLayer, newLayer);
    205         }
     319        addEditLayerChangeListener(listener, false);
    206320    }
    207321
    208     protected void fireLayerAdded(Layer newLayer) {
    209         for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
    210             l.layerAdded(newLayer);
    211         }
    212     }
    213 
    214     protected void fireLayerRemoved(Layer layer) {
    215         for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
    216             l.layerRemoved(layer);
    217         }
    218     }
    219 
    220     protected void fireEditLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
    221         for (EditLayerChangeListener l : editLayerChangeListeners) {
    222             l.editLayerChanged(oldLayer, newLayer);
    223         }
    224     }
     322    public boolean viewportFollowing = false;
    225323
    226     /**
    227      * A list of all layers currently loaded. Locked by {@link #layerLock}.
    228      */
    229     private final transient List<Layer> layers = new ArrayList<>();
    230 
    231     /**
    232      * This lock manages concurrent access to {@link #layers},
    233      * {@link #editLayer} and {@link #activeLayer}.
    234      * <p>
    235      * The read lock is always held while those fields are read or while layer change listeners are fired.
    236      */
    237     //private final ReentrantReadWriteLock layerLock = new ReentrantReadWriteLock();
     324    private LayerManagerWithActive layerManager = new LayerManagerWithActive();
    238325
    239326    /**
    240327     * The play head marker: there is only one of these so it isn't in any specific layer
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    242329    public transient PlayHeadMarker playHeadMarker = null;
    243330
    244331    /**
    245      * The layer from the layers list that is currently active. Locked by {@link #layerLock}.
    246      */
    247     private transient Layer activeLayer;
    248 
    249     /**
    250      * The edit layer is the current active data layer. Locked by {@link #layerLock}.
    251      */
    252     private transient OsmDataLayer editLayer;
    253 
    254     /**
    255332     * The last event performed by mouse.
    256333     */
    257334    public MouseEvent lastMEvent = new MouseEvent(this, 0, 0, 0, 0, 0, 0, false); // In case somebody reads it before first mouse move
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    275352
    276353    /**
    277354     * Constructs a new {@code MapView}.
     355     * @param layerManager The layers to display.
    278356     * @param contentPane The content pane used to register shortcuts in its
    279357     * {@link InputMap} and {@link ActionMap}
    280358     * @param viewportData the initial viewport of the map. Can be null, then
    281359     * the viewport is derived from the layer data.
    282360     */
    283     public MapView(final JPanel contentPane, final ViewportData viewportData) {
     361    public MapView(LayerManagerWithActive layerManager, final JPanel contentPane, final ViewportData viewportData) {
     362        this.layerManager = layerManager;
    284363        initialViewport = viewportData;
     364        layerManager.addLayerChangeListener(this);
     365        layerManager.addActiveLayerChangeListener(this, false);
    285366        Main.pref.addPreferenceChangeListener(this);
    286367
    287368        addComponentListener(new ComponentAdapter() {
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    353434    }
    354435
    355436    /**
    356      * Adds a GPX layer. A GPX layer is added below the lowest data layer.
    357      * <p>
    358      * Does not call {@link #fireLayerAdded(Layer)}.
    359      *
    360      * @param layer the GPX layer
    361      */
    362     protected void addGpxLayer(GpxLayer layer) {
    363         synchronized (layers) {
    364             if (layers.isEmpty()) {
    365                 layers.add(layer);
    366                 return;
    367             }
    368             for (int i = layers.size()-1; i >= 0; i--) {
    369                 if (layers.get(i) instanceof OsmDataLayer) {
    370                     if (i == layers.size()-1) {
    371                         layers.add(layer);
    372                     } else {
    373                         layers.add(i+1, layer);
    374                     }
    375                     return;
    376                 }
    377             }
    378             layers.add(0, layer);
    379         }
    380     }
    381 
    382     /**
    383437     * Add a layer to the current MapView. The layer will be added at topmost
    384438     * position.
    385439     * @param layer The layer to add
    386440     */
     441    @Deprecated
    387442    public void addLayer(Layer layer) {
    388         boolean isOsmDataLayer = layer instanceof OsmDataLayer;
    389         EnumSet<LayerListenerType> listenersToFire = EnumSet.noneOf(LayerListenerType.class);
    390         Layer oldActiveLayer = activeLayer;
    391         OsmDataLayer oldEditLayer = editLayer;
    392 
    393         synchronized (layers) {
    394             if (layer instanceof MarkerLayer && playHeadMarker == null) {
    395                 playHeadMarker = PlayHeadMarker.create();
    396             }
    397 
    398             if (layer instanceof GpxLayer) {
    399                 addGpxLayer((GpxLayer) layer);
    400             } else if (layers.isEmpty()) {
    401                 layers.add(layer);
    402             } else if (layer.isBackgroundLayer()) {
    403                 int i = 0;
    404                 for (; i < layers.size(); i++) {
    405                     if (layers.get(i).isBackgroundLayer()) {
    406                         break;
    407                     }
    408                 }
    409                 layers.add(i, layer);
    410             } else {
    411                 layers.add(0, layer);
    412             }
    413 
    414             if (isOsmDataLayer || oldActiveLayer == null) {
    415                 // autoselect the new layer
    416                 listenersToFire.addAll(setActiveLayer(layer, true));
    417             }
    418 
    419             if (isOsmDataLayer) {
    420                 ((OsmDataLayer) layer).addLayerStateChangeListener(this);
    421             }
     443        layerManager.addLayer(layer);
     444    }
    422445
    423             layer.addPropertyChangeListener(this);
    424             Main.addProjectionChangeListener(layer);
    425             AudioPlayer.reset();
     446    @Override
     447    public void layerAdded(LayerAddEvent e) {
     448        Layer layer = e.getAddedLayer();
     449        if (layer instanceof MarkerLayer && playHeadMarker == null) {
     450            playHeadMarker = PlayHeadMarker.create();
    426451        }
    427         fireLayerAdded(layer);
    428         onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
    429452
    430         if (!listenersToFire.isEmpty()) {
    431             repaint();
     453        boolean isOsmDataLayer = layer instanceof OsmDataLayer;
     454        if (isOsmDataLayer) {
     455            ((OsmDataLayer) layer).addLayerStateChangeListener(this);
    432456        }
     457
     458        layer.addPropertyChangeListener(this);
     459        Main.addProjectionChangeListener(layer);
     460        AudioPlayer.reset();
     461
     462        repaint();
    433463    }
    434464
    435465    @Override
     466    @Deprecated
    436467    protected DataSet getCurrentDataSet() {
    437         synchronized (layers) {
    438             if (editLayer != null)
    439                 return editLayer.data;
    440             else
    441                 return null;
    442         }
     468        return layerManager.getEditDataSet();
    443469    }
    444470
    445471    /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    448474     * @return true if the active data layer (edit layer) is drawable, false otherwise
    449475     */
    450476    public boolean isActiveLayerDrawable() {
    451         synchronized (layers) {
    452             return editLayer != null;
    453         }
     477         return getEditLayer() != null;
    454478    }
    455479
    456480    /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    459483     * @return true if the active data layer (edit layer) is visible, false otherwise
    460484     */
    461485    public boolean isActiveLayerVisible() {
    462         synchronized (layers) {
    463             return isActiveLayerDrawable() && editLayer.isVisible();
    464         }
    465     }
    466 
    467     /**
    468      * Determines the next active data layer according to the following
    469      * rules:
    470      * <ul>
    471      *   <li>if there is at least one {@link OsmDataLayer} the first one
    472      *     becomes active</li>
    473      *   <li>otherwise, the top most layer of any type becomes active</li>
    474      * </ul>
    475      *
    476      * @return the next active data layer
    477      */
    478     protected Layer determineNextActiveLayer(List<Layer> layersList) {
    479         // First look for data layer
    480         for (Layer layer:layersList) {
    481             if (layer instanceof OsmDataLayer)
    482                 return layer;
    483         }
    484 
    485         // Then any layer
    486         if (!layersList.isEmpty())
    487             return layersList.get(0);
    488 
    489         // and then give up
    490         return null;
    491 
     486        OsmDataLayer e = getEditLayer();
     487        return e != null && e.isVisible();
    492488    }
    493489
    494490    /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    496492     * an LayerChange event is fired.
    497493     * @param layer The layer to remove
    498494     */
     495    @Deprecated
    499496    public void removeLayer(Layer layer) {
    500         EnumSet<LayerListenerType> listenersToFire = EnumSet.noneOf(LayerListenerType.class);
    501         Layer oldActiveLayer = activeLayer;
    502         OsmDataLayer oldEditLayer = editLayer;
    503 
    504         synchronized (layers) {
    505             List<Layer> layersList = new ArrayList<>(layers);
    506 
    507             if (!layersList.remove(layer))
    508                 return;
    509 
    510             listenersToFire = setEditLayer(layersList);
    511 
    512             if (layer == activeLayer) {
    513                 listenersToFire.addAll(setActiveLayer(determineNextActiveLayer(layersList), false));
    514             }
    515 
    516             if (layer instanceof OsmDataLayer) {
    517                 ((OsmDataLayer) layer).removeLayerPropertyChangeListener(this);
    518             }
     497        layerManager.removeLayer(layer);
     498    }
    519499
    520             layers.remove(layer);
    521             Main.removeProjectionChangeListener(layer);
    522             layer.removePropertyChangeListener(this);
    523             layer.destroy();
    524             AudioPlayer.reset();
     500    @Override
     501    public void layerRemoving(LayerRemoveEvent e) {
     502        Layer layer = e.getRemovedLayer();
     503        if (layer instanceof OsmDataLayer) {
     504            ((OsmDataLayer) layer).removeLayerPropertyChangeListener(this);
    525505        }
    526         onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
    527         fireLayerRemoved(layer);
    528506
    529         repaint();
    530     }
     507        Main.removeProjectionChangeListener(layer);
     508        layer.removePropertyChangeListener(this);
     509        layer.destroy();
     510        AudioPlayer.reset();
    531511
    532     private void onEditLayerChanged(OsmDataLayer oldEditLayer) {
    533         fireEditLayerChanged(oldEditLayer, editLayer);
    534         refreshTitle();
     512        repaint();
    535513    }
    536514
    537515    private boolean virtualNodesEnabled = false;
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    560538     * @param pos       The new position of the layer
    561539     */
    562540    public void moveLayer(Layer layer, int pos) {
    563         EnumSet<LayerListenerType> listenersToFire;
    564         Layer oldActiveLayer = activeLayer;
    565         OsmDataLayer oldEditLayer = editLayer;
    566 
    567         synchronized (layers) {
    568             int curLayerPos = layers.indexOf(layer);
    569             if (curLayerPos == -1)
    570                 throw new IllegalArgumentException(tr("Layer not in list."));
    571             if (pos == curLayerPos)
    572                 return; // already in place.
    573             layers.remove(curLayerPos);
    574             if (pos >= layers.size()) {
    575                 layers.add(layer);
    576             } else {
    577                 layers.add(pos, layer);
    578             }
    579             listenersToFire = setEditLayer(layers);
    580             AudioPlayer.reset();
    581         }
    582         onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
     541        layerManager.moveLayer(layer, pos);
     542    }
    583543
     544    @Override
     545    public void layerOrderChanged(LayerOrderChangeEvent e) {
     546        AudioPlayer.reset();
    584547        repaint();
    585548    }
    586549
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    590553     * @return The index in the list.
    591554     * @throws IllegalArgumentException if that layer does not belong to this view.
    592555     */
     556    @Deprecated
    593557    public int getLayerPos(Layer layer) {
    594558        int curLayerPos;
    595         synchronized (layers) {
    596             curLayerPos = layers.indexOf(layer);
    597         }
     559            curLayerPos = getAllLayersAsList().indexOf(layer);
    598560        if (curLayerPos == -1)
    599561            throw new IllegalArgumentException(tr("Layer not in list."));
    600562        return curLayerPos;
    601563    }
    602564
    603     /**
    604      * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
    605      * first, layer with the highest Z-Order last.
    606      * <p>
    607      * The active data layer is pulled above all adjacent data layers.
    608      *
    609      * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
    610      * first, layer with the highest Z-Order last.
    611      */
    612     public List<Layer> getVisibleLayersInZOrder() {
    613         synchronized (layers) {
    614             List<Layer> ret = new ArrayList<>();
    615             // This is set while we delay the addition of the active layer.
    616             boolean activeLayerDelayed = false;
    617             for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) {
    618                 Layer l = iterator.previous();
    619                 if (!l.isVisible()) {
    620                     // ignored
    621                 } else if (l == activeLayer && l instanceof OsmDataLayer) {
    622                     // delay and add after the current block of OsmDataLayer
    623                     activeLayerDelayed = true;
    624                 } else {
    625                     if (activeLayerDelayed && !(l instanceof OsmDataLayer)) {
    626                         // add active layer before the current one.
    627                         ret.add(activeLayer);
    628                         activeLayerDelayed = false;
    629                     }
    630                     // Add this layer now
    631                     ret.add(l);
    632                 }
    633             }
    634             if (activeLayerDelayed) {
    635                 ret.add(activeLayer);
    636             }
    637             return ret;
    638         }
    639     }
    640565
    641566    private void paintLayer(Layer layer, Graphics2D g, Bounds box) {
    642567        if (layer.getOpacity() < 1) {
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    655580            return;
    656581        }
    657582
    658         List<Layer> visibleLayers = getVisibleLayersInZOrder();
     583        List<Layer> visibleLayers = layerManager.getVisibleLayersInZOrder();
    659584
    660585        int nonChangedLayersCount = 0;
    661586        for (Layer l: visibleLayers) {
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    822747    /**
    823748     * @return An unmodifiable collection of all layers
    824749     */
     750    @Deprecated
    825751    public Collection<Layer> getAllLayers() {
    826         synchronized (layers) {
    827             return Collections.unmodifiableCollection(new ArrayList<>(layers));
    828         }
     752        return layerManager.getLayers();
    829753    }
    830754
    831755    /**
    832756     * @return An unmodifiable ordered list of all layers
    833757     */
     758    @Deprecated
    834759    public List<Layer> getAllLayersAsList() {
    835         synchronized (layers) {
    836             return Collections.unmodifiableList(new ArrayList<>(layers));
    837         }
     760        return layerManager.getLayers();
    838761    }
    839762
    840763    /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    848771     * @param ofType The layer type.
    849772     * @return an unmodifiable list of layers of a certain type.
    850773     */
     774    @Deprecated
    851775    public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
    852         return new ArrayList<>(Utils.filteredCollection(getAllLayers(), ofType));
     776        return layerManager.getLayersOfType(ofType);
    853777    }
    854778
    855779    /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    857781     *
    858782     * @return the number of layers managed by this map view
    859783     */
     784    @Deprecated
    860785    public int getNumLayers() {
    861         synchronized (layers) {
    862             return layers.size();
    863         }
     786        return getAllLayers().size();
    864787    }
    865788
    866789    /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    868791     *
    869792     * @return true if there is at least one layer in this map view
    870793     */
     794    @Deprecated
    871795    public boolean hasLayers() {
    872796        return getNumLayers() > 0;
    873797    }
    874798
    875799    /**
    876      * Sets the active edit layer.
    877      * <p>
    878      * You must own a write {@link #layerLock} when calling this method.
    879      * @param layersList A list to select that layer from.
    880      * @return A list of change listeners that should be fired using {@link #onActiveEditLayerChanged(Layer, OsmDataLayer, EnumSet)}
    881      */
    882     private EnumSet<LayerListenerType> setEditLayer(List<Layer> layersList) {
    883         final OsmDataLayer newEditLayer = findNewEditLayer(layersList);
    884 
    885         // Set new edit layer
    886         if (newEditLayer != editLayer) {
    887             if (newEditLayer == null) {
    888                 // Note: Unsafe to call while layer write lock is held.
    889                 getCurrentDataSet().setSelected();
    890             }
    891 
    892             editLayer = newEditLayer;
    893             return EnumSet.of(LayerListenerType.EDIT_LAYER_CHANGE);
    894         } else {
    895             return EnumSet.noneOf(LayerListenerType.class);
    896         }
    897 
    898     }
    899 
    900     private OsmDataLayer findNewEditLayer(List<Layer> layersList) {
    901         OsmDataLayer newEditLayer = layersList.contains(editLayer) ? editLayer : null;
    902         // Find new edit layer
    903         if (activeLayer != editLayer || !layersList.contains(editLayer)) {
    904             if (activeLayer instanceof OsmDataLayer && layersList.contains(activeLayer)) {
    905                 newEditLayer = (OsmDataLayer) activeLayer;
    906             } else {
    907                 for (Layer layer:layersList) {
    908                     if (layer instanceof OsmDataLayer) {
    909                         newEditLayer = (OsmDataLayer) layer;
    910                         break;
    911                     }
    912                 }
    913             }
    914         }
    915         return newEditLayer;
    916     }
    917 
    918     /**
    919800     * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance
    920801     * of {@link OsmDataLayer} also sets {@link #editLayer} to <code>layer</code>.
    921802     *
    922803     * @param layer the layer to be activate; must be one of the layers in the list of layers
    923804     * @throws IllegalArgumentException if layer is not in the lis of layers
    924805     */
     806    @Deprecated
    925807    public void setActiveLayer(Layer layer) {
    926         EnumSet<LayerListenerType> listenersToFire;
    927         Layer oldActiveLayer;
    928         OsmDataLayer oldEditLayer;
    929 
    930         synchronized (layers) {
    931             oldActiveLayer = activeLayer;
    932             oldEditLayer = editLayer;
    933             listenersToFire = setActiveLayer(layer, true);
    934         }
    935         onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
    936 
    937         repaint();
     808        layerManager.setActiveLayer(layer);
    938809    }
    939 
    940     /**
    941      * Sets the active layer. Propagates this change to all map buttons.
    942      * @param layer The layer to be active.
    943      * @param setEditLayer if this is <code>true</code>, the edit layer is also set.
    944      * @return A list of change listeners that should be fired using {@link #onActiveEditLayerChanged(Layer, OsmDataLayer, EnumSet)}
    945      */
    946     private EnumSet<LayerListenerType> setActiveLayer(final Layer layer, boolean setEditLayer) {
    947         if (layer != null && !layers.contains(layer))
    948             throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString()));
    949 
    950         if (layer == activeLayer)
    951             return EnumSet.noneOf(LayerListenerType.class);
    952 
    953         activeLayer = layer;
    954         EnumSet<LayerListenerType> listenersToFire = EnumSet.of(LayerListenerType.ACTIVE_LAYER_CHANGE);
    955         if (setEditLayer) {
    956             listenersToFire.addAll(setEditLayer(layers));
    957         }
    958 
    959         return listenersToFire;
    960     }
    961 
    962810    /**
    963811     * Replies the currently active layer
    964812     *
    965813     * @return the currently active layer (may be null)
    966814     */
     815    @Deprecated
    967816    public Layer getActiveLayer() {
    968         synchronized (layers) {
    969             return activeLayer;
    970         }
    971     }
    972 
    973     private enum LayerListenerType {
    974         ACTIVE_LAYER_CHANGE,
    975         EDIT_LAYER_CHANGE
     817        return layerManager.getActiveLayer();
    976818    }
    977819
    978     /**
    979      * This is called whenever one of active layer/edit layer or both may have been changed,
    980      * @param oldActive The old active layer
    981      * @param oldEdit The old edit layer.
    982      * @param listenersToFire A mask of listeners to fire using {@link LayerListenerType}s
    983      */
    984     private void onActiveEditLayerChanged(final Layer oldActive, final OsmDataLayer oldEdit, EnumSet<LayerListenerType> listenersToFire) {
    985         if (listenersToFire.contains(LayerListenerType.EDIT_LAYER_CHANGE)) {
    986             onEditLayerChanged(oldEdit);
    987         }
    988         if (listenersToFire.contains(LayerListenerType.ACTIVE_LAYER_CHANGE)) {
    989             onActiveLayerChanged(oldActive);
    990         }
    991     }
    992 
    993     private void onActiveLayerChanged(final Layer old) {
    994         fireActiveLayerChanged(old, activeLayer);
    995 
     820    @Override
     821    public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
    996822        /* This only makes the buttons look disabled. Disabling the actions as well requires
    997823         * the user to re-select the tool after i.e. moving a layer. While testing I found
    998824         * that I switch layers and actions at the same time and it was annoying to mind the
    999825         * order. This way it works as visual clue for new users */
    1000826        for (final AbstractButton b: Main.map.allMapModeButtons) {
    1001827            MapMode mode = (MapMode) b.getAction();
    1002             final boolean activeLayerSupported = mode.layerIsSupported(activeLayer);
     828            final boolean activeLayerSupported = mode.layerIsSupported(layerManager.getActiveLayer());
    1003829            if (activeLayerSupported) {
    1004830                Main.registerActionShortcut(mode, mode.getShortcut()); //fix #6876
    1005831            } else {
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    1012838            });
    1013839        }
    1014840        AudioPlayer.reset();
     841        refreshTitle();
    1015842        repaint();
    1016843    }
    1017844
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    1020847     *
    1021848     * @return the current edit layer. May be null.
    1022849     */
     850    @Deprecated
    1023851    public OsmDataLayer getEditLayer() {
    1024         synchronized (layers) {
    1025             return editLayer;
    1026         }
     852        return layerManager.getEditLayer();
    1027853    }
    1028854
    1029855    /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    1032858     * @param layer the layer
    1033859     * @return true if the list of layers managed by this map view contain layer
    1034860     */
     861    @Deprecated
    1035862    public boolean hasLayer(Layer layer) {
    1036         synchronized (layers) {
    1037             return layers.contains(layer);
    1038         }
     863        return layerManager.containsLayer(layer);
    1039864    }
    1040865
    1041866    /**
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    1099924     */
    1100925    protected void refreshTitle() {
    1101926        if (Main.parent != null) {
    1102             synchronized (layers) {
    1103                 boolean dirty = editLayer != null &&
    1104                         (editLayer.requiresSaveToFile() || (editLayer.requiresUploadToServer() && !editLayer.isUploadDiscouraged()));
    1105                 ((JFrame) Main.parent).setTitle((dirty ? "* " : "") + tr("Java OpenStreetMap Editor"));
    1106                 ((JFrame) Main.parent).getRootPane().putClientProperty("Window.documentModified", dirty);
    1107             }
     927            OsmDataLayer editLayer = layerManager.getEditLayer();
     928            boolean dirty = editLayer != null &&
     929                    (editLayer.requiresSaveToFile() || (editLayer.requiresUploadToServer() && !editLayer.isUploadDiscouraged()));
     930            ((JFrame) Main.parent).setTitle((dirty ? "* " : "") + tr("Java OpenStreetMap Editor"));
     931            ((JFrame) Main.parent).getRootPane().putClientProperty("Window.documentModified", dirty);
    1108932        }
    1109933    }
    1110934
    implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer  
    1123947    };
    1124948
    1125949    public void destroy() {
     950        layerManager.removeLayerChangeListener(this);
     951        layerManager.removeActiveLayerChangeListener(this);
    1126952        Main.pref.removePreferenceChangeListener(this);
    1127953        DataSet.removeSelectionListener(repaintSelectionChangedListener);
    1128954        MultipolygonCache.getInstance().clear(this);
    1129955        if (mapMover != null) {
    1130956            mapMover.destroy();
    1131957        }
    1132         synchronized (layers) {
    1133             activeLayer = null;
    1134             changedLayer = null;
    1135             editLayer = null;
    1136             layers.clear();
    1137             nonChangedLayers.clear();
    1138         }
     958        nonChangedLayers.clear();
    1139959        synchronized (temporaryLayers) {
    1140960            temporaryLayers.clear();
    1141961        }
  • src/org/openstreetmap/josm/gui/layer/GpxLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/GpxLayer.java b/src/org/openstreetmap/josm/gui/layer/GpxLayer.java
    index 25ca961..f20d465 100644
    a b public class GpxLayer extends Layer {  
    364364        return SaveActionBase.createAndOpenSaveFileChooser(tr("Save GPX file"), GpxImporter.FILE_FILTER);
    365365    }
    366366
     367    @Override
     368    public LayerPositionStrategy getPositionStrategy() {
     369        return LayerPositionStrategy.AFTER_LAST_DATA_LAYER;
     370    }
     371
    367372}
  • src/org/openstreetmap/josm/gui/layer/Layer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/Layer.java b/src/org/openstreetmap/josm/gui/layer/Layer.java
    index e16587b..3218636 100644
    a b public abstract class Layer implements Destroyable, MapViewPaintable, Projection  
    506506    public File createAndOpenSaveFileChooser() {
    507507        return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
    508508    }
     509
     510    /**
     511     * Gets the strategy that specifies where this layer should be inserted in a layer list.
     512     * @return That strategy.
     513     */
     514    public LayerPositionStrategy getPositionStrategy() {
     515        return isBackgroundLayer() ? LayerPositionStrategy.BEFORE_FIRST_BACKGROUND_LAYER : LayerPositionStrategy.IN_FRONT;
     516    }
    509517}
  • new file src/org/openstreetmap/josm/gui/layer/LayerManager.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/LayerManager.java b/src/org/openstreetmap/josm/gui/layer/LayerManager.java
    new file mode 100644
    index 0000000..c39abdf
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.layer;
     3
     4import java.util.ArrayList;
     5import java.util.Collections;
     6import java.util.List;
     7import java.util.concurrent.CopyOnWriteArrayList;
     8
     9import org.openstreetmap.josm.Main;
     10import org.openstreetmap.josm.tools.Utils;
     11
     12/**
     13 * This class handles the layer management.
     14 * <p>
     15 * This manager handles a list of layers with the first layer being the front layer.
     16 * <h1>Threading</h1>
     17 * Methods of this manager may be called from any thread in any order. Listeners are called while this layer manager is locked, so they should not block.
     18 *
     19 * @author Michael Zangl
     20 */
     21public class LayerManager {
     22    /**
     23     * Interface to notify listeners of a layer change.
     24     */
     25    public interface LayerChangeListener {
     26        /**
     27         * Notifies this listener that a layer has been added.
     28         * @param e The new added layer event
     29         */
     30        void layerAdded(LayerAddEvent e);
     31
     32        /**
     33         * Notifies this listener that a layer is about to be removed.
     34         * @param e The layer to be removed (as event)
     35         */
     36        void layerRemoving(LayerRemoveEvent e);
     37
     38        /**
     39         * Notifies this listener that the order of layers was changed.
     40         * @param e The order change event.
     41         */
     42        void layerOrderChanged(LayerOrderChangeEvent e);
     43    }
     44
     45    protected static class LayerManagerEvent {
     46        private final LayerManager source;
     47
     48        LayerManagerEvent(LayerManager source) {
     49            this.source = source;
     50        }
     51
     52        public LayerManager getSource() {
     53            return source;
     54        }
     55    }
     56
     57    /**
     58     * The event that is fired whenever a layer was added.
     59     * @author Michael Zangl
     60     */
     61    public static class LayerAddEvent extends LayerManagerEvent {
     62        private final Layer addedLayer;
     63
     64        LayerAddEvent(LayerManager source, Layer addedLayer) {
     65            super(source);
     66            this.addedLayer = addedLayer;
     67        }
     68
     69        /**
     70         * Gets the layer that was added.
     71         * @return The added layer.
     72         */
     73        public Layer getAddedLayer() {
     74            return addedLayer;
     75        }
     76    }
     77
     78    /**
     79     * The event that is fired before removing a layer.
     80     * @author Michael Zangl
     81     */
     82    public static class LayerRemoveEvent extends LayerManagerEvent {
     83        private final Layer removedLayer;
     84
     85        LayerRemoveEvent(LayerManager source, Layer removedLayer) {
     86            super(source);
     87            this.removedLayer = removedLayer;
     88        }
     89
     90        /**
     91         * Gets the layer that is about to be removed.
     92         * @return The layer.
     93         */
     94        public Layer getRemovedLayer() {
     95            return removedLayer;
     96        }
     97    }
     98
     99    /**
     100     * An event that is fired whenever the order of layers changed.
     101     * @author Michael Zangl
     102     */
     103    public static class LayerOrderChangeEvent extends LayerManagerEvent {
     104        LayerOrderChangeEvent(LayerManager source) {
     105            super(source);
     106        }
     107
     108    }
     109
     110    /**
     111     * This is the list of layers we manage.
     112     */
     113    private final List<Layer> layers = new ArrayList<>();
     114
     115    private final List<LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<>();
     116
     117    /**
     118     * Add a layer. The layer will be added at a given psoition.
     119     * @param layer The layer to add
     120     */
     121    public synchronized void addLayer(Layer layer) {
     122        if (containsLayer(layer)) {
     123            throw new IllegalArgumentException("Cannot add a layer twice.");
     124        }
     125        LayerPositionStrategy positionStrategy = layer.getPositionStrategy();
     126        int position = positionStrategy.getPosition(this);
     127        checkPosition(position);
     128        insertLayerAt(layer, position);
     129        fireLayerAdded(layer);
     130    }
     131
     132    /**
     133     * Remove the layer from the mapview. If the layer was in the list before,
     134     * an LayerChange event is fired.
     135     * @param layer The layer to remove
     136     */
     137    public synchronized void removeLayer(Layer layer) {
     138        checkContainsLayer(layer);
     139
     140        fireLayerRemoving(layer);
     141        layers.remove(layer);
     142    }
     143
     144    /**
     145     * Move a layer to a new position.
     146     * @param layer The layer to move.
     147     * @param position The position.
     148     * @throws IndexOutOfBoundsException if the position is out of bounds.
     149     */
     150    public synchronized void moveLayer(Layer layer, int position) {
     151        checkContainsLayer(layer);
     152        checkPosition(position);
     153
     154        int curLayerPos = layers.indexOf(layer);
     155        if (position == curLayerPos)
     156            return; // already in place.
     157        layers.remove(curLayerPos);
     158        insertLayerAt(layer, position);
     159        fireLayerOrderChanged();
     160    }
     161
     162    private void insertLayerAt(Layer layer, int position) {
     163        if (position == layers.size()) {
     164            layers.add(layer);
     165        } else {
     166            layers.add(position, layer);
     167        }
     168    }
     169
     170    private void checkPosition(int position) {
     171        if (position < 0 || position > layers.size()) {
     172            throw new IndexOutOfBoundsException("Position " + position + " out of range.");
     173        }
     174    }
     175
     176    /**
     177     * Gets an unmodifiable list of all layers that are currently in this manager. This list won't update once layers are added or removed.
     178     * @return The list of layers.
     179     */
     180    public List<Layer> getLayers() {
     181        return Collections.unmodifiableList(new ArrayList<>(layers));
     182    }
     183
     184    /**
     185     * Replies an unmodifiable list of layers of a certain type.
     186     *
     187     * Example:
     188     * <pre>
     189     *     List&lt;WMSLayer&gt; wmsLayers = getLayersOfType(WMSLayer.class);
     190     * </pre>
     191     *
     192     * @param ofType The layer type.
     193     * @return an unmodifiable list of layers of a certain type.
     194     */
     195    public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
     196        return new ArrayList<>(Utils.filteredCollection(getLayers(), ofType));
     197    }
     198
     199    /**
     200     * replies true if the list of layers managed by this map view contain layer
     201     *
     202     * @param layer the layer
     203     * @return true if the list of layers managed by this map view contain layer
     204     */
     205    public synchronized boolean containsLayer(Layer layer) {
     206        return layers.contains(layer);
     207    }
     208
     209    protected void checkContainsLayer(Layer layer) {
     210        if (!containsLayer(layer)) {
     211            throw new IllegalArgumentException(layer + " is not managed by us.");
     212        }
     213    }
     214
     215    /**
     216     * Adds a layer change listener
     217     *
     218     * @param listener the listener.
     219     */
     220    public synchronized void addLayerChangeListener(LayerChangeListener listener) {
     221        addLayerChangeListener(listener, false);
     222    }
     223
     224    /**
     225     * Adds a layer change listener
     226     *
     227     * @param listener the listener.
     228     * @param fireAdd if we should fire an add event for every layer in this manager.
     229     */
     230    public synchronized void addLayerChangeListener(LayerChangeListener listener, boolean fireAdd) {
     231        if (layerChangeListeners.contains(listener)) {
     232            throw new IllegalArgumentException("Listener already registered.");
     233        }
     234        layerChangeListeners.add(listener);
     235        if (fireAdd) {
     236            for (Layer l : getLayers()) {
     237                listener.layerAdded(new LayerAddEvent(this, l));
     238            }
     239        }
     240    }
     241
     242    /**
     243     * Removes a layer change listener
     244     *
     245     * @param listener the listener. Ignored if null or already registered.
     246     */
     247    public synchronized void removeLayerChangeListener(LayerChangeListener listener) {
     248        removeLayerChangeListener(listener, false);
     249    }
     250
     251
     252    /**
     253     * Removes a layer change listener
     254     *
     255     * @param listener the listener.
     256     * @param fireRemove if we should fire a remove event for every layer in this manager.
     257     */
     258    public synchronized void removeLayerChangeListener(LayerChangeListener listener, boolean fireRemove) {
     259        if (!layerChangeListeners.remove(listener)) {
     260            //throw new IllegalArgumentException("Listener was not registered before: " + listener);
     261            Main.error("Listener was not registered before: " + listener);
     262        } else {
     263            if (fireRemove) {
     264                for (Layer l : getLayers()) {
     265                    listener.layerRemoving(new LayerRemoveEvent(this, l));
     266                }
     267            }
     268        }
     269    }
     270
     271    private void fireLayerAdded(Layer layer) {
     272        LayerAddEvent e = new LayerAddEvent(this, layer);
     273        for (LayerChangeListener l : layerChangeListeners) {
     274            l.layerAdded(e);
     275        }
     276    }
     277
     278    private void fireLayerRemoving(Layer layer) {
     279        LayerRemoveEvent e = new LayerRemoveEvent(this, layer);
     280        for (LayerChangeListener l : layerChangeListeners) {
     281            l.layerRemoving(e);
     282        }
     283    }
     284
     285    private void fireLayerOrderChanged() {
     286        LayerOrderChangeEvent e = new LayerOrderChangeEvent(this);
     287        for (LayerChangeListener l : layerChangeListeners) {
     288            l.layerOrderChanged(e);
     289        }
     290    }
     291}
  • new file src/org/openstreetmap/josm/gui/layer/LayerManagerWithActive.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/LayerManagerWithActive.java b/src/org/openstreetmap/josm/gui/layer/LayerManagerWithActive.java
    new file mode 100644
    index 0000000..2f6aafe
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.layer;
     3
     4import java.util.ArrayList;
     5import java.util.List;
     6import java.util.ListIterator;
     7import java.util.concurrent.CopyOnWriteArrayList;
     8
     9import org.openstreetmap.josm.data.osm.DataSet;
     10
     11/**
     12 * This class extends the layer manager by adding an active and an edit layer.
     13 * <p>
     14 * The active layer is the layer the user is currently working on.
     15 * <p>
     16 * The edit layer is an data layer that we currently work with.
     17 * @author Michael Zangl
     18 */
     19public class LayerManagerWithActive extends LayerManager {
     20    /**
     21     * This listener listens to changes of the active or the edit layer.
     22     * @author Michael Zangl
     23     *
     24     */
     25    public interface ActiveLayerChangeListener {
     26        /**
     27         * Called whenever the active or edit layer changed.
     28         * <p>
     29         * You can be sure that this layer is still contained in this set.
     30         * @param e The change event.
     31         */
     32        public void activeOrEditLayerChanged(ActiveLayerChangeEvent e);
     33    }
     34
     35    /**
     36     * This event is fired whenever the active or the edit layer changes.
     37     * @author Michael Zangl
     38     */
     39    public class ActiveLayerChangeEvent extends LayerManagerEvent {
     40
     41        private final OsmDataLayer previousEditLayer;
     42
     43        private final Layer previousActiveLayer;
     44
     45        /**
     46         * Create a new {@link ActiveLayerChangeEvent}
     47         * @param source The source
     48         * @param previousEditLayer the previous edit layer
     49         * @param previousActiveLayer the previous active layer
     50         */
     51        ActiveLayerChangeEvent(LayerManagerWithActive source, OsmDataLayer previousEditLayer,
     52                Layer previousActiveLayer) {
     53            super(source);
     54            this.previousEditLayer = previousEditLayer;
     55            this.previousActiveLayer = previousActiveLayer;
     56        }
     57
     58        /**
     59         * Gets the edit layer that was previously used.
     60         * @return The old edit layer, <code>null</code> if there is none.
     61         */
     62        public OsmDataLayer getPreviousEditLayer() {
     63            return previousEditLayer;
     64        }
     65
     66        /**
     67         * Gets the active layer that was previously used.
     68         * @return The old active layer, <code>null</code> if there is none.
     69         */
     70        public Layer getPreviousActiveLayer() {
     71            return previousActiveLayer;
     72        }
     73
     74        @Override
     75        public LayerManagerWithActive getSource() {
     76            return (LayerManagerWithActive) super.getSource();
     77        }
     78    }
     79
     80    private final List<ActiveLayerChangeListener> activeLayerChangeListeners = new CopyOnWriteArrayList<>();
     81
     82    /**
     83     * Adds a active/edit layer change listener
     84     *
     85     * @param listener the listener.
     86     * @param initialFire fire a fake active-layer-changed-event right after adding
     87     * the listener. The previous layers will be null.
     88     */
     89    public void addActiveLayerChangeListener(ActiveLayerChangeListener listener, boolean initialFire) {
     90        activeLayerChangeListeners.add(listener);
     91        if (initialFire) {
     92            listener.activeOrEditLayerChanged(new ActiveLayerChangeEvent(this, null, null));
     93        }
     94    }
     95
     96    /**
     97     * Removes an active/edit layer change listener.
     98     * @param listener the listener.
     99     */
     100    public void removeActiveLayerChangeListener(ActiveLayerChangeListener listener) {
     101        activeLayerChangeListeners.remove(listener);
     102    }
     103    /**
     104     * The layer from the layers list that is currently active.
     105     */
     106    private Layer activeLayer;
     107
     108    /**
     109     * The edit layer is the current active data layer.
     110     */
     111    private OsmDataLayer editLayer;
     112
     113    /**
     114     * Set the active layer. If the layer is an OsmDataLayer, the edit layer is also changed.
     115     * @param layer The active layer.
     116     */
     117    public synchronized void setActiveLayer(Layer layer) {
     118        checkContainsLayer(layer);
     119        setActiveLayer(layer, false);
     120    }
     121
     122    private void setActiveLayer(Layer layer, boolean forceEditLayerUpdate) {
     123        ActiveLayerChangeEvent event = new ActiveLayerChangeEvent(this, editLayer, activeLayer);
     124        activeLayer = layer;
     125        if (activeLayer instanceof OsmDataLayer) {
     126            editLayer = (OsmDataLayer) activeLayer;
     127        } else if (forceEditLayerUpdate) {
     128            editLayer = null;
     129        }
     130        fireActiveLayerChange(event);
     131    }
     132
     133    private void fireActiveLayerChange(ActiveLayerChangeEvent event) {
     134        if (event.getPreviousActiveLayer() != activeLayer || event.getPreviousEditLayer() != editLayer) {
     135            for (ActiveLayerChangeListener l : activeLayerChangeListeners) {
     136                l.activeOrEditLayerChanged(event);
     137            }
     138        }
     139    }
     140
     141    @Override
     142    public synchronized void addLayer(Layer layer) {
     143        super.addLayer(layer);
     144
     145        // update the active layer automatically.
     146        if (layer instanceof OsmDataLayer || activeLayer == null) {
     147            setActiveLayer(layer);
     148        }
     149    }
     150
     151    @Override
     152    public synchronized void removeLayer(Layer layer) {
     153        if (layer == activeLayer || layer == editLayer) {
     154            Layer nextActive = suggestNextActiveLayer(layer);
     155            setActiveLayer(nextActive, true);
     156        }
     157
     158        super.removeLayer(layer);
     159    }
     160
     161    /**
     162     * Determines the next active data layer according to the following
     163     * rules:
     164     * <ul>
     165     *   <li>if there is at least one {@link OsmDataLayer} the first one
     166     *     becomes active</li>
     167     *   <li>otherwise, the top most layer of any type becomes active</li>
     168     * </ul>
     169     *
     170     * @param except A layer to ignore.
     171     * @return the next active data layer
     172     */
     173    private Layer suggestNextActiveLayer(Layer except) {
     174        List<Layer> layersList = new ArrayList<>(getLayers());
     175        layersList.remove(except);
     176        // First look for data layer
     177        for (Layer layer : layersList) {
     178            if (layer instanceof OsmDataLayer)
     179                return layer;
     180        }
     181
     182        // Then any layer
     183        if (!layersList.isEmpty())
     184            return layersList.get(0);
     185
     186        // and then give up
     187        return null;
     188    }
     189
     190    /**
     191     * Replies the currently active layer
     192     *
     193     * @return the currently active layer (may be null)
     194     */
     195    public synchronized Layer getActiveLayer() {
     196        return activeLayer;
     197    }
     198
     199    /**
     200     * Replies the current edit layer, if any
     201     *
     202     * @return the current edit layer. May be null.
     203     */
     204    public synchronized OsmDataLayer getEditLayer() {
     205        return editLayer;
     206    }
     207
     208    /**
     209     * Gets the data set of the active edit layer.
     210     * @return That data set, <code>null</code> if there is no edit layer.
     211     */
     212    public synchronized DataSet getEditDataSet() {
     213        return editLayer != null ? editLayer.data : null;
     214    }
     215
     216
     217    /**
     218     * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
     219     * first, layer with the highest Z-Order last.
     220     * <p>
     221     * The active data layer is pulled above all adjacent data layers.
     222     *
     223     * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
     224     * first, layer with the highest Z-Order last.
     225     */
     226    public synchronized List<Layer> getVisibleLayersInZOrder() {
     227        List<Layer> ret = new ArrayList<>();
     228        // This is set while we delay the addition of the active layer.
     229        boolean activeLayerDelayed = false;
     230        List<Layer> layers = getLayers();
     231        for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) {
     232            Layer l = iterator.previous();
     233            if (!l.isVisible()) {
     234                // ignored
     235            } else if (l == activeLayer && l instanceof OsmDataLayer) {
     236                // delay and add after the current block of OsmDataLayer
     237                activeLayerDelayed = true;
     238            } else {
     239                if (activeLayerDelayed && !(l instanceof OsmDataLayer)) {
     240                    // add active layer before the current one.
     241                    ret.add(activeLayer);
     242                    activeLayerDelayed = false;
     243                }
     244                // Add this layer now
     245                ret.add(l);
     246            }
     247        }
     248        if (activeLayerDelayed) {
     249            ret.add(activeLayer);
     250        }
     251        return ret;
     252    }
     253}
  • new file src/org/openstreetmap/josm/gui/layer/LayerPositionStrategy.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/LayerPositionStrategy.java b/src/org/openstreetmap/josm/gui/layer/LayerPositionStrategy.java
    new file mode 100644
    index 0000000..73d18e3
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.layer;
     3
     4import java.util.List;
     5
     6/**
     7 * This class defines a position to insert a given layer in the list of layers.
     8 * @author Michael Zangl
     9 */
     10public abstract class LayerPositionStrategy {
     11
     12    /**
     13     * always inserts at the front of the stack.
     14     */
     15    public static final LayerPositionStrategy IN_FRONT = new LayerPositionStrategy() {
     16        @Override
     17        public int getPosition(LayerManager manager) {
     18            return 0;
     19        }
     20    };
     21
     22    /**
     23     * A GPX layer is added below the lowest data layer.
     24     */
     25    public static final LayerPositionStrategy AFTER_LAST_DATA_LAYER = new LayerPositionStrategy() {
     26        @Override
     27        public int getPosition(LayerManager manager) {
     28            List<Layer> layers = manager.getLayers();
     29            for (int i = layers.size() - 1; i >= 0; i--) {
     30                if (layers.get(i) instanceof OsmDataLayer) {
     31                    return i + 1;
     32                }
     33            }
     34            return 0;
     35        }
     36    };
     37
     38    /**
     39     * The default for background layers: They are added before the first background layer in the list. If there is n one, they are added at the end of the list.
     40     */
     41    public static final LayerPositionStrategy BEFORE_FIRST_BACKGROUND_LAYER = new LayerPositionStrategy() {
     42        @Override
     43        public int getPosition(LayerManager manager) {
     44            List<Layer> layers = manager.getLayers();
     45            for (int i = 0; i < layers.size(); i++) {
     46                if (layers.get(i).isBackgroundLayer()) {
     47                    return i;
     48                }
     49            }
     50            return layers.size();
     51        }
     52    };
     53
     54    /**
     55     * Gets the position where the layer should be inserted
     56     * @param manager The layer manager to insert the layer in.
     57     * @return The position in the range 0...layers.size
     58     */
     59    public abstract int getPosition(LayerManager manager);
     60}
  • test/unit/org/openstreetmap/josm/actions/mapmode/SelectActionTest.java

    diff --git a/test/unit/org/openstreetmap/josm/actions/mapmode/SelectActionTest.java b/test/unit/org/openstreetmap/josm/actions/mapmode/SelectActionTest.java
    index 82eb98b..f833384 100644
    a b public class SelectActionTest {  
    4949                };
    5050
    5151        MapViewMock(DataSet dataSet, OsmDataLayer layer) {
    52             super(null, null);
     52            super(Main.layerManager, null, null);
    5353            this.layer = layer;
    5454            this.currentDataSet = dataSet;
    5555        }