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}