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

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

Allow to globally add the new selection listeners that get detailed events.

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