001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003 004import java.util.Collections; 005import java.util.HashSet; 006import java.util.LinkedHashSet; 007import java.util.Set; 008import java.util.stream.Collectors; 009import java.util.stream.Stream; 010 011import org.openstreetmap.josm.tools.CheckParameterUtil; 012 013/** 014 * This is a listener that listens to selection change events in the data set. 015 * @author Michael Zangl 016 * @since 12048 017 */ 018@FunctionalInterface 019public interface DataSelectionListener { 020 021 /** 022 * Called whenever the selection is changed. 023 * 024 * You get notified about the new selection, the elements that were added and removed and the layer that triggered the event. 025 * @param event The selection change event. 026 * @see SelectionChangeEvent 027 */ 028 void selectionChanged(SelectionChangeEvent event); 029 030 /** 031 * The event that is fired when the selection changed. 032 * @author Michael Zangl 033 * @since 12048 034 */ 035 interface SelectionChangeEvent { 036 /** 037 * Gets the previous selection 038 * <p> 039 * This collection cannot be modified and will not change. 040 * @return The old selection 041 */ 042 Set<OsmPrimitive> getOldSelection(); 043 044 /** 045 * Gets the new selection. New elements are added to the end of the collection. 046 * <p> 047 * This collection cannot be modified and will not change. 048 * @return The new selection 049 */ 050 Set<OsmPrimitive> getSelection(); 051 052 /** 053 * Gets the primitives that have been removed from the selection. 054 * <p> 055 * Those are the primitives contained in {@link #getOldSelection()} but not in {@link #getSelection()} 056 * <p> 057 * This collection cannot be modified and will not change. 058 * @return The primitives that were removed 059 */ 060 Set<OsmPrimitive> getRemoved(); 061 062 /** 063 * Gets the primitives that have been added to the selection. 064 * <p> 065 * Those are the primitives contained in {@link #getSelection()} but not in {@link #getOldSelection()} 066 * <p> 067 * This collection cannot be modified and will not change. 068 * @return The primitives that were added 069 */ 070 Set<OsmPrimitive> getAdded(); 071 072 /** 073 * Gets the data set that triggered this selection event. 074 * @return The data set. 075 */ 076 DataSet getSource(); 077 078 /** 079 * Test if this event did not change anything. 080 * <p> 081 * This will return <code>false</code> for all events that are sent to listeners, so you don't need to test it. 082 * @return <code>true</code> if this did not change the selection. 083 */ 084 default boolean isNop() { 085 return getAdded().isEmpty() && getRemoved().isEmpty(); 086 } 087 } 088 089 /** 090 * The base class for selection events 091 * @author Michael Zangl 092 * @since 12048 093 */ 094 abstract class AbstractSelectionEvent implements SelectionChangeEvent { 095 private final DataSet source; 096 private final Set<OsmPrimitive> old; 097 098 public AbstractSelectionEvent(DataSet source, Set<OsmPrimitive> old) { 099 CheckParameterUtil.ensureParameterNotNull(source, "source"); 100 CheckParameterUtil.ensureParameterNotNull(old, "old"); 101 this.source = source; 102 this.old = Collections.unmodifiableSet(old); 103 } 104 105 @Override 106 public Set<OsmPrimitive> getOldSelection() { 107 return old; 108 } 109 110 @Override 111 public DataSet getSource() { 112 return source; 113 } 114 } 115 116 117 /** 118 * The selection is replaced by a new selection 119 * @author Michael Zangl 120 * @since 12048 121 */ 122 class SelectionReplaceEvent extends AbstractSelectionEvent { 123 private final Set<OsmPrimitive> current; 124 private Set<OsmPrimitive> removed; 125 private Set<OsmPrimitive> added; 126 127 /** 128 * Create a {@link SelectionReplaceEvent} 129 * @param source The source dataset 130 * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. 131 * @param newSelection The primitives of the new selection. 132 */ 133 public SelectionReplaceEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> newSelection) { 134 super(source, old); 135 this.current = newSelection.collect(Collectors.toCollection(LinkedHashSet::new)); 136 } 137 138 @Override 139 public Set<OsmPrimitive> getSelection() { 140 return current; 141 } 142 143 @Override 144 public synchronized Set<OsmPrimitive> getRemoved() { 145 if (removed == null) { 146 removed = getOldSelection().stream() 147 .filter(p -> !current.contains(p)) 148 .collect(Collectors.toCollection(LinkedHashSet::new)); 149 } 150 return removed; 151 } 152 153 @Override 154 public synchronized Set<OsmPrimitive> getAdded() { 155 if (added == null) { 156 added = current.stream() 157 .filter(p -> !getOldSelection().contains(p)).collect(Collectors.toCollection(LinkedHashSet::new)); 158 } 159 return added; 160 } 161 162 @Override 163 public String toString() { 164 return "SelectionReplaceEvent [current=" + current + ", removed=" + removed + ", added=" + added + ']'; 165 } 166 } 167 168 /** 169 * Primitives are added to the selection 170 * @author Michael Zangl 171 * @since 12048 172 */ 173 class SelectionAddEvent extends AbstractSelectionEvent { 174 private final Set<OsmPrimitive> add; 175 private final Set<OsmPrimitive> current; 176 177 /** 178 * Create a {@link SelectionAddEvent} 179 * @param source The source dataset 180 * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. 181 * @param toAdd The primitives to add. 182 */ 183 public SelectionAddEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toAdd) { 184 super(source, old); 185 this.add = toAdd 186 .filter(p -> !old.contains(p)) 187 .collect(Collectors.toCollection(LinkedHashSet::new)); 188 if (this.add.isEmpty()) { 189 this.current = this.getOldSelection(); 190 } else { 191 this.current = new LinkedHashSet<>(old); 192 this.current.addAll(add); 193 } 194 } 195 196 @Override 197 public Set<OsmPrimitive> getSelection() { 198 return Collections.unmodifiableSet(current); 199 } 200 201 @Override 202 public Set<OsmPrimitive> getRemoved() { 203 return Collections.emptySet(); 204 } 205 206 @Override 207 public Set<OsmPrimitive> getAdded() { 208 return Collections.unmodifiableSet(add); 209 } 210 211 @Override 212 public String toString() { 213 return "SelectionAddEvent [add=" + add + ", current=" + current + ']'; 214 } 215 } 216 217 /** 218 * Primitives are removed from the selection 219 * @author Michael Zangl 220 * @since 12048 221 */ 222 class SelectionRemoveEvent extends AbstractSelectionEvent { 223 private final Set<OsmPrimitive> remove; 224 private final Set<OsmPrimitive> current; 225 226 /** 227 * Create a {@link SelectionRemoveEvent} 228 * @param source The source dataset 229 * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. 230 * @param toRemove The primitives to remove. 231 */ 232 public SelectionRemoveEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toRemove) { 233 super(source, old); 234 this.remove = toRemove 235 .filter(old::contains) 236 .collect(Collectors.toCollection(LinkedHashSet::new)); 237 if (this.remove.isEmpty()) { 238 this.current = this.getOldSelection(); 239 } else { 240 HashSet<OsmPrimitive> currentSet = new LinkedHashSet<>(old); 241 currentSet.removeAll(remove); 242 current = currentSet; 243 } 244 } 245 246 @Override 247 public Set<OsmPrimitive> getSelection() { 248 return Collections.unmodifiableSet(current); 249 } 250 251 @Override 252 public Set<OsmPrimitive> getRemoved() { 253 return Collections.unmodifiableSet(remove); 254 } 255 256 @Override 257 public Set<OsmPrimitive> getAdded() { 258 return Collections.emptySet(); 259 } 260 261 @Override 262 public String toString() { 263 return "SelectionRemoveEvent [remove=" + remove + ", current=" + current + ']'; 264 } 265 } 266 267 /** 268 * Toggle the selected state of a primitive 269 * @author Michael Zangl 270 * @since 12048 271 */ 272 class SelectionToggleEvent extends AbstractSelectionEvent { 273 private final Set<OsmPrimitive> current; 274 private final Set<OsmPrimitive> remove; 275 private final Set<OsmPrimitive> add; 276 277 /** 278 * Create a {@link SelectionToggleEvent} 279 * @param source The source dataset 280 * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. 281 * @param toToggle The primitives to toggle. 282 */ 283 public SelectionToggleEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toToggle) { 284 super(source, old); 285 HashSet<OsmPrimitive> currentSet = new LinkedHashSet<>(old); 286 HashSet<OsmPrimitive> removeSet = new LinkedHashSet<>(); 287 HashSet<OsmPrimitive> addSet = new LinkedHashSet<>(); 288 toToggle.forEach(p -> { 289 if (currentSet.remove(p)) { 290 removeSet.add(p); 291 } else { 292 addSet.add(p); 293 currentSet.add(p); 294 } 295 }); 296 this.current = Collections.unmodifiableSet(currentSet); 297 this.remove = Collections.unmodifiableSet(removeSet); 298 this.add = Collections.unmodifiableSet(addSet); 299 } 300 301 @Override 302 public Set<OsmPrimitive> getSelection() { 303 return Collections.unmodifiableSet(current); 304 } 305 306 @Override 307 public Set<OsmPrimitive> getRemoved() { 308 return Collections.unmodifiableSet(remove); 309 } 310 311 @Override 312 public Set<OsmPrimitive> getAdded() { 313 return Collections.unmodifiableSet(add); 314 } 315 316 @Override 317 public String toString() { 318 return "SelectionToggleEvent [current=" + current + ", remove=" + remove + ", add=" + add + ']'; 319 } 320 } 321}