Ticket #13467: patch-13467.patch
File patch-13467.patch, 39.3 KB (added by , 22 months ago) |
---|
-
src/org/openstreetmap/josm/data/osm/DataSet.java
5 5 6 6 import java.awt.geom.Area; 7 7 import java.util.ArrayList; 8 import java.util.Arrays;9 8 import java.util.Collection; 10 9 import java.util.Collections; 11 10 import java.util.HashMap; 12 11 import java.util.HashSet; 13 12 import java.util.Iterator; 14 import java.util.LinkedHashSet;15 13 import java.util.LinkedList; 16 14 import java.util.List; 17 15 import java.util.Map; … … 21 19 import java.util.concurrent.locks.Lock; 22 20 import java.util.concurrent.locks.ReadWriteLock; 23 21 import java.util.concurrent.locks.ReentrantReadWriteLock; 22 import java.util.function.Function; 24 23 import java.util.function.Predicate; 25 24 import java.util.stream.Collectors; 25 import java.util.stream.Stream; 26 26 27 27 import org.openstreetmap.josm.Main; 28 28 import org.openstreetmap.josm.data.Bounds; … … 32 32 import org.openstreetmap.josm.data.SelectionChangedListener; 33 33 import org.openstreetmap.josm.data.coor.EastNorth; 34 34 import org.openstreetmap.josm.data.coor.LatLon; 35 import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionAddEvent; 36 import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionChangeEvent; 37 import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionRemoveEvent; 38 import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionReplaceEvent; 39 import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionToggleEvent; 35 40 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 36 41 import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent; 37 42 import org.openstreetmap.josm.data.osm.event.DataChangedEvent; … … 41 46 import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 42 47 import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 43 48 import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 49 import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 44 50 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 45 51 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 46 52 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; … … 166 172 private UploadPolicy uploadPolicy; 167 173 168 174 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 175 176 /** 177 * The mutex lock that is used to synchronize selection changes. 178 */ 169 179 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(); 170 184 185 /** 186 * A list of listeners that listen to selection changes on this layer. 187 */ 188 private final ListenerList<DataSelectionListener> selectionListeners = ListenerList.create(); 189 171 190 private Area cachedDataSourceArea; 172 191 private List<Bounds> cachedDataSourceBounds; 173 192 … … 183 202 // Transparently register as projection change listener. No need to explicitly remove 184 203 // the listener, projection change listeners are managed as WeakReferences. 185 204 Main.addProjectionChangeListener(this); 205 addSelectionListener((DataSelectionListener) e -> fireDreprecatedSelectionChange(e.getSelection())); 186 206 } 187 207 188 208 /** … … 636 656 } 637 657 if (!success) 638 658 throw new JosmRuntimeException("failed to remove primitive: "+primitive); 639 synchronized (selectionLock) { 640 selectedPrimitives.remove(primitive); 641 selectionSnapshot = null; 642 } 659 clearSelection(primitiveId); 643 660 allPrimitives.remove(primitive); 644 661 primitive.setDataset(null); 645 662 firePrimitivesRemoved(Collections.singletonList(primitive), false); … … 653 670 *---------------------------------------------------*/ 654 671 655 672 /** 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 /** 656 697 * A list of listeners to selection changed events. The list is static, as listeners register 657 698 * themselves for any dataset selection changes that occur, regardless of the current active 658 699 * dataset. (However, the selection does only change in the active layer) … … 662 703 /** 663 704 * Adds a new selection listener. 664 705 * @param listener The selection listener to add 706 * @see #addSelectionListener(DataSelectionListener) 707 * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener) 665 708 */ 666 709 public static void addSelectionListener(SelectionChangedListener listener) { 667 710 ((CopyOnWriteArrayList<SelectionChangedListener>) selListeners).addIfAbsent(listener); … … 670 713 /** 671 714 * Removes a selection listener. 672 715 * @param listener The selection listener to remove 716 * @see #removeSelectionListener(DataSelectionListener) 717 * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener) 673 718 */ 674 719 public static void removeSelectionListener(SelectionChangedListener listener) { 675 720 selListeners.remove(listener); … … 678 723 /** 679 724 * Notifies all registered {@link SelectionChangedListener} about the current selection in 680 725 * this dataset. 681 * 726 * @deprecated You should never need to do this from the outside. 682 727 */ 728 @Deprecated 683 729 public void fireSelectionChanged() { 684 Collection<? extends OsmPrimitive> currentSelection = getAllSelected(); 730 fireDreprecatedSelectionChange(getAllSelected()); 731 } 732 733 private static void fireDreprecatedSelectionChange(Collection<? extends OsmPrimitive> currentSelection) { 685 734 for (SelectionChangedListener l : selListeners) { 686 735 l.selectionChanged(currentSelection); 687 736 } 688 737 } 689 738 690 private Set<OsmPrimitive> selectedPrimitives = new LinkedHashSet<>();691 private Collection<OsmPrimitive> selectionSnapshot;692 693 739 /** 694 740 * Returns selected nodes and ways. 695 741 * @return selected nodes and ways … … 753 799 * @return unmodifiable collection of primitives 754 800 */ 755 801 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; 764 803 } 765 804 766 805 /** … … 792 831 * @return whether the selection is empty or not 793 832 */ 794 833 public boolean selectionEmpty() { 795 return selectedPrimitives.isEmpty();834 return currentSelectedPrimitives.isEmpty(); 796 835 } 797 836 798 837 /** … … 801 840 * @return whether {@code osm} is selected or not 802 841 */ 803 842 public boolean isSelected(OsmPrimitive osm) { 804 return selectedPrimitives.contains(osm);843 return currentSelectedPrimitives.contains(osm); 805 844 } 806 845 807 846 /** 808 * Toggles the selected state of the given collection of primitives.809 * @param osm The primitives to toggle810 */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 toggle829 */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 /**846 847 * set what virtual nodes should be highlighted. Requires a Collection of 847 848 * *WaySegments* to avoid a VirtualNode class that wouldn't have much use 848 849 * otherwise. … … 874 875 * 875 876 * @param selection the selection 876 877 * @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. 877 879 */ 880 @Deprecated 878 881 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); 894 883 } 895 884 896 885 /** … … 900 889 * @param selection the selection 901 890 */ 902 891 public void setSelected(Collection<? extends PrimitiveId> selection) { 903 setSelected(selection , true /* fire selection change event */);892 setSelected(selection.stream()); 904 893 } 905 894 906 895 /** 907 896 * Sets the current selection to the primitives in <code>osm</code> 908 897 * and notifies all {@link SelectionChangedListener}. 909 898 * 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. 911 900 */ 912 901 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)); 919 903 } 920 904 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 921 910 /** 922 911 * Adds the primitives in <code>selection</code> to the current selection 923 912 * and notifies all {@link SelectionChangedListener}. … … 925 914 * @param selection the selection 926 915 */ 927 916 public void addSelected(Collection<? extends PrimitiveId> selection) { 928 addSelected(selection , true /* fire selection change event */);917 addSelected(selection.stream()); 929 918 } 930 919 931 920 /** … … 935 924 * @param osm the primitives to add 936 925 */ 937 926 public void addSelected(PrimitiveId... osm) { 938 addSelected( Arrays.asList(osm));927 addSelected(Stream.of(osm)); 939 928 } 940 929 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 941 935 /** 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. 948 938 */ 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)); 966 941 } 967 942 968 943 /** 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. 970 946 */ 971 public void clear HighlightedVirtualNodes() {972 setHighlightedVirtualNodes(new ArrayList<WaySegment>());947 public void clearSelection(Collection<? extends PrimitiveId> list) { 948 clearSelection(list.stream()); 973 949 } 974 950 975 951 /** 976 * clear all highlights of way segments952 * Clears the current selection. 977 953 */ 978 public void clear HighlightedWaySegments() {979 set HighlightedWaySegments(new ArrayList<WaySegment>());954 public void clearSelection() { 955 setSelected(Stream.empty()); 980 956 } 981 957 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 982 963 /** 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 985 966 */ 986 public void clearSelection(PrimitiveId...osm) {987 clearSelection(Arrays.asList(osm));967 public void toggleSelected(Collection<? extends PrimitiveId> osm) { 968 toggleSelected(osm.stream()); 988 969 } 989 970 990 971 /** 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 993 974 */ 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)); 1010 977 } 1011 978 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 1012 984 /** 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. 1014 990 */ 1015 public void clearSelection() { 1016 if (!selectedPrimitives.isEmpty()) { 991 private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) { 992 lock.readLock().lock(); 993 try { 1017 994 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; 1020 1002 } 1021 fireSelectionChanged(); 1003 } finally { 1004 lock.readLock().unlock(); 1022 1005 } 1023 1006 } 1024 1007 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 } 1025 1021 @Override 1026 1022 public synchronized Area getDataSourceArea() { 1027 1023 if (cachedDataSourceArea == null) { … … 1371 1367 public void cleanupDeletedPrimitives() { 1372 1368 beginUpdate(); 1373 1369 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()))); 1384 1372 } finally { 1385 1373 endUpdate(); 1386 1374 } 1387 1375 } 1388 1376 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))); 1408 1382 } 1409 1383 1410 1384 /** -
src/org/openstreetmap/josm/data/osm/DataSelectionListener.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.osm; 3 4 import java.util.Collections; 5 import java.util.HashSet; 6 import java.util.Set; 7 import java.util.stream.Collectors; 8 import java.util.stream.Stream; 9 10 import 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 18 public 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
2 2 package org.openstreetmap.josm.data.osm.event; 3 3 4 4 import java.util.Collection; 5 import java.util.Collections; 6 import java.util.HashSet; 5 7 import java.util.List; 6 8 import java.util.Objects; 9 import java.util.Set; 7 10 import java.util.concurrent.CopyOnWriteArrayList; 11 import java.util.stream.Stream; 8 12 9 13 import javax.swing.SwingUtilities; 10 14 15 import org.openstreetmap.josm.Main; 11 16 import org.openstreetmap.josm.data.SelectionChangedListener; 17 import org.openstreetmap.josm.data.osm.DataSelectionListener; 12 18 import org.openstreetmap.josm.data.osm.DataSet; 13 19 import org.openstreetmap.josm.data.osm.OsmPrimitive; 14 20 import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 21 import org.openstreetmap.josm.gui.layer.MainLayerManager; 22 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 23 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 15 24 16 25 /** 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 * 19 33 * @since 2912 20 34 */ 21 public class SelectionEventManager implements SelectionChangedListener {35 public class SelectionEventManager implements DataSelectionListener, ActiveLayerChangeListener { 22 36 23 37 private static final SelectionEventManager instance = new SelectionEventManager(); 24 38 … … 58 72 /** 59 73 * Constructs a new {@code SelectionEventManager}. 60 74 */ 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); 63 80 } 64 81 65 82 /** … … 88 105 } 89 106 90 107 @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(); 92 133 fireEvents(normalListeners, newSelection); 93 134 selection = newSelection; 94 135 SwingUtilities.invokeLater(edtRunnable); -
src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
849 849 updateKeyModifiers(e); 850 850 if (ctrl) mergePrims(e.getPoint()); 851 851 } 852 getLayerManager().getEditDataSet().fireSelectionChanged();853 852 } 854 853 855 854 static class ConfirmMoveDialog extends ExtendedDialog { -
src/org/openstreetmap/josm/actions/SelectNonBranchingWaySequences.java
159 159 } while (way != null); 160 160 161 161 if (selectionChanged) 162 data.setSelected(selection , true);162 data.setSelected(selection); 163 163 } 164 164 } -
src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java
75 75 76 76 // make sure everybody is notified about the changes 77 77 // 78 layer.data.fireSelectionChanged();79 78 editor.setRelation(newRelation); 80 79 if (editor instanceof RelationEditor) { 81 80 RelationDialogManager.getRelationDialogManager().updateContext( … … 107 106 memberTableModel.applyToRelation(editedRelation); 108 107 if (!editedRelation.hasEqualSemanticAttributes(editor.getRelation(), false)) { 109 108 Main.main.undoRedo.add(new ChangeCommand(editor.getRelation(), editedRelation)); 110 layer.data.fireSelectionChanged();111 109 } 112 110 } 113 111 -
test/unit/org/openstreetmap/josm/data/osm/event/SelectionEventManagerTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.osm.event; 3 4 import static org.junit.Assert.assertEquals; 5 import static org.junit.Assert.assertNull; 6 7 import java.util.Arrays; 8 import java.util.Collection; 9 import java.util.HashSet; 10 11 import org.junit.Rule; 12 import org.junit.Test; 13 import org.openstreetmap.josm.Main; 14 import org.openstreetmap.josm.command.CommandTest.CommandTestDataWithRelation; 15 import org.openstreetmap.josm.data.SelectionChangedListener; 16 import org.openstreetmap.josm.data.osm.OsmPrimitive; 17 import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 18 import org.openstreetmap.josm.testutils.JOSMTestRules; 19 20 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 21 22 /** 23 * Tests the {@link SelectionEventManager} 24 * @author Michael Zangl 25 * @since xxx 26 */ 27 public 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 }