001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm.event;
003
004import java.util.Collection;
005import java.util.List;
006import java.util.concurrent.CopyOnWriteArrayList;
007
008import javax.swing.SwingUtilities;
009
010import org.openstreetmap.josm.data.SelectionChangedListener;
011import org.openstreetmap.josm.data.osm.DataSet;
012import org.openstreetmap.josm.data.osm.OsmPrimitive;
013import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
014
015/**
016 * Similar like {@link DatasetEventManager}, just for selection events. Because currently selection changed
017 * event are global, only FIRE_IN_EDT and FIRE_EDT_CONSOLIDATED modes are really useful
018 *
019 */
020public class SelectionEventManager implements SelectionChangedListener {
021
022    private static final SelectionEventManager instance = new SelectionEventManager();
023
024    public static SelectionEventManager getInstance() {
025        return instance;
026    }
027
028    private static class ListenerInfo {
029        final SelectionChangedListener listener;
030
031        public ListenerInfo(SelectionChangedListener listener) {
032            this.listener = listener;
033        }
034
035        @Override
036        public int hashCode() {
037            return listener.hashCode();
038        }
039
040        @Override
041        public boolean equals(Object o) {
042            return o instanceof ListenerInfo && ((ListenerInfo)o).listener == listener;
043        }
044    }
045
046    private Collection<? extends OsmPrimitive> selection;
047    private final CopyOnWriteArrayList<ListenerInfo> inEDTListeners = new CopyOnWriteArrayList<>();
048    private final CopyOnWriteArrayList<ListenerInfo> normalListeners = new CopyOnWriteArrayList<>();
049
050    /**
051     * Constructs a new {@code SelectionEventManager}.
052     */
053    public SelectionEventManager() {
054        DataSet.addSelectionListener(this);
055    }
056
057    public void addSelectionListener(SelectionChangedListener listener, FireMode fireMode) {
058        if (fireMode == FireMode.IN_EDT)
059            throw new UnsupportedOperationException("IN_EDT mode not supported, you probably want to use IN_EDT_CONSOLIDATED.");
060        if (fireMode == FireMode.IN_EDT || fireMode == FireMode.IN_EDT_CONSOLIDATED) {
061            inEDTListeners.addIfAbsent(new ListenerInfo(listener));
062        } else {
063            normalListeners.addIfAbsent(new ListenerInfo(listener));
064        }
065    }
066
067    public void removeSelectionListener(SelectionChangedListener listener) {
068        ListenerInfo searchListener = new ListenerInfo(listener);
069        inEDTListeners.remove(searchListener);
070        normalListeners.remove(searchListener);
071    }
072
073    @Override
074    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
075        fireEvents(normalListeners, newSelection);
076        selection = newSelection;
077        SwingUtilities.invokeLater(edtRunnable);
078    }
079
080    private void fireEvents(List<ListenerInfo> listeners, Collection<? extends OsmPrimitive> newSelection) {
081        for (ListenerInfo listener: listeners) {
082            listener.listener.selectionChanged(newSelection);
083        }
084    }
085
086    private final Runnable edtRunnable = new Runnable() {
087        @Override
088        public void run() {
089            if (selection != null) {
090                fireEvents(inEDTListeners, selection);
091            }
092        }
093    };
094
095}