Changeset 12048 in josm


Ignore:
Timestamp:
2017-05-03T16:09:46+02:00 (17 months ago)
Author:
michael2402
Message:

Add per-layer selection listeners. Make selection listener code more generic.

Location:
trunk
Files:
2 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/SelectNonBranchingWaySequences.java

    r9999 r12048  
    160160
    161161        if (selectionChanged)
    162             data.setSelected(selection, true);
     162            data.setSelected(selection);
    163163    }
    164164}
  • trunk/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java

    r11978 r12048  
    850850            if (ctrl) mergePrims(e.getPoint());
    851851        }
    852         getLayerManager().getEditDataSet().fireSelectionChanged();
    853852    }
    854853
  • trunk/src/org/openstreetmap/josm/data/osm/DataSet.java

    r12014 r12048  
    66import java.awt.geom.Area;
    77import java.util.ArrayList;
    8 import java.util.Arrays;
    98import java.util.Collection;
    109import java.util.Collections;
     
    1211import java.util.HashSet;
    1312import java.util.Iterator;
    14 import java.util.LinkedHashSet;
    1513import java.util.LinkedList;
    1614import java.util.List;
     
    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;
     
    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;
     
    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;
     
    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();
     184
     185    /**
     186     * A list of listeners that listen to selection changes on this layer.
     187     */
     188    private final ListenerList<DataSelectionListener> selectionListeners = ListenerList.create();
    170189
    171190    private Area cachedDataSourceArea;
     
    184203        // the listener, projection change listeners are managed as WeakReferences.
    185204        Main.addProjectionChangeListener(this);
     205        addSelectionListener((DataSelectionListener) e -> fireDreprecatedSelectionChange(e.getSelection()));
    186206    }
    187207
     
    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);
     
    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,
     677     *      org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode)
     678     *      To add a global listener.
     679     */
     680    public void addSelectionListener(DataSelectionListener listener) {
     681        selectionListeners.addListener(listener);
     682    }
     683
     684    /**
     685     * Remove a listener that listens to selection changes in this specific data set.
     686     * @param listener The listener.
     687     * @see #addSelectionListener(DataSelectionListener)
     688     */
     689    public void removeSelectionListener(DataSelectionListener listener) {
     690        selectionListeners.removeListener(listener);
     691    }
     692
     693    /*---------------------------------------------------
     694     *   OLD SELECTION HANDLING
     695     *---------------------------------------------------*/
     696
     697    /**
    656698     * A list of listeners to selection changed events. The list is static, as listeners register
    657699     * themselves for any dataset selection changes that occur, regardless of the current active
     
    663705     * Adds a new selection listener.
    664706     * @param listener The selection listener to add
     707     * @see #addSelectionListener(DataSelectionListener)
     708     * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
    665709     */
    666710    public static void addSelectionListener(SelectionChangedListener listener) {
     
    671715     * Removes a selection listener.
    672716     * @param listener The selection listener to remove
     717     * @see #removeSelectionListener(DataSelectionListener)
     718     * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
    673719     */
    674720    public static void removeSelectionListener(SelectionChangedListener listener) {
     
    679725     * Notifies all registered {@link SelectionChangedListener} about the current selection in
    680726     * this dataset.
    681      *
    682      */
     727     * @deprecated You should never need to do this from the outside.
     728     */
     729    @Deprecated
    683730    public void fireSelectionChanged() {
    684         Collection<? extends OsmPrimitive> currentSelection = getAllSelected();
     731        fireDreprecatedSelectionChange(getAllSelected());
     732    }
     733
     734    private static void fireDreprecatedSelectionChange(Collection<? extends OsmPrimitive> currentSelection) {
    685735        for (SelectionChangedListener l : selListeners) {
    686736            l.selectionChanged(currentSelection);
    687737        }
    688738    }
    689 
    690     private Set<OsmPrimitive> selectedPrimitives = new LinkedHashSet<>();
    691     private Collection<OsmPrimitive> selectionSnapshot;
    692739
    693740    /**
     
    754801     */
    755802    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;
     803        return currentSelectedPrimitives;
    764804    }
    765805
     
    793833     */
    794834    public boolean selectionEmpty() {
    795         return selectedPrimitives.isEmpty();
     835        return currentSelectedPrimitives.isEmpty();
    796836    }
    797837
     
    802842     */
    803843    public boolean isSelected(OsmPrimitive osm) {
    804         return selectedPrimitives.contains(osm);
    805     }
    806 
    807     /**
    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;
     844        return currentSelectedPrimitives.contains(osm);
    843845    }
    844846
     
    875877     * @param selection the selection
    876878     * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
    877      */
     879     * @deprecated Use {@link #setSelected(Collection)} instead. To bee removed end of 2017. Does not seem to be used by plugins.
     880     */
     881    @Deprecated
    878882    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         }
     883        setSelected(selection);
    894884    }
    895885
     
    901891     */
    902892    public void setSelected(Collection<? extends PrimitiveId> selection) {
    903         setSelected(selection, true /* fire selection change event */);
     893        setSelected(selection.stream());
    904894    }
    905895
     
    908898     * and notifies all {@link SelectionChangedListener}.
    909899     *
    910      * @param osm the primitives to set
     900     * @param osm the primitives to set. <code>null</code> values are ignored for now, but this may be removed in the future.
    911901     */
    912902    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);
     903        setSelected(Stream.of(osm).filter(Objects::nonNull));
     904    }
     905
     906    private void setSelected(Stream<? extends PrimitiveId> stream) {
     907        doSelectionChange(old -> new SelectionReplaceEvent(this, old,
     908                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
    919909    }
    920910
     
    926916     */
    927917    public void addSelected(Collection<? extends PrimitiveId> selection) {
    928         addSelected(selection, true /* fire selection change event */);
     918        addSelected(selection.stream());
    929919    }
    930920
     
    936926     */
    937927    public void addSelected(PrimitiveId... osm) {
    938         addSelected(Arrays.asList(osm));
    939     }
    940 
    941     /**
    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
    948      */
    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);
     928        addSelected(Stream.of(osm));
     929    }
     930
     931    private void addSelected(Stream<? extends PrimitiveId> stream) {
     932        doSelectionChange(old -> new SelectionAddEvent(this, old,
     933                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
     934    }
     935
     936    /**
     937     * Removes the selection from every value in the collection.
     938     * @param osm The collection of ids to remove the selection from.
     939     */
     940    public void clearSelection(PrimitiveId... osm) {
     941        clearSelection(Stream.of(osm));
     942    }
     943
     944    /**
     945     * Removes the selection from every value in the collection.
     946     * @param list The collection of ids to remove the selection from.
     947     */
     948    public void clearSelection(Collection<? extends PrimitiveId> list) {
     949        clearSelection(list.stream());
     950    }
     951
     952    /**
     953     * Clears the current selection.
     954     */
     955    public void clearSelection() {
     956        setSelected(Stream.empty());
     957    }
     958
     959    private void clearSelection(Stream<? extends PrimitiveId> stream) {
     960        doSelectionChange(old -> new SelectionRemoveEvent(this, old,
     961                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
     962    }
     963
     964    /**
     965     * Toggles the selected state of the given collection of primitives.
     966     * @param osm The primitives to toggle
     967     */
     968    public void toggleSelected(Collection<? extends PrimitiveId> osm) {
     969        toggleSelected(osm.stream());
     970    }
     971
     972    /**
     973     * Toggles the selected state of the given collection of primitives.
     974     * @param osm The primitives to toggle
     975     */
     976    public void toggleSelected(PrimitiveId... osm) {
     977        toggleSelected(Stream.of(osm));
     978    }
     979
     980    private void toggleSelected(Stream<? extends PrimitiveId> stream) {
     981        doSelectionChange(old -> new SelectionToggleEvent(this, old,
     982                stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
     983    }
     984
     985    /**
     986     * Do a selection change.
     987     * <p>
     988     * This is the only method that changes the current selection state.
     989     * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives.
     990     * @return true iff the command did change the selection.
     991     * @since 12048
     992     */
     993    private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) {
     994        lock.readLock().lock();
     995        try {
     996            synchronized (selectionLock) {
     997                SelectionChangeEvent event = command.apply(currentSelectedPrimitives);
     998                if (event.isNop()) {
     999                    return false;
    9561000                }
    957             }
    958             if (changed) {
    959                 selectionSnapshot = null;
    960             }
    961         }
    962         if (fireSelectionChangeEvent && changed) {
    963             fireSelectionChanged();
    964         }
    965         return changed;
     1001                currentSelectedPrimitives = event.getSelection();
     1002                selectionListeners.fireEvent(l -> l.selectionChanged(event));
     1003                return true;
     1004            }
     1005        } finally {
     1006            lock.readLock().unlock();
     1007        }
    9661008    }
    9671009
     
    9781020    public void clearHighlightedWaySegments() {
    9791021        setHighlightedWaySegments(new ArrayList<WaySegment>());
    980     }
    981 
    982     /**
    983      * Removes the selection from every value in the collection.
    984      * @param osm The collection of ids to remove the selection from.
    985      */
    986     public void clearSelection(PrimitiveId... osm) {
    987         clearSelection(Arrays.asList(osm));
    988     }
    989 
    990     /**
    991      * Removes the selection from every value in the collection.
    992      * @param list The collection of ids to remove the selection from.
    993      */
    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         }
    1010     }
    1011 
    1012     /**
    1013      * Clears the current selection.
    1014      */
    1015     public void clearSelection() {
    1016         if (!selectedPrimitives.isEmpty()) {
    1017             synchronized (selectionLock) {
    1018                 selectedPrimitives.clear();
    1019                 selectionSnapshot = null;
    1020             }
    1021             fireSelectionChanged();
    1022         }
    10231022    }
    10241023
     
    13721371        beginUpdate();
    13731372        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             }
     1373            cleanupDeleted(Stream.concat(
     1374                    nodes.stream(), Stream.concat(ways.stream(), relations.stream())));
    13841375        } finally {
    13851376            endUpdate();
     
    13871378    }
    13881379
    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;
     1380    private void cleanupDeleted(Stream<? extends OsmPrimitive> it) {
     1381        clearSelection(it
     1382                .filter(primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew()))
     1383                .peek(allPrimitives::remove)
     1384                .peek(primitive -> primitive.setDataset(null)));
    14081385    }
    14091386
  • trunk/src/org/openstreetmap/josm/data/osm/event/SelectionEventManager.java

    r11928 r12048  
    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();
     
    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
     
    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;
  • trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java

    r11848 r12048  
    7676        // make sure everybody is notified about the changes
    7777        //
    78         layer.data.fireSelectionChanged();
    7978        editor.setRelation(newRelation);
    8079        if (editor instanceof RelationEditor) {
     
    108107        if (!editedRelation.hasEqualSemanticAttributes(editor.getRelation(), false)) {
    109108            Main.main.undoRedo.add(new ChangeCommand(editor.getRelation(), editedRelation));
    110             layer.data.fireSelectionChanged();
    111109        }
    112110    }
Note: See TracChangeset for help on using the changeset viewer.