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