source: josm/trunk/src/org/openstreetmap/josm/data/osm/event/SelectionEventManager.java@ 13223

Last change on this file since 13223 was 13223, checked in by Don-vip, 7 years ago

fix #15008 - make sure EDT listeners receive correct selection events

  • Property svn:eol-style set to native
File size: 8.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm.event;
3
4import java.util.Collections;
5import java.util.HashSet;
6import java.util.List;
7import java.util.Objects;
8import java.util.concurrent.CopyOnWriteArrayList;
9import java.util.stream.Stream;
10
11import org.openstreetmap.josm.data.SelectionChangedListener;
12import org.openstreetmap.josm.data.osm.DataIntegrityProblemException;
13import org.openstreetmap.josm.data.osm.DataSelectionListener;
14import org.openstreetmap.josm.data.osm.DataSet;
15import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
16import org.openstreetmap.josm.gui.MainApplication;
17import org.openstreetmap.josm.gui.layer.MainLayerManager;
18import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
19import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
20import org.openstreetmap.josm.gui.util.GuiHelper;
21import org.openstreetmap.josm.tools.bugreport.BugReport;
22import org.openstreetmap.josm.tools.bugreport.ReportedException;
23
24/**
25 * Similar like {@link DatasetEventManager}, just for selection events.
26 *
27 * It allows to register listeners to global selection events for the selection in the current edit layer.
28 *
29 * If you want to listen to selections to a specific data layer,
30 * you can register a listener to that layer by using {@link DataSet#addSelectionListener(DataSelectionListener)}
31 *
32 * @since 2912
33 */
34public class SelectionEventManager implements DataSelectionListener, ActiveLayerChangeListener {
35
36 private static final SelectionEventManager INSTANCE = new SelectionEventManager();
37
38 /**
39 * Returns the unique instance.
40 * @return the unique instance
41 */
42 public static SelectionEventManager getInstance() {
43 return INSTANCE;
44 }
45
46 private interface ListenerInfo {
47 void fire(SelectionChangeEvent event);
48 }
49
50 private static class OldListenerInfo implements ListenerInfo {
51 private final SelectionChangedListener listener;
52
53 OldListenerInfo(SelectionChangedListener listener) {
54 this.listener = listener;
55 }
56
57 @Override
58 public void fire(SelectionChangeEvent event) {
59 listener.selectionChanged(event.getSelection());
60 }
61
62 @Override
63 public int hashCode() {
64 return Objects.hash(listener);
65 }
66
67 @Override
68 public boolean equals(Object o) {
69 if (this == o) return true;
70 if (o == null || getClass() != o.getClass()) return false;
71 OldListenerInfo that = (OldListenerInfo) o;
72 return Objects.equals(listener, that.listener);
73 }
74
75 @Override
76 public String toString() {
77 return "OldListenerInfo [listener=" + listener + ']';
78 }
79 }
80
81 private static class DataListenerInfo implements ListenerInfo {
82 private final DataSelectionListener listener;
83
84 DataListenerInfo(DataSelectionListener listener) {
85 this.listener = listener;
86 }
87
88 @Override
89 public void fire(SelectionChangeEvent event) {
90 listener.selectionChanged(event);
91 }
92
93 @Override
94 public int hashCode() {
95 return Objects.hash(listener);
96 }
97
98 @Override
99 public boolean equals(Object o) {
100 if (this == o) return true;
101 if (o == null || getClass() != o.getClass()) return false;
102 DataListenerInfo that = (DataListenerInfo) o;
103 return Objects.equals(listener, that.listener);
104 }
105
106 @Override
107 public String toString() {
108 return "DataListenerInfo [listener=" + listener + ']';
109 }
110 }
111
112 private final CopyOnWriteArrayList<ListenerInfo> inEDTListeners = new CopyOnWriteArrayList<>();
113 private final CopyOnWriteArrayList<ListenerInfo> immedatelyListeners = new CopyOnWriteArrayList<>();
114
115 /**
116 * Constructs a new {@code SelectionEventManager}.
117 */
118 protected SelectionEventManager() {
119 MainLayerManager layerManager = MainApplication.getLayerManager();
120 // We do not allow for destructing this object.
121 // Currently, this is a singleton class, so this is not required.
122 layerManager.addAndFireActiveLayerChangeListener(this);
123 }
124
125 /**
126 * Registers a new {@code SelectionChangedListener}.
127 *
128 * It is preferred to add a DataSelectionListener - that listener will receive more information about the event.
129 * @param listener listener to add
130 * @param fireMode Set this to IN_EDT_CONSOLIDATED if you want the event to be fired in the EDT thread.
131 * Set it to IMMEDIATELY if you want the event to fire in the thread that caused the selection update.
132 */
133 public void addSelectionListener(SelectionChangedListener listener, FireMode fireMode) {
134 if (fireMode == FireMode.IN_EDT) {
135 throw new UnsupportedOperationException("IN_EDT mode not supported, you probably want to use IN_EDT_CONSOLIDATED.");
136 } else if (fireMode == FireMode.IN_EDT_CONSOLIDATED) {
137 inEDTListeners.addIfAbsent(new OldListenerInfo(listener));
138 } else {
139 immedatelyListeners.addIfAbsent(new OldListenerInfo(listener));
140 }
141 }
142
143 /**
144 * Adds a selection listener that gets notified for selections immediately.
145 * @param listener The listener to add.
146 * @since 12098
147 */
148 public void addSelectionListener(DataSelectionListener listener) {
149 immedatelyListeners.addIfAbsent(new DataListenerInfo(listener));
150 }
151
152 /**
153 * Adds a selection listener that gets notified for selections later in the EDT thread.
154 * Events are sent in the right order but may be delayed.
155 * @param listener The listener to add.
156 * @since 12098
157 */
158 public void addSelectionListenerForEdt(DataSelectionListener listener) {
159 inEDTListeners.addIfAbsent(new DataListenerInfo(listener));
160 }
161
162 /**
163 * Unregisters a {@code SelectionChangedListener}.
164 * @param listener listener to remove
165 */
166 public void removeSelectionListener(SelectionChangedListener listener) {
167 remove(new OldListenerInfo(listener));
168 }
169
170 /**
171 * Unregisters a {@code DataSelectionListener}.
172 * @param listener listener to remove
173 * @since 12098
174 */
175 public void removeSelectionListener(DataSelectionListener listener) {
176 remove(new DataListenerInfo(listener));
177 }
178
179 private void remove(ListenerInfo searchListener) {
180 inEDTListeners.remove(searchListener);
181 immedatelyListeners.remove(searchListener);
182 }
183
184 @Override
185 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
186 DataSet oldDataSet = e.getPreviousEditDataSet();
187 if (oldDataSet != null) {
188 // Fake a selection removal
189 // Relying on this allows components to not have to monitor layer changes.
190 // If we would not do this, e.g. the move command would have a hard time tracking which layer
191 // the last moved selection was in.
192 selectionChanged(new SelectionReplaceEvent(oldDataSet,
193 new HashSet<>(oldDataSet.getAllSelected()), Stream.empty()));
194 oldDataSet.removeSelectionListener(this);
195 }
196 DataSet newDataSet = e.getSource().getEditDataSet();
197 if (newDataSet != null) {
198 newDataSet.addSelectionListener(this);
199 // Fake a selection add
200 selectionChanged(new SelectionReplaceEvent(newDataSet,
201 Collections.emptySet(), newDataSet.getAllSelected().stream()));
202 }
203 }
204
205 @Override
206 public void selectionChanged(SelectionChangeEvent event) {
207 fireEvent(immedatelyListeners, event);
208 try {
209 GuiHelper.runInEDTAndWaitWithException(() -> fireEvent(inEDTListeners, event));
210 } catch (ReportedException e) {
211 throw BugReport.intercept(e).put("event", event).put("inEDTListeners", inEDTListeners);
212 }
213 }
214
215 private static void fireEvent(List<ListenerInfo> listeners, SelectionChangeEvent event) {
216 for (ListenerInfo listener: listeners) {
217 try {
218 listener.fire(event);
219 } catch (DataIntegrityProblemException e) {
220 throw BugReport.intercept(e).put("event", event).put("listeners", listeners);
221 }
222 }
223 }
224
225 /**
226 * Only to be used during unit tests, to reset the state. Do not use it in plugins/other code.
227 * Called after the layer manager was reset by the test framework.
228 */
229 public void resetState() {
230 inEDTListeners.clear();
231 immedatelyListeners.clear();
232 MainApplication.getLayerManager().addAndFireActiveLayerChangeListener(this);
233 }
234}
Note: See TracBrowser for help on using the repository browser.