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 * The selection is replaced by a new selection 118 * @author Michael Zangl 119 * @since 12048 120 */ 121 class SelectionReplaceEvent extends AbstractSelectionEvent { 122 private final Set<OsmPrimitive> current; 123 private Set<OsmPrimitive> removed; 124 private Set<OsmPrimitive> added; 125 126 /** 127 * Create a {@link SelectionReplaceEvent} 128 * @param source The source dataset 129 * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. 130 * @param newSelection The primitives of the new selection. 131 */ 132 public SelectionReplaceEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> newSelection) { 133 super(source, old); 134 this.current = newSelection.collect(Collectors.toCollection(LinkedHashSet::new)); 135 } 136 137 @Override 138 public Set<OsmPrimitive> getSelection() { 139 return current; 140 } 141 142 @Override 143 public synchronized Set<OsmPrimitive> getRemoved() { 144 if (removed == null) { 145 removed = getOldSelection().stream() 146 .filter(p -> !current.contains(p)) 147 .collect(Collectors.toCollection(LinkedHashSet::new)); 148 } 149 return removed; 150 } 151 152 @Override 153 public synchronized Set<OsmPrimitive> getAdded() { 154 if (added == null) { 155 added = current.stream() 156 .filter(p -> !getOldSelection().contains(p)).collect(Collectors.toCollection(LinkedHashSet::new)); 157 } 158 return added; 159 } 160 161 @Override 162 public String toString() { 163 return "SelectionReplaceEvent [current=" + current + ", removed=" + removed + ", added=" + added + ']'; 164 } 165 } 166 167 /** 168 * Primitives are added to the selection 169 * @author Michael Zangl 170 * @since 12048 171 */ 172 class SelectionAddEvent extends AbstractSelectionEvent { 173 private final Set<OsmPrimitive> add; 174 private final Set<OsmPrimitive> current; 175 176 /** 177 * Create a {@link SelectionAddEvent} 178 * @param source The source dataset 179 * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. 180 * @param toAdd The primitives to add. 181 */ 182 public SelectionAddEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toAdd) { 183 super(source, old); 184 this.add = toAdd 185 .filter(p -> !old.contains(p)) 186 .collect(Collectors.toCollection(LinkedHashSet::new)); 187 if (this.add.isEmpty()) { 188 this.current = this.getOldSelection(); 189 } else { 190 this.current = new LinkedHashSet<>(old); 191 this.current.addAll(add); 192 } 193 } 194 195 @Override 196 public Set<OsmPrimitive> getSelection() { 197 return Collections.unmodifiableSet(current); 198 } 199 200 @Override 201 public Set<OsmPrimitive> getRemoved() { 202 return Collections.emptySet(); 203 } 204 205 @Override 206 public Set<OsmPrimitive> getAdded() { 207 return Collections.unmodifiableSet(add); 208 } 209 210 @Override 211 public String toString() { 212 return "SelectionAddEvent [add=" + add + ", current=" + current + ']'; 213 } 214 } 215 216 /** 217 * Primitives are removed from the selection 218 * @author Michael Zangl 219 * @since 12048 220 */ 221 class SelectionRemoveEvent extends AbstractSelectionEvent { 222 private final Set<OsmPrimitive> remove; 223 private final Set<OsmPrimitive> current; 224 225 /** 226 * Create a {@link SelectionRemoveEvent} 227 * @param source The source dataset 228 * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. 229 * @param toRemove The primitives to remove. 230 */ 231 public SelectionRemoveEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toRemove) { 232 super(source, old); 233 this.remove = toRemove 234 .filter(old::contains) 235 .collect(Collectors.toCollection(LinkedHashSet::new)); 236 if (this.remove.isEmpty()) { 237 this.current = this.getOldSelection(); 238 } else { 239 HashSet<OsmPrimitive> currentSet = new LinkedHashSet<>(old); 240 currentSet.removeAll(remove); 241 current = currentSet; 242 } 243 } 244 245 @Override 246 public Set<OsmPrimitive> getSelection() { 247 return Collections.unmodifiableSet(current); 248 } 249 250 @Override 251 public Set<OsmPrimitive> getRemoved() { 252 return Collections.unmodifiableSet(remove); 253 } 254 255 @Override 256 public Set<OsmPrimitive> getAdded() { 257 return Collections.emptySet(); 258 } 259 260 @Override 261 public String toString() { 262 return "SelectionRemoveEvent [remove=" + remove + ", current=" + current + ']'; 263 } 264 } 265 266 /** 267 * Toggle the selected state of a primitive 268 * @author Michael Zangl 269 * @since 12048 270 */ 271 class SelectionToggleEvent extends AbstractSelectionEvent { 272 private final Set<OsmPrimitive> current; 273 private final Set<OsmPrimitive> remove; 274 private final Set<OsmPrimitive> add; 275 276 /** 277 * Create a {@link SelectionToggleEvent} 278 * @param source The source dataset 279 * @param old The old primitves that were previously selected. The caller needs to ensure that this set is not modifed. 280 * @param toToggle The primitives to toggle. 281 */ 282 public SelectionToggleEvent(DataSet source, Set<OsmPrimitive> old, Stream<OsmPrimitive> toToggle) { 283 super(source, old); 284 HashSet<OsmPrimitive> currentSet = new LinkedHashSet<>(old); 285 HashSet<OsmPrimitive> removeSet = new LinkedHashSet<>(); 286 HashSet<OsmPrimitive> addSet = new LinkedHashSet<>(); 287 toToggle.forEach(p -> { 288 if (currentSet.remove(p)) { 289 removeSet.add(p); 290 } else { 291 addSet.add(p); 292 currentSet.add(p); 293 } 294 }); 295 this.current = Collections.unmodifiableSet(currentSet); 296 this.remove = Collections.unmodifiableSet(removeSet); 297 this.add = Collections.unmodifiableSet(addSet); 298 } 299 300 @Override 301 public Set<OsmPrimitive> getSelection() { 302 return Collections.unmodifiableSet(current); 303 } 304 305 @Override 306 public Set<OsmPrimitive> getRemoved() { 307 return Collections.unmodifiableSet(remove); 308 } 309 310 @Override 311 public Set<OsmPrimitive> getAdded() { 312 return Collections.unmodifiableSet(add); 313 } 314 315 @Override 316 public String toString() { 317 return "SelectionToggleEvent [current=" + current + ", remove=" + remove + ", add=" + add + ']'; 318 } 319 } 320}