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

Last change on this file since 12048 was 12048, checked in by michael2402, 16 months ago

Add per-layer selection listeners. Make selection listener code more generic.

  • Property svn:eol-style set to native
File size: 5.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm.event;
3
4import java.util.Collection;
5import java.util.Collections;
6import java.util.HashSet;
7import java.util.List;
8import java.util.Objects;
9import java.util.Set;
10import java.util.concurrent.CopyOnWriteArrayList;
11import java.util.stream.Stream;
12
13import javax.swing.SwingUtilities;
14
15import org.openstreetmap.josm.Main;
16import org.openstreetmap.josm.data.SelectionChangedListener;
17import org.openstreetmap.josm.data.osm.DataSelectionListener;
18import org.openstreetmap.josm.data.osm.DataSet;
19import org.openstreetmap.josm.data.osm.OsmPrimitive;
20import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
21import org.openstreetmap.josm.gui.layer.MainLayerManager;
22import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
23import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
24
25/**
26 * Similar like {@link DatasetEventManager}, just for selection events.
27 *
28 * It allows to register listeners to global selection events for the selection in the current edit layer.
29 *
30 * If you want to listen to selections to a specific data layer,
31 * you can register a listener to that layer by using {@link DataSet#addSelectionListener(DataSelectionListener)}
32 *
33 * @since 2912
34 */
35public class SelectionEventManager implements DataSelectionListener, ActiveLayerChangeListener {
36
37    private static final SelectionEventManager instance = new SelectionEventManager();
38
39    /**
40     * Returns the unique instance.
41     * @return the unique instance
42     */
43    public static SelectionEventManager getInstance() {
44        return instance;
45    }
46
47    private static class ListenerInfo {
48        private final SelectionChangedListener listener;
49
50        ListenerInfo(SelectionChangedListener listener) {
51            this.listener = listener;
52        }
53
54        @Override
55        public int hashCode() {
56            return Objects.hash(listener);
57        }
58
59        @Override
60        public boolean equals(Object o) {
61            if (this == o) return true;
62            if (o == null || getClass() != o.getClass()) return false;
63            ListenerInfo that = (ListenerInfo) o;
64            return Objects.equals(listener, that.listener);
65        }
66    }
67
68    private Collection<? extends OsmPrimitive> selection;
69    private final CopyOnWriteArrayList<ListenerInfo> inEDTListeners = new CopyOnWriteArrayList<>();
70    private final CopyOnWriteArrayList<ListenerInfo> normalListeners = new CopyOnWriteArrayList<>();
71
72    /**
73     * Constructs a new {@code SelectionEventManager}.
74     */
75    protected SelectionEventManager() {
76        MainLayerManager layerManager = Main.getLayerManager();
77        // We do not allow for destructing this object.
78        // Currently, this is a singleton class, so this is not required.
79        layerManager.addAndFireActiveLayerChangeListener(this);
80    }
81
82    /**
83     * Registers a new {@code SelectionChangedListener}.
84     * @param listener listener to add
85     * @param fireMode EDT firing mode
86     */
87    public void addSelectionListener(SelectionChangedListener listener, FireMode fireMode) {
88        if (fireMode == FireMode.IN_EDT)
89            throw new UnsupportedOperationException("IN_EDT mode not supported, you probably want to use IN_EDT_CONSOLIDATED.");
90        if (fireMode == FireMode.IN_EDT || fireMode == FireMode.IN_EDT_CONSOLIDATED) {
91            inEDTListeners.addIfAbsent(new ListenerInfo(listener));
92        } else {
93            normalListeners.addIfAbsent(new ListenerInfo(listener));
94        }
95    }
96
97    /**
98     * Unregisters a {@code SelectionChangedListener}.
99     * @param listener listener to remove
100     */
101    public void removeSelectionListener(SelectionChangedListener listener) {
102        ListenerInfo searchListener = new ListenerInfo(listener);
103        inEDTListeners.remove(searchListener);
104        normalListeners.remove(searchListener);
105    }
106
107    @Override
108    public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
109        DataSet oldDataSet = e.getPreviousEditDataSet();
110        if (oldDataSet != null) {
111            // Fake a selection removal
112            // Relying on this allows components to not have to monitor layer changes.
113            // If we would not do this, e.g. the move command would have a hard time tracking which layer
114            // the last moved selection was in.
115            SelectionReplaceEvent event = new SelectionReplaceEvent(oldDataSet,
116                    new HashSet<>(oldDataSet.getAllSelected()), Stream.empty());
117            selectionChanged(event);
118            oldDataSet.removeSelectionListener(this);
119        }
120        DataSet newDataSet = e.getSource().getEditDataSet();
121        if (newDataSet != null) {
122            newDataSet.addSelectionListener(this);
123            // Fake a selection add
124            SelectionReplaceEvent event = new SelectionReplaceEvent(newDataSet,
125                    Collections.emptySet(), newDataSet.getAllSelected().stream());
126            selectionChanged(event);
127        }
128    }
129
130    @Override
131    public void selectionChanged(SelectionChangeEvent e) {
132        Set<OsmPrimitive> newSelection = e.getSelection();
133        fireEvents(normalListeners, newSelection);
134        selection = newSelection;
135        SwingUtilities.invokeLater(edtRunnable);
136    }
137
138    private static void fireEvents(List<ListenerInfo> listeners, Collection<? extends OsmPrimitive> newSelection) {
139        for (ListenerInfo listener: listeners) {
140            listener.listener.selectionChanged(newSelection);
141        }
142    }
143
144    private final Runnable edtRunnable = () -> {
145        if (selection != null) {
146            fireEvents(inEDTListeners, selection);
147        }
148    };
149}
Note: See TracBrowser for help on using the repository browser.