Ticket #13467: patch-13467.patch

File patch-13467.patch, 39.3 KB (added by michael2402, 22 months ago)
  • src/org/openstreetmap/josm/data/osm/DataSet.java

     
    55
    66import java.awt.geom.Area;
    77import java.util.ArrayList;
    8 import java.util.Arrays;
    98import java.util.Collection;
    109import java.util.Collections;
    1110import java.util.HashMap;
    1211import java.util.HashSet;
    1312import java.util.Iterator;
    14 import java.util.LinkedHashSet;
    1513import java.util.LinkedList;
    1614import java.util.List;
    1715import java.util.Map;
     
    2119import java.util.concurrent.locks.Lock;
    2220import java.util.concurrent.locks.ReadWriteLock;
    2321import java.util.concurrent.locks.ReentrantReadWriteLock;
     22import java.util.function.Function;
    2423import java.util.function.Predicate;
    2524import java.util.stream.Collectors;
     25import java.util.stream.Stream;
    2626
    2727import org.openstreetmap.josm.Main;
    2828import org.openstreetmap.josm.data.Bounds;
     
    3232import org.openstreetmap.josm.data.SelectionChangedListener;
    3333import org.openstreetmap.josm.data.coor.EastNorth;
    3434import org.openstreetmap.josm.data.coor.LatLon;
     35import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionAddEvent;
     36import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionChangeEvent;
     37import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionRemoveEvent;
     38import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionReplaceEvent;
     39import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionToggleEvent;
    3540import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
    3641import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent;
    3742import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
     
    4146import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
    4247import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
    4348import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
     49import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
    4450import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
    4551import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
    4652import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
     
    166172    private UploadPolicy uploadPolicy;
    167173
    168174    private final ReadWriteLock lock = new ReentrantReadWriteLock();
     175
     176    /**
     177     * The mutex lock that is used to synchronize selection changes.
     178     */
    169179    private final Object selectionLock = new Object();
     180    /**
     181     * The current selected primitives. This is always a unmodifiable set.
     182     */
     183    private Set<OsmPrimitive> currentSelectedPrimitives = Collections.emptySet();
    170184
     185    /**
     186     * A list of listeners that listen to selection changes on this layer.
     187     */
     188    private final ListenerList<DataSelectionListener> selectionListeners = ListenerList.create();
     189
    171190    private Area cachedDataSourceArea;
    172191    private List<Bounds> cachedDataSourceBounds;
    173192
     
    183202        // Transparently register as projection change listener. No need to explicitly remove
    184203        // the listener, projection change listeners are managed as WeakReferences.
    185204        Main.addProjectionChangeListener(this);
     205        addSelectionListener((DataSelectionListener) e -> fireDreprecatedSelectionChange(e.getSelection()));
    186206    }
    187207
    188208    /**
     
    636656            }
    637657            if (!success)
    638658                throw new JosmRuntimeException("failed to remove primitive: "+primitive);
    639             synchronized (selectionLock) {
    640                 selectedPrimitives.remove(primitive);
    641                 selectionSnapshot = null;
    642             }
     659            clearSelection(primitiveId);
    643660            allPrimitives.remove(primitive);
    644661            primitive.setDataset(null);
    645662            firePrimitivesRemoved(Collections.singletonList(primitive), false);
     
    653670     *---------------------------------------------------*/
    654671
    655672    /**
     673     * Add a listener that listens to selection changes in this specific data set.
     674     * @param listener The listener.
     675     * @see #removeSelectionListener(DataSelectionListener)
     676     * @see SelectionEventManager#addSelectionListener(SelectionChangedListener, org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode)
     677     *      To add a global listener.
     678     */
     679    public void addSelectionListener(DataSelectionListener listener) {
     680        selectionListeners.addListener(listener);
     681    }
     682
     683    /**
     684     * Remove a listener that listens to selection changes in this specific data set.
     685     * @param listener The listener.
     686     * @see #addSelectionListener(DataSelectionListener)
     687     */
     688    public void removeSelectionListener(DataSelectionListener listener) {
     689        selectionListeners.removeListener(listener);
     690    }
     691
     692    /*---------------------------------------------------
     693     *   OLD SELECTION HANDLING
     694     *---------------------------------------------------*/
     695
     696    /**
    656697     * A list of listeners to selection changed events. The list is static, as listeners register
    657698     * themselves for any dataset selection changes that occur, regardless of the current active
    658699     * dataset. (However, the selection does only change in the active layer)
     
    662703    /**
    663704     * Adds a new selection listener.
    664705     * @param listener The selection listener to add
     706     * @see #addSelectionListener(DataSelectionListener)
     707     * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
    665708     */
    666709    public static void addSelectionListener(SelectionChangedListener listener) {
    667710        ((CopyOnWriteArrayList<SelectionChangedListener>) selListeners).addIfAbsent(listener);
     
    670713    /**
    671714     * Removes a selection listener.
    672715     * @param listener The selection listener to remove
     716     * @see #removeSelectionListener(DataSelectionListener)
     717     * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
    673718     */
    674719    public static void removeSelectionListener(SelectionChangedListener listener) {
    675720        selListeners.remove(listener);
     
    678723    /**
    679724     * Notifies all registered {@link SelectionChangedListener} about the current selection in
    680725     * this dataset.
    681      *
     726     * @deprecated You should never need to do this from the outside.
    682727     */
     728    @Deprecated
    683729    public void fireSelectionChanged() {
    684         Collection<? extends OsmPrimitive> currentSelection = getAllSelected();
     730        fireDreprecatedSelectionChange(getAllSelected());
     731    }
     732
     733    private static void fireDreprecatedSelectionChange(Collection<? extends OsmPrimitive> currentSelection) {
    685734        for (SelectionChangedListener l : selListeners) {
    686735            l.selectionChanged(currentSelection);
    687736        }
    688737    }
    689738
    690     private Set<OsmPrimitive> selectedPrimitives = new LinkedHashSet<>();
    691     private Collection<OsmPrimitive> selectionSnapshot;
    692 
    693739    /**
    694740     * Returns selected nodes and ways.
    695741     * @return selected nodes and ways
     
    753799     * @return unmodifiable collection of primitives
    754800     */
    755801    public Collection<OsmPrimitive> getAllSelected() {
    756         Collection<OsmPrimitive> currentList;
    757         synchronized (selectionLock) {
    758             if (selectionSnapshot == null) {
    759                 selectionSnapshot = Collections.unmodifiableList(new ArrayList<>(selectedPrimitives));
    760             }
    761             currentList = selectionSnapshot;
    762         }
    763         return currentList;
     802        return currentSelectedPrimitives;
    764803    }
    765804
    766805    /**
     
    792831     * @return whether the selection is empty or not
    793832     */
    794833    public boolean selectionEmpty() {
    795         return selectedPrimitives.isEmpty();
     834        return currentSelectedPrimitives.isEmpty();
    796835    }
    797836
    798837    /**
     
    801840     * @return whether {@code osm} is selected or not
    802841     */
    803842    public boolean isSelected(OsmPrimitive osm) {
    804         return selectedPrimitives.contains(osm);
     843        return currentSelectedPrimitives.contains(osm);
    805844    }
    806845
    807846    /**
    808      * Toggles the selected state of the given collection of primitives.
    809      * @param osm The primitives to toggle
    810      */
    811     public void toggleSelected(Collection<? extends PrimitiveId> osm) {
    812         boolean changed = false;
    813         synchronized (selectionLock) {
    814             for (PrimitiveId o : osm) {
    815                 changed = changed | this.dotoggleSelected(o);
    816             }
    817             if (changed) {
    818                 selectionSnapshot = null;
    819             }
    820         }
    821         if (changed) {
    822             fireSelectionChanged();
    823         }
    824     }
    825 
    826     /**
    827      * Toggles the selected state of the given collection of primitives.
    828      * @param osm The primitives to toggle
    829      */
    830     public void toggleSelected(PrimitiveId... osm) {
    831         toggleSelected(Arrays.asList(osm));
    832     }
    833 
    834     private boolean dotoggleSelected(PrimitiveId primitiveId) {
    835         OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
    836         if (primitive == null)
    837             return false;
    838         if (!selectedPrimitives.remove(primitive)) {
    839             selectedPrimitives.add(primitive);
    840         }
    841         selectionSnapshot = null;
    842         return true;
    843     }
    844 
    845     /**
    846847     * set what virtual nodes should be highlighted. Requires a Collection of
    847848     * *WaySegments* to avoid a VirtualNode class that wouldn't have much use
    848849     * otherwise.
     
    874875     *
    875876     * @param selection the selection
    876877     * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
     878     * @deprecated Use {@link #setSelected(Collection)} instead. To bee removed end of 2017. Does not seem to be used by plugins.
    877879     */
     880    @Deprecated
    878881    public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
    879         boolean changed;
    880         synchronized (selectionLock) {
    881             Set<OsmPrimitive> oldSelection = new LinkedHashSet<>(selectedPrimitives);
    882             selectedPrimitives = new LinkedHashSet<>();
    883             addSelected(selection, false);
    884             changed = !oldSelection.equals(selectedPrimitives);
    885             if (changed) {
    886                 selectionSnapshot = null;
    887             }
    888         }
    889 
    890         if (changed && fireSelectionChangeEvent) {
    891             // If selection is not empty then event was already fired in addSelecteds
    892             fireSelectionChanged();
    893         }
     882        setSelected(selection);
    894883    }
    895884
    896885    /**
     
    900889     * @param selection the selection
    901890     */
    902891    public void setSelected(Collection<? extends PrimitiveId> selection) {
    903         setSelected(selection, true /* fire selection change event */);
     892        setSelected(selection.stream());
    904893    }
    905894
    906895    /**
    907896     * Sets the current selection to the primitives in <code>osm</code>
    908897     * and notifies all {@link SelectionChangedListener}.
    909898     *
    910      * @param osm the primitives to set
     899     * @param osm the primitives to set. <code>null</code> values are ignored for now, but this may be removed in the future.
    911900     */
    912901    public void setSelected(PrimitiveId... osm) {
    913         if (osm.length == 1 && osm[0] == null) {
    914             setSelected();
    915             return;
    916         }
    917         List<PrimitiveId> list = Arrays.asList(osm);
    918         setSelected(list);
     902        setSelected(Stream.of(osm).filter(Objects::nonNull));
    919903    }
    920904
     905    private void setSelected(Stream<? extends PrimitiveId> stream) {
     906        doSelectionChange(old -> new SelectionReplaceEvent(this, old,
     907                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
     908    }
     909
    921910    /**
    922911     * Adds the primitives in <code>selection</code> to the current selection
    923912     * and notifies all {@link SelectionChangedListener}.
     
    925914     * @param selection the selection
    926915     */
    927916    public void addSelected(Collection<? extends PrimitiveId> selection) {
    928         addSelected(selection, true /* fire selection change event */);
     917        addSelected(selection.stream());
    929918    }
    930919
    931920    /**
     
    935924     * @param osm the primitives to add
    936925     */
    937926    public void addSelected(PrimitiveId... osm) {
    938         addSelected(Arrays.asList(osm));
     927        addSelected(Stream.of(osm));
    939928    }
    940929
     930    private void addSelected(Stream<? extends PrimitiveId> stream) {
     931        doSelectionChange(old -> new SelectionAddEvent(this, old,
     932                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
     933    }
     934
    941935    /**
    942      * Adds the primitives in <code>selection</code> to the current selection.
    943      * Notifies all {@link SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
    944      *
    945      * @param selection the selection
    946      * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
    947      * @return if the selection was changed in the process
     936     * Removes the selection from every value in the collection.
     937     * @param osm The collection of ids to remove the selection from.
    948938     */
    949     private boolean addSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
    950         boolean changed = false;
    951         synchronized (selectionLock) {
    952             for (PrimitiveId id: selection) {
    953                 OsmPrimitive primitive = getPrimitiveByIdChecked(id);
    954                 if (primitive != null) {
    955                     changed = changed | selectedPrimitives.add(primitive);
    956                 }
    957             }
    958             if (changed) {
    959                 selectionSnapshot = null;
    960             }
    961         }
    962         if (fireSelectionChangeEvent && changed) {
    963             fireSelectionChanged();
    964         }
    965         return changed;
     939    public void clearSelection(PrimitiveId... osm) {
     940        clearSelection(Stream.of(osm));
    966941    }
    967942
    968943    /**
    969      * clear all highlights of virtual nodes
     944     * Removes the selection from every value in the collection.
     945     * @param list The collection of ids to remove the selection from.
    970946     */
    971     public void clearHighlightedVirtualNodes() {
    972         setHighlightedVirtualNodes(new ArrayList<WaySegment>());
     947    public void clearSelection(Collection<? extends PrimitiveId> list) {
     948        clearSelection(list.stream());
    973949    }
    974950
    975951    /**
    976      * clear all highlights of way segments
     952     * Clears the current selection.
    977953     */
    978     public void clearHighlightedWaySegments() {
    979         setHighlightedWaySegments(new ArrayList<WaySegment>());
     954    public void clearSelection() {
     955        setSelected(Stream.empty());
    980956    }
    981957
     958    private void clearSelection(Stream<? extends PrimitiveId> stream) {
     959        doSelectionChange(old -> new SelectionRemoveEvent(this, old,
     960                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
     961    }
     962
    982963    /**
    983      * Removes the selection from every value in the collection.
    984      * @param osm The collection of ids to remove the selection from.
     964     * Toggles the selected state of the given collection of primitives.
     965     * @param osm The primitives to toggle
    985966     */
    986     public void clearSelection(PrimitiveId... osm) {
    987         clearSelection(Arrays.asList(osm));
     967    public void toggleSelected(Collection<? extends PrimitiveId> osm) {
     968        toggleSelected(osm.stream());
    988969    }
    989970
    990971    /**
    991      * Removes the selection from every value in the collection.
    992      * @param list The collection of ids to remove the selection from.
     972     * Toggles the selected state of the given collection of primitives.
     973     * @param osm The primitives to toggle
    993974     */
    994     public void clearSelection(Collection<? extends PrimitiveId> list) {
    995         boolean changed = false;
    996         synchronized (selectionLock) {
    997             for (PrimitiveId id:list) {
    998                 OsmPrimitive primitive = getPrimitiveById(id);
    999                 if (primitive != null) {
    1000                     changed = changed | selectedPrimitives.remove(primitive);
    1001                 }
    1002             }
    1003             if (changed) {
    1004                 selectionSnapshot = null;
    1005             }
    1006         }
    1007         if (changed) {
    1008             fireSelectionChanged();
    1009         }
     975    public void toggleSelected(PrimitiveId... osm) {
     976        toggleSelected(Stream.of(osm));
    1010977    }
    1011978
     979    private void toggleSelected(Stream<? extends PrimitiveId> stream) {
     980        doSelectionChange(old -> new SelectionToggleEvent(this, old,
     981                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
     982    }
     983
    1012984    /**
    1013      * Clears the current selection.
     985     * Do a selection change.
     986     * <p>
     987     * This is the only method that changes the current selection state.
     988     * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives.
     989     * @return if the command did change the selection.
    1014990     */
    1015     public void clearSelection() {
    1016         if (!selectedPrimitives.isEmpty()) {
     991    private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) {
     992        lock.readLock().lock();
     993        try {
    1017994            synchronized (selectionLock) {
    1018                 selectedPrimitives.clear();
    1019                 selectionSnapshot = null;
     995                SelectionChangeEvent event = command.apply(currentSelectedPrimitives);
     996                if (event.isNop()) {
     997                    return false;
     998                }
     999                currentSelectedPrimitives = event.getSelection();
     1000                selectionListeners.fireEvent(l -> l.selectionChanged(event));
     1001                return true;
    10201002            }
    1021             fireSelectionChanged();
     1003        } finally {
     1004            lock.readLock().unlock();
    10221005        }
    10231006    }
    10241007
     1008    /**
     1009     * clear all highlights of virtual nodes
     1010     */
     1011    public void clearHighlightedVirtualNodes() {
     1012        setHighlightedVirtualNodes(new ArrayList<WaySegment>());
     1013    }
     1014
     1015    /**
     1016     * clear all highlights of way segments
     1017     */
     1018    public void clearHighlightedWaySegments() {
     1019        setHighlightedWaySegments(new ArrayList<WaySegment>());
     1020    }
    10251021    @Override
    10261022    public synchronized Area getDataSourceArea() {
    10271023        if (cachedDataSourceArea == null) {
     
    13711367    public void cleanupDeletedPrimitives() {
    13721368        beginUpdate();
    13731369        try {
    1374             boolean changed = cleanupDeleted(nodes.iterator());
    1375             if (cleanupDeleted(ways.iterator())) {
    1376                 changed = true;
    1377             }
    1378             if (cleanupDeleted(relations.iterator())) {
    1379                 changed = true;
    1380             }
    1381             if (changed) {
    1382                 fireSelectionChanged();
    1383             }
     1370            cleanupDeleted(Stream.concat(
     1371                    nodes.stream(), Stream.concat(ways.stream(), relations.stream())));
    13841372        } finally {
    13851373            endUpdate();
    13861374        }
    13871375    }
    13881376
    1389     private boolean cleanupDeleted(Iterator<? extends OsmPrimitive> it) {
    1390         boolean changed = false;
    1391         synchronized (selectionLock) {
    1392             while (it.hasNext()) {
    1393                 OsmPrimitive primitive = it.next();
    1394                 if (primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew())) {
    1395                     selectedPrimitives.remove(primitive);
    1396                     selectionSnapshot = null;
    1397                     allPrimitives.remove(primitive);
    1398                     primitive.setDataset(null);
    1399                     changed = true;
    1400                     it.remove();
    1401                 }
    1402             }
    1403             if (changed) {
    1404                 selectionSnapshot = null;
    1405             }
    1406         }
    1407         return changed;
     1377    private void cleanupDeleted(Stream<? extends OsmPrimitive> it) {
     1378        clearSelection(it
     1379                .filter(primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew()))
     1380                .peek(allPrimitives::remove)
     1381                .peek(primitive -> primitive.setDataset(null)));
    14081382    }
    14091383
    14101384    /**
  • src/org/openstreetmap/josm/data/osm/DataSelectionListener.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.osm;
     3
     4import java.util.Collections;
     5import java.util.HashSet;
     6import java.util.Set;
     7import java.util.stream.Collectors;
     8import java.util.stream.Stream;
     9
     10import org.openstreetmap.josm.tools.CheckParameterUtil;
     11
     12/**
     13 * This is a listener that listens to selection change events in the data set.
     14 * @author Michael Zangl
     15 * @since xxx
     16 */
     17@FunctionalInterface
     18public interface DataSelectionListener {
     19
     20    /**
     21     * Called whenever the selection is changed.
     22     * @param e The selection change event.
     23     */
     24    void selectionChanged(SelectionChangeEvent e);
     25
     26    /**
     27     * The event that is fired when the selection changed.
     28     * @author Michael Zangl
     29     * @since xxx
     30     */
     31    public static interface SelectionChangeEvent {
     32        /**
     33         * Gets the previous selection
     34         * <p>
     35         * This collection cannot be modified and will not change.
     36         * @return The old selection
     37         */
     38        public Set<OsmPrimitive> getOldSelection();
     39
     40        /**
     41         * Gets the new selection
     42         * <p>
     43         * This collection cannot be modified and will not change.
     44         * @return The new selection
     45         */
     46        public Set<OsmPrimitive> getSelection();
     47
     48        /**
     49         * Gets the primitives that have been removed from the selection.
     50         * <p>
     51         * Those are the primitives contained in {@link #getOldSelection()} but not in {@link #getSelection()}
     52         * <p>
     53         * This collection cannot be modified and will not change.
     54         * @return The primitives
     55         */
     56        public Set<OsmPrimitive> getRemoved();
     57
     58        /**
     59         * Gets the primitives that have been added to the selection.
     60         * <p>
     61         * Those are the primitives contained in {@link #getSelection()} but not in {@link #getOldSelection()}
     62         * <p>
     63         * This collection cannot be modified and will not change.
     64         * @return The primitives
     65         */
     66        public Set<OsmPrimitive> getAdded();
     67
     68        /**
     69         * Gets the data set that triggered this selection event.
     70         * @return The data set.
     71         */
     72        public DataSet getSource();
     73
     74        /**
     75         * Test if this event did not change anything.
     76         * <p>
     77         * Should return true for all events that are fired.
     78         * @return <code>true</code> if this did not change the selection.
     79         */
     80        default boolean isNop() {
     81            return getAdded().isEmpty() && getRemoved().isEmpty();
     82        }
     83    }
     84
     85    /**
     86     * The base class for selection events
     87     * @author Michael Zangl
     88     * @since xxx
     89     */
     90    abstract static class AbstractSelectionEvent implements SelectionChangeEvent {
     91        private final DataSet source;
     92        private final Set<OsmPrimitive> old;
     93
     94        public AbstractSelectionEvent(DataSet source, Set<OsmPrimitive> old) {
     95            CheckParameterUtil.ensureParameterNotNull(source, "source");
     96            CheckParameterUtil.ensureParameterNotNull(old, "old");
     97            this.source = source;
     98            this.old = Collections.unmodifiableSet(old);
     99        }
     100
     101        @Override
     102        public Set<OsmPrimitive> getOldSelection() {
     103            return old;
     104        }
     105
     106        @Override
     107        public DataSet getSource() {
     108            return source;
     109        }
     110    }
     111
     112
     113    /**
     114     * The selection is replaced by a new selection
     115     * @author Michael Zangl
     116     * @since xxx
     117     */
     118    public static class SelectionReplaceEvent extends AbstractSelectionEvent {
     119        private final Set<OsmPrimitive> current;
     120        private Set<OsmPrimitive> removed;
     121        private Set<OsmPrimitive> added;
     122
     123        /**
     124         * Create a {@link SelectionReplaceEvent}
     125         * @param source The source dataset
     126         * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed.
     127         * @param newSelection The primitives of the new selection.
     128         */
     129        public SelectionReplaceEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> newSelection) {
     130            super(source, old);
     131            this.current = newSelection.collect(Collectors.toSet());
     132        }
     133
     134        @Override
     135        public Set<OsmPrimitive> getSelection() {
     136            return current;
     137        }
     138
     139        @Override
     140        public synchronized Set<OsmPrimitive> getRemoved() {
     141            if (removed == null) {
     142                removed = getOldSelection().stream().filter(p -> !current.contains(p)).collect(Collectors.toSet());
     143            }
     144            return removed;
     145        }
     146
     147        @Override
     148        public synchronized Set<OsmPrimitive> getAdded() {
     149            if (added == null) {
     150                added = current.stream().filter(p -> !getOldSelection().contains(p)).collect(Collectors.toSet());
     151            }
     152            return added;
     153        }
     154    }
     155
     156    /**
     157     * Primitives are added to the selection
     158     * @author Michael Zangl
     159     * @since xxx
     160     */
     161    public static class SelectionAddEvent extends AbstractSelectionEvent {
     162        private final Set<OsmPrimitive> add;
     163        private final Set<OsmPrimitive> current;
     164
     165        /**
     166         * Create a {@link SelectionAddEvent}
     167         * @param source The source dataset
     168         * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed.
     169         * @param toAdd The primitives to add.
     170         */
     171        public SelectionAddEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toAdd) {
     172            super(source, old);
     173            this.add = toAdd.filter(p -> !old.contains(p)).collect(Collectors.toSet());
     174            if (this.add.isEmpty()) {
     175                this.current = this.getOldSelection();
     176            } else {
     177                this.current = new HashSet<>(old);
     178                this.current.addAll(add);
     179            }
     180        }
     181
     182        @Override
     183        public Set<OsmPrimitive> getSelection() {
     184            return Collections.unmodifiableSet(current);
     185        }
     186
     187        @Override
     188        public Set<OsmPrimitive> getRemoved() {
     189            return Collections.emptySet();
     190        }
     191
     192        @Override
     193        public Set<OsmPrimitive> getAdded() {
     194            return Collections.unmodifiableSet(add);
     195        }
     196    }
     197
     198    /**
     199     * Primitives are removed from the selection
     200     * @author Michael Zangl
     201     * @since xxx
     202     */
     203    public static class SelectionRemoveEvent extends AbstractSelectionEvent {
     204        private final Set<OsmPrimitive> remove;
     205        private final Set<OsmPrimitive> current;
     206
     207        /**
     208         * Create a {@link SelectionRemoveEvent}
     209         * @param source The source dataset
     210         * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed.
     211         * @param toRemove The primitives to remove.
     212         */
     213        public SelectionRemoveEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toRemove) {
     214            super(source, old);
     215            this.remove = toRemove.filter(old::contains).collect(Collectors.toSet());
     216            if (this.remove.isEmpty()) {
     217                this.current = this.getOldSelection();
     218            } else {
     219                HashSet<OsmPrimitive> currentSet = new HashSet<>(old);
     220                currentSet.removeAll(remove);
     221                current = Collections.unmodifiableSet(currentSet);
     222            }
     223        }
     224
     225        @Override
     226        public Set<OsmPrimitive> getSelection() {
     227            return Collections.unmodifiableSet(current);
     228        }
     229
     230        @Override
     231        public Set<OsmPrimitive> getRemoved() {
     232            return Collections.unmodifiableSet(remove);
     233        }
     234
     235        @Override
     236        public Set<OsmPrimitive> getAdded() {
     237            return Collections.emptySet();
     238        }
     239    }
     240
     241    /**
     242     * Toggle the selected state of a primitive
     243     * @author Michael Zangl
     244     * @since xxx
     245     */
     246    public static class SelectionToggleEvent extends AbstractSelectionEvent {
     247        private final Set<OsmPrimitive> current;
     248        private final Set<OsmPrimitive> remove;
     249        private final Set<OsmPrimitive> add;
     250
     251        /**
     252         * Create a {@link SelectionToggleEvent}
     253         * @param source The source dataset
     254         * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed.
     255         * @param toToggle The primitives to toggle.
     256         */
     257        public SelectionToggleEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toToggle) {
     258            super(source, old);
     259            HashSet<OsmPrimitive> currentSet = new HashSet<>(old);
     260            HashSet<OsmPrimitive> removeSet = new HashSet<>();
     261            HashSet<OsmPrimitive> addSet = new HashSet<>();
     262            toToggle.forEach(p -> {
     263                if (currentSet.remove(p)) {
     264                    removeSet.add(p);
     265                } else {
     266                    addSet.add(p);
     267                    currentSet.add(p);
     268                }
     269            });
     270            this.current = Collections.unmodifiableSet(currentSet);
     271            this.remove = Collections.unmodifiableSet(removeSet);
     272            this.add = Collections.unmodifiableSet(addSet);
     273        }
     274
     275        @Override
     276        public Set<OsmPrimitive> getSelection() {
     277            return Collections.unmodifiableSet(current);
     278        }
     279
     280        @Override
     281        public Set<OsmPrimitive> getRemoved() {
     282            return Collections.unmodifiableSet(remove);
     283        }
     284
     285        @Override
     286        public Set<OsmPrimitive> getAdded() {
     287            return Collections.unmodifiableSet(add);
     288        }
     289    }
     290}
  • src/org/openstreetmap/josm/data/osm/event/SelectionEventManager.java

     
    22package org.openstreetmap.josm.data.osm.event;
    33
    44import java.util.Collection;
     5import java.util.Collections;
     6import java.util.HashSet;
    57import java.util.List;
    68import java.util.Objects;
     9import java.util.Set;
    710import java.util.concurrent.CopyOnWriteArrayList;
     11import java.util.stream.Stream;
    812
    913import javax.swing.SwingUtilities;
    1014
     15import org.openstreetmap.josm.Main;
    1116import org.openstreetmap.josm.data.SelectionChangedListener;
     17import org.openstreetmap.josm.data.osm.DataSelectionListener;
    1218import org.openstreetmap.josm.data.osm.DataSet;
    1319import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1420import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
     21import org.openstreetmap.josm.gui.layer.MainLayerManager;
     22import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
     23import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
    1524
    1625/**
    17  * Similar like {@link DatasetEventManager}, just for selection events. Because currently selection changed
    18  * event are global, only FIRE_IN_EDT and FIRE_EDT_CONSOLIDATED modes are really useful
     26 * Similar like {@link DatasetEventManager}, just for selection events.
     27 *
     28 * It allows to register listeners to global selection events for the selection in the current edit layer.
     29 *
     30 * If you want to listen to selections to a specific data layer,
     31 * you can register a listener to that layer by using {@link DataSet#addSelectionListener(DataSelectionListener)}
     32 *
    1933 * @since 2912
    2034 */
    21 public class SelectionEventManager implements SelectionChangedListener {
     35public class SelectionEventManager implements DataSelectionListener, ActiveLayerChangeListener {
    2236
    2337    private static final SelectionEventManager instance = new SelectionEventManager();
    2438
     
    5872    /**
    5973     * Constructs a new {@code SelectionEventManager}.
    6074     */
    61     public SelectionEventManager() {
    62         DataSet.addSelectionListener(this);
     75    protected SelectionEventManager() {
     76        MainLayerManager layerManager = Main.getLayerManager();
     77        // We do not allow for destructing this object.
     78        // Currently, this is a singleton class, so this is not required.
     79        layerManager.addAndFireActiveLayerChangeListener(this);
    6380    }
    6481
    6582    /**
     
    88105    }
    89106
    90107    @Override
    91     public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
     108    public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
     109        DataSet oldDataSet = e.getPreviousEditDataSet();
     110        if (oldDataSet != null) {
     111            // Fake a selection removal
     112            // Relying on this allows components to not have to monitor layer changes.
     113            // If we would not do this, e.g. the move command would have a hard time tracking which layer
     114            // the last moved selection was in.
     115            SelectionReplaceEvent event = new SelectionReplaceEvent(oldDataSet,
     116                    new HashSet<>(oldDataSet.getAllSelected()), Stream.empty());
     117            selectionChanged(event);
     118            oldDataSet.removeSelectionListener(this);
     119        }
     120        DataSet newDataSet = e.getSource().getEditDataSet();
     121        if (newDataSet != null) {
     122            newDataSet.addSelectionListener(this);
     123            // Fake a selection add
     124            SelectionReplaceEvent event = new SelectionReplaceEvent(newDataSet,
     125                    Collections.emptySet(), newDataSet.getAllSelected().stream());
     126            selectionChanged(event);
     127        }
     128    }
     129
     130    @Override
     131    public void selectionChanged(SelectionChangeEvent e) {
     132        Set<OsmPrimitive> newSelection = e.getSelection();
    92133        fireEvents(normalListeners, newSelection);
    93134        selection = newSelection;
    94135        SwingUtilities.invokeLater(edtRunnable);
  • src/org/openstreetmap/josm/actions/mapmode/SelectAction.java

     
    849849            updateKeyModifiers(e);
    850850            if (ctrl) mergePrims(e.getPoint());
    851851        }
    852         getLayerManager().getEditDataSet().fireSelectionChanged();
    853852    }
    854853
    855854    static class ConfirmMoveDialog extends ExtendedDialog {
  • src/org/openstreetmap/josm/actions/SelectNonBranchingWaySequences.java

     
    159159        } while (way != null);
    160160
    161161        if (selectionChanged)
    162             data.setSelected(selection, true);
     162            data.setSelected(selection);
    163163    }
    164164}
  • src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java

     
    7575
    7676        // make sure everybody is notified about the changes
    7777        //
    78         layer.data.fireSelectionChanged();
    7978        editor.setRelation(newRelation);
    8079        if (editor instanceof RelationEditor) {
    8180            RelationDialogManager.getRelationDialogManager().updateContext(
     
    107106        memberTableModel.applyToRelation(editedRelation);
    108107        if (!editedRelation.hasEqualSemanticAttributes(editor.getRelation(), false)) {
    109108            Main.main.undoRedo.add(new ChangeCommand(editor.getRelation(), editedRelation));
    110             layer.data.fireSelectionChanged();
    111109        }
    112110    }
    113111
  • test/unit/org/openstreetmap/josm/data/osm/event/SelectionEventManagerTest.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.osm.event;
     3
     4import static org.junit.Assert.assertEquals;
     5import static org.junit.Assert.assertNull;
     6
     7import java.util.Arrays;
     8import java.util.Collection;
     9import java.util.HashSet;
     10
     11import org.junit.Rule;
     12import org.junit.Test;
     13import org.openstreetmap.josm.Main;
     14import org.openstreetmap.josm.command.CommandTest.CommandTestDataWithRelation;
     15import org.openstreetmap.josm.data.SelectionChangedListener;
     16import org.openstreetmap.josm.data.osm.OsmPrimitive;
     17import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
     18import org.openstreetmap.josm.testutils.JOSMTestRules;
     19
     20import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     21
     22/**
     23 * Tests the {@link SelectionEventManager}
     24 * @author Michael Zangl
     25 * @since xxx
     26 */
     27public class SelectionEventManagerTest {
     28    private final class SelectionListener implements SelectionChangedListener {
     29        private Collection<? extends OsmPrimitive> newSelection;
     30
     31        @Override
     32        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
     33            this.newSelection = newSelection;
     34        }
     35    }
     36
     37    /**
     38     */
     39    @Rule
     40    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     41    public JOSMTestRules test = new JOSMTestRules().preferences();
     42
     43    /**
     44     * Tests that events in the active layer are propagated.
     45     */
     46    @Test
     47    public void test() {
     48        // automatically adds the layers
     49        CommandTestDataWithRelation testData1 = new CommandTestDataWithRelation();
     50        CommandTestDataWithRelation testData2 = new CommandTestDataWithRelation();
     51        Main.getLayerManager().setActiveLayer(testData1.layer);
     52        assertEquals(testData1.layer, Main.getLayerManager().getEditLayer());
     53
     54        SelectionListener listener = new SelectionListener();
     55        SelectionEventManager.getInstance().addSelectionListener(listener , FireMode.IMMEDIATELY);
     56        assertNull(listener.newSelection);
     57
     58        // active layer, should change
     59        testData1.layer.data.setSelected(testData1.existingNode.getPrimitiveId());
     60        assertEquals(new HashSet<OsmPrimitive>(Arrays.asList(testData1.existingNode)), listener.newSelection);
     61
     62        listener.newSelection = null;
     63        testData1.layer.data.clearSelection(testData1.existingNode.getPrimitiveId());
     64        assertEquals(new HashSet<OsmPrimitive>(Arrays.asList()), listener.newSelection);
     65
     66        listener.newSelection = null;
     67        testData1.layer.data.addSelected(testData1.existingNode2.getPrimitiveId());
     68        assertEquals(new HashSet<OsmPrimitive>(Arrays.asList(testData1.existingNode2)), listener.newSelection);
     69
     70        // changing to other dataset should trigger a empty selection
     71        listener.newSelection = null;
     72        Main.getLayerManager().setActiveLayer(testData2.layer);
     73        assertEquals(new HashSet<OsmPrimitive>(Arrays.asList()), listener.newSelection);
     74
     75        // This should not trigger anything, since the layer is not active any more.
     76        listener.newSelection = null;
     77        testData1.layer.data.clearSelection(testData1.existingNode.getPrimitiveId());
     78        assertNull(listener.newSelection);
     79    }
     80
     81}