// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.osm; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.openstreetmap.josm.tools.CheckParameterUtil; /** * This is a listener that listens to selection change events in the data set. * @author Michael Zangl * @since 12048 */ @FunctionalInterface public interface DataSelectionListener { /** * Called whenever the selection is changed. * * You get notified about the new selection, the elements that were added and removed and the layer that triggered the event. * @param event The selection change event. * @see SelectionChangeEvent */ void selectionChanged(SelectionChangeEvent event); /** * The event that is fired when the selection changed. * @author Michael Zangl * @since 12048 */ interface SelectionChangeEvent { /** * Gets the previous selection *

* This collection cannot be modified and will not change. * @return The old selection */ Set getOldSelection(); /** * Gets the new selection. New elements are added to the end of the collection. *

* This collection cannot be modified and will not change. * @return The new selection */ Set getSelection(); /** * Gets the primitives that have been removed from the selection. *

* Those are the primitives contained in {@link #getOldSelection()} but not in {@link #getSelection()} *

* This collection cannot be modified and will not change. * @return The primitives that were removed */ Set getRemoved(); /** * Gets the primitives that have been added to the selection. *

* Those are the primitives contained in {@link #getSelection()} but not in {@link #getOldSelection()} *

* This collection cannot be modified and will not change. * @return The primitives that were added */ Set getAdded(); /** * Gets the data set that triggered this selection event. * @return The data set. */ DataSet getSource(); /** * Test if this event did not change anything. *

* This will return false for all events that are sent to listeners, so you don't need to test it. * @return true if this did not change the selection. */ default boolean isNop() { return getAdded().isEmpty() && getRemoved().isEmpty(); } } /** * The base class for selection events * @author Michael Zangl * @since 12048 */ abstract class AbstractSelectionEvent implements SelectionChangeEvent { private final DataSet source; private final Set old; public AbstractSelectionEvent(DataSet source, Set old) { CheckParameterUtil.ensureParameterNotNull(source, "source"); CheckParameterUtil.ensureParameterNotNull(old, "old"); this.source = source; this.old = Collections.unmodifiableSet(old); } @Override public Set getOldSelection() { return old; } @Override public DataSet getSource() { return source; } } /** * The selection is replaced by a new selection * @author Michael Zangl * @since 12048 */ class SelectionReplaceEvent extends AbstractSelectionEvent { private final Set current; private Set removed; private Set added; /** * Create a {@link SelectionReplaceEvent} * @param source The source dataset * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. * @param newSelection The primitives of the new selection. */ public SelectionReplaceEvent(DataSet source, Set old, Stream newSelection) { super(source, old); this.current = newSelection.collect(Collectors.toCollection(LinkedHashSet::new)); } @Override public Set getSelection() { return current; } @Override public synchronized Set getRemoved() { if (removed == null) { removed = getOldSelection().stream() .filter(p -> !current.contains(p)) .collect(Collectors.toCollection(LinkedHashSet::new)); } return removed; } @Override public synchronized Set getAdded() { if (added == null) { added = current.stream() .filter(p -> !getOldSelection().contains(p)).collect(Collectors.toCollection(LinkedHashSet::new)); } return added; } } /** * Primitives are added to the selection * @author Michael Zangl * @since 12048 */ class SelectionAddEvent extends AbstractSelectionEvent { private final Set add; private final Set current; /** * Create a {@link SelectionAddEvent} * @param source The source dataset * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. * @param toAdd The primitives to add. */ public SelectionAddEvent(DataSet source, Set old, Stream toAdd) { super(source, old); this.add = toAdd .filter(p -> !old.contains(p)) .collect(Collectors.toCollection(LinkedHashSet::new)); if (this.add.isEmpty()) { this.current = this.getOldSelection(); } else { this.current = new LinkedHashSet<>(old); this.current.addAll(add); } } @Override public Set getSelection() { return Collections.unmodifiableSet(current); } @Override public Set getRemoved() { return Collections.emptySet(); } @Override public Set getAdded() { return Collections.unmodifiableSet(add); } } /** * Primitives are removed from the selection * @author Michael Zangl * @since 12048 */ class SelectionRemoveEvent extends AbstractSelectionEvent { private final Set remove; private final Set current; /** * Create a {@link SelectionRemoveEvent} * @param source The source dataset * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. * @param toRemove The primitives to remove. */ public SelectionRemoveEvent(DataSet source, Set old, Stream toRemove) { super(source, old); this.remove = toRemove .filter(old::contains) .collect(Collectors.toCollection(LinkedHashSet::new)); if (this.remove.isEmpty()) { this.current = this.getOldSelection(); } else { HashSet currentSet = new LinkedHashSet<>(old); currentSet.removeAll(remove); current = Collections.unmodifiableSet(currentSet); } } @Override public Set getSelection() { return Collections.unmodifiableSet(current); } @Override public Set getRemoved() { return Collections.unmodifiableSet(remove); } @Override public Set getAdded() { return Collections.emptySet(); } } /** * Toggle the selected state of a primitive * @author Michael Zangl * @since 12048 */ class SelectionToggleEvent extends AbstractSelectionEvent { private final Set current; private final Set remove; private final Set add; /** * Create a {@link SelectionToggleEvent} * @param source The source dataset * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. * @param toToggle The primitives to toggle. */ public SelectionToggleEvent(DataSet source, Set old, Stream toToggle) { super(source, old); HashSet currentSet = new LinkedHashSet<>(old); HashSet removeSet = new LinkedHashSet<>(); HashSet addSet = new LinkedHashSet<>(); toToggle.forEach(p -> { if (currentSet.remove(p)) { removeSet.add(p); } else { addSet.add(p); currentSet.add(p); } }); this.current = Collections.unmodifiableSet(currentSet); this.remove = Collections.unmodifiableSet(removeSet); this.add = Collections.unmodifiableSet(addSet); } @Override public Set getSelection() { return Collections.unmodifiableSet(current); } @Override public Set getRemoved() { return Collections.unmodifiableSet(remove); } @Override public Set getAdded() { return Collections.unmodifiableSet(add); } } }