source: josm/trunk/src/org/openstreetmap/josm/data/osm/DataSelectionListener.java@ 12069

Last change on this file since 12069 was 12069, checked in by michael2402, 7 years ago

Fix #14737: Preserve selection order and add comments that it is ordered. Add unit tests.

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