001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.plugins.eventbus;
003
004import static org.openstreetmap.josm.eventbus.JosmEventBus.post;
005
006import java.awt.event.KeyEvent;
007
008import org.openstreetmap.josm.Main;
009import org.openstreetmap.josm.actions.ExpertToggleAction;
010import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener;
011import org.openstreetmap.josm.data.SelectionChangedListener;
012import org.openstreetmap.josm.data.SystemOfMeasurement;
013import org.openstreetmap.josm.data.SystemOfMeasurement.SoMChangeListener;
014import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener;
015import org.openstreetmap.josm.data.conflict.ConflictCollection;
016import org.openstreetmap.josm.data.conflict.IConflictListener;
017import org.openstreetmap.josm.data.gpx.GpxData;
018import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;
019import org.openstreetmap.josm.data.gpx.GpxTrack.GpxTrackChangeListener;
020import org.openstreetmap.josm.data.osm.ChangesetCache;
021import org.openstreetmap.josm.data.osm.ChangesetCacheListener;
022import org.openstreetmap.josm.data.osm.DataSelectionListener;
023import org.openstreetmap.josm.data.osm.DataSet;
024import org.openstreetmap.josm.data.osm.HighlightUpdateListener;
025import org.openstreetmap.josm.data.osm.NoteData;
026import org.openstreetmap.josm.data.osm.NoteData.NoteDataUpdateListener;
027import org.openstreetmap.josm.data.osm.PrimitiveId;
028import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
029import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
030import org.openstreetmap.josm.data.osm.event.DataSetListener;
031import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
032import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
033import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
034import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
035import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
036import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
037import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
038import org.openstreetmap.josm.data.osm.history.HistoryDataSetListener;
039import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
040import org.openstreetmap.josm.gui.MainApplication;
041import org.openstreetmap.josm.gui.MapFrame;
042import org.openstreetmap.josm.gui.MapFrame.MapModeChangeListener;
043import org.openstreetmap.josm.gui.NavigatableComponent;
044import org.openstreetmap.josm.gui.NavigatableComponent.ZoomChangeListener;
045import org.openstreetmap.josm.gui.conflict.tags.MultiValueCellEditor.NavigationListener;
046import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
047import org.openstreetmap.josm.gui.dialogs.LayerListDialog.LayerListModelListener;
048import org.openstreetmap.josm.gui.dialogs.relation.IMemberModelListener;
049import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer;
050import org.openstreetmap.josm.gui.layer.GpxLayer;
051import org.openstreetmap.josm.gui.layer.ImageryLayer;
052import org.openstreetmap.josm.gui.layer.Layer;
053import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
054import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
055import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
056import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
057import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
058import org.openstreetmap.josm.gui.layer.MainLayerManager.LayerAvailabilityEvent;
059import org.openstreetmap.josm.gui.layer.MainLayerManager.LayerAvailabilityListener;
060import org.openstreetmap.josm.gui.layer.MapViewPaintable.PaintableInvalidationListener;
061import org.openstreetmap.josm.gui.layer.NoteLayer;
062import org.openstreetmap.josm.gui.layer.OsmDataLayer;
063import org.openstreetmap.josm.gui.layer.OsmDataLayer.LayerStateChangeListener;
064import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings.FilterChangeListener;
065import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings.DisplaySettingsChangeListener;
066import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
067import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.MapPaintSylesUpdateListener;
068import org.openstreetmap.josm.gui.preferences.imagery.AddImageryPanel.ContentValidationListener;
069import org.openstreetmap.josm.gui.preferences.server.ProxyPreference;
070import org.openstreetmap.josm.gui.preferences.server.ProxyPreferenceListener;
071import org.openstreetmap.josm.gui.progress.ProgressMonitor.CancelListener;
072import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetListener;
073import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
074import org.openstreetmap.josm.gui.util.KeyPressReleaseListener;
075import org.openstreetmap.josm.gui.util.ModifierExListener;
076import org.openstreetmap.josm.io.OsmApi;
077import org.openstreetmap.josm.io.OsmApi.OsmApiInitializationListener;
078import org.openstreetmap.josm.plugins.Plugin;
079import org.openstreetmap.josm.plugins.PluginInformation;
080import org.openstreetmap.josm.plugins.eventbus.actions.ExpertModeChangedEvent;
081import org.openstreetmap.josm.plugins.eventbus.data.CommandQueueEvent;
082import org.openstreetmap.josm.plugins.eventbus.data.SoMChangedEvent;
083import org.openstreetmap.josm.plugins.eventbus.data.conflict.ConflictsAddedEvent;
084import org.openstreetmap.josm.plugins.eventbus.data.conflict.ConflictsRemovedEvent;
085import org.openstreetmap.josm.plugins.eventbus.data.osm.NoteDataUpdatedEvent;
086import org.openstreetmap.josm.plugins.eventbus.data.osm.SelectedNoteChangedEvent;
087import org.openstreetmap.josm.plugins.eventbus.data.osm.history.HistoryClearedEvent;
088import org.openstreetmap.josm.plugins.eventbus.data.osm.history.HistoryUpdatedEvent;
089import org.openstreetmap.josm.plugins.eventbus.data.projection.ProjectionChangedEvent;
090import org.openstreetmap.josm.plugins.eventbus.gui.MapFrameInitializedEvent;
091import org.openstreetmap.josm.plugins.eventbus.gui.MapModeChangeEvent;
092import org.openstreetmap.josm.plugins.eventbus.gui.ZoomChangedEvent;
093import org.openstreetmap.josm.plugins.eventbus.gui.conflict.tags.NextDecisionEvent;
094import org.openstreetmap.josm.plugins.eventbus.gui.conflict.tags.PreviousDecisionEvent;
095import org.openstreetmap.josm.plugins.eventbus.gui.dialogs.LayerListRefreshedEvent;
096import org.openstreetmap.josm.plugins.eventbus.gui.dialogs.LayerVisibleEvent;
097import org.openstreetmap.josm.plugins.eventbus.gui.dialogs.relation.MemberVisibleEvent;
098import org.openstreetmap.josm.plugins.eventbus.gui.layer.LayerStateChangeEvent;
099import org.openstreetmap.josm.plugins.eventbus.gui.layer.imagery.FilterChangedEvent;
100import org.openstreetmap.josm.plugins.eventbus.gui.mappaint.MapPaintStyleEntryUpdatedEvent;
101import org.openstreetmap.josm.plugins.eventbus.gui.mappaint.MapPaintStylesUpdatedEvent;
102import org.openstreetmap.josm.plugins.eventbus.gui.preferences.imagery.ContentValidationEvent;
103import org.openstreetmap.josm.plugins.eventbus.gui.preferences.server.ProxyPreferenceChangedEvent;
104import org.openstreetmap.josm.plugins.eventbus.gui.progress.OperationCancelledEvent;
105import org.openstreetmap.josm.plugins.eventbus.gui.tagging.presets.TaggingPresetModifiedEvent;
106import org.openstreetmap.josm.plugins.eventbus.gui.util.KeyPressedEvent;
107import org.openstreetmap.josm.plugins.eventbus.gui.util.KeyReleasedEvent;
108import org.openstreetmap.josm.plugins.eventbus.gui.util.ModifierExChangedEvent;
109import org.openstreetmap.josm.plugins.eventbus.io.OsmApiInitializedEvent;
110import org.openstreetmap.josm.spi.preferences.Config;
111import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
112import org.openstreetmap.josm.tools.Logging;
113
114/**
115 * Event bus plugin, providing an event bus more powerful than the traditional listeners registration.
116 */
117public class EventBusPlugin extends Plugin {
118
119    // Defines an instance for each type of listener, and keeps a reference to avoid garbage collection
120
121    private final ActiveLayerChangeListener activeLayerChangeListener = e -> post(e);
122    private final ChangesetCacheListener changesetCacheListener = e -> post(e);
123    private final DataSelectionListener selectionListener = e -> post(e);
124    private final ExpertModeChangeListener expertModeChangeListener = x -> post(new ExpertModeChangedEvent(this, x));
125    private final GpxDataChangeListener gpxChangeListener = e -> post(e);
126    private final GpxTrackChangeListener gpxTrackChangeListener = e -> post(e);
127    private final HighlightUpdateListener highlightUpdateListener = e -> post(e);
128    private final SelectionChangedListener selectionChangedListener = e -> post(e);
129    private final PreferenceChangedListener preferenceChangedListener = e -> post(e);
130    private final PaintableInvalidationListener paintableInvalidationListener = e -> post(e);
131    private final NoteDataUpdateListener noteDataUpdateListener = new NoteDataUpdateListener() {
132        @Override
133        public void selectedNoteChanged(NoteData noteData) {
134            post(new SelectedNoteChangedEvent(this, noteData));
135        }
136
137        @Override
138        public void noteDataUpdated(NoteData data) {
139            post(new NoteDataUpdatedEvent(this, data));
140        }
141    };
142
143    private final IConflictListener conflictsListener = new IConflictListener() {
144        @Override
145        public void onConflictsRemoved(ConflictCollection conflicts) {
146            post(new ConflictsRemovedEvent(this, conflicts));
147        }
148
149        @Override
150        public void onConflictsAdded(ConflictCollection conflicts) {
151            post(new ConflictsAddedEvent(this, conflicts));
152        }
153    };
154
155    private final FilterChangeListener filterChangeListener = () -> post(new FilterChangedEvent(this));
156    private final DisplaySettingsChangeListener displaySettingsChangeListener = e -> post(e);
157
158    private final LayerStateChangeListener layerStateChangeListener =
159            (layer, newValue) -> post(new LayerStateChangeEvent(this, layer, newValue));
160
161    private final LayerAvailabilityListener layerAvailabilityListener = new LayerAvailabilityListener() {
162        @Override
163        public void beforeFirstLayerAdded(LayerAvailabilityEvent e) {
164            post(e);
165        }
166
167        @Override
168        public void afterLastLayerRemoved(LayerAvailabilityEvent e) {
169            post(e);
170        }
171    };
172
173    // CHECKSTYLE.OFF: AnonInnerLengthCheck
174    private final LayerChangeListener layerChangeListener = new LayerChangeListener() {
175        @Override
176        public void layerAdded(LayerAddEvent e) {
177            post(e);
178            Layer layer = e.getAddedLayer();
179            layer.addInvalidationListener(paintableInvalidationListener);
180            if (layer instanceof OsmDataLayer) {
181                ((OsmDataLayer) layer).addLayerStateChangeListener(layerStateChangeListener);
182                DataSet ds = ((OsmDataLayer) layer).data;
183                ds.addDataSetListener(dataSetListener);
184                ds.addHighlightUpdateListener(highlightUpdateListener);
185                ds.addSelectionListener(selectionListener);
186                ds.getConflicts().addConflictListener(conflictsListener);
187            } else if (layer instanceof GpxLayer) {
188                GpxData gpx = ((GpxLayer) layer).data;
189                gpx.addChangeListener(gpxChangeListener);
190                // TODO: cannot add a listener for GpxTrackChangeListener. It would require first new events for tracks being added and removed
191                Logging.debug("TODO: add" + gpxTrackChangeListener);
192            } else if (layer instanceof NoteLayer) {
193                NoteData notes = ((NoteLayer) layer).getNoteData();
194                notes.addNoteDataUpdateListener(noteDataUpdateListener);
195            } else if (layer instanceof ImageryLayer) {
196                ((ImageryLayer) layer).getFilterSettings().addFilterChangeListener(filterChangeListener);
197                if (layer instanceof AbstractTileSourceLayer) {
198                    ((AbstractTileSourceLayer<?>) layer).getDisplaySettings().addSettingsChangeListener(displaySettingsChangeListener);
199                }
200            }
201        }
202
203        @Override
204        public void layerRemoving(LayerRemoveEvent e) {
205            post(e);
206            Layer layer = e.getRemovedLayer();
207            layer.removeInvalidationListener(paintableInvalidationListener);
208            if (layer instanceof OsmDataLayer) {
209                ((OsmDataLayer) layer).removeLayerStateChangeListener(layerStateChangeListener);
210                DataSet ds = ((OsmDataLayer) layer).data;
211                ds.removeDataSetListener(dataSetListener);
212                ds.removeHighlightUpdateListener(highlightUpdateListener);
213                ds.removeSelectionListener(selectionListener);
214                ds.getConflicts().removeConflictListener(conflictsListener);
215                // TODO: cannot add a listener for GpxTrackChangeListener. It would require first new events for tracks being added and removed
216                Logging.debug("TODO: remove" + gpxTrackChangeListener);
217            } else if (layer instanceof GpxLayer) {
218                GpxData gpx = ((GpxLayer) layer).data;
219                gpx.removeChangeListener(gpxChangeListener);
220            } else if (layer instanceof NoteLayer) {
221                NoteData notes = ((NoteLayer) layer).getNoteData();
222                notes.removeNoteDataUpdateListener(noteDataUpdateListener);
223            } else if (layer instanceof ImageryLayer) {
224                ((ImageryLayer) layer).getFilterSettings().removeFilterChangeListener(filterChangeListener);
225                if (layer instanceof AbstractTileSourceLayer) {
226                    ((AbstractTileSourceLayer<?>) layer).getDisplaySettings().removeSettingsChangeListener(displaySettingsChangeListener);
227                }
228            }
229        }
230
231        @Override
232        public void layerOrderChanged(LayerOrderChangeEvent e) {
233            post(e);
234        }
235    };
236    // CHECKSTYLE.ON: AnonInnerLengthCheck
237
238    private final DataSetListener dataSetListener = new DataSetListener() {
239        @Override
240        public void wayNodesChanged(WayNodesChangedEvent event) {
241            post(event);
242        }
243
244        @Override
245        public void tagsChanged(TagsChangedEvent event) {
246            post(event);
247        }
248
249        @Override
250        public void relationMembersChanged(RelationMembersChangedEvent event) {
251            post(event);
252        }
253
254        @Override
255        public void primitivesRemoved(PrimitivesRemovedEvent event) {
256            post(event);
257        }
258
259        @Override
260        public void primitivesAdded(PrimitivesAddedEvent event) {
261            post(event);
262        }
263
264        @Override
265        public void otherDatasetChange(AbstractDatasetChangedEvent event) {
266            post(event);
267        }
268
269        @Override
270        public void nodeMoved(NodeMovedEvent event) {
271            post(event);
272        }
273
274        @Override
275        public void dataChanged(DataChangedEvent event) {
276            post(event);
277        }
278    };
279
280    private final HistoryDataSetListener historyDataSetListener = new HistoryDataSetListener() {
281        @Override
282        public void historyUpdated(HistoryDataSet source, PrimitiveId id) {
283            post(new HistoryUpdatedEvent(this, source, id));
284        }
285
286        @Override
287        public void historyDataSetCleared(HistoryDataSet source) {
288            post(new HistoryClearedEvent(this, source));
289        }
290    };
291
292    private final ProjectionChangeListener projectionChangeListener = (oldValue, newValue) -> {
293        post(new ProjectionChangedEvent(this, oldValue, newValue));
294    };
295
296    private final SoMChangeListener soMChangeListener = (oldSoM, newSoM) -> {
297        post(new SoMChangedEvent(this, oldSoM, newSoM));
298    };
299
300    private final CommandQueueListener commandQueueListener = (queueSize, redoSize) -> {
301        post(new CommandQueueEvent(this, queueSize, redoSize));
302    };
303
304    private final NavigationListener navigationListener = new NavigationListener() {
305        @Override
306        public void gotoNextDecision() {
307            post(new NextDecisionEvent(this));
308        }
309
310        @Override
311        public void gotoPreviousDecision() {
312            post(new PreviousDecisionEvent(this));
313        }
314    };
315
316    private final LayerListModelListener layerListModelListener = new LayerListModelListener() {
317        @Override
318        public void makeVisible(int index, Layer layer) {
319            post(new LayerVisibleEvent(this, index, layer));
320        }
321
322        @Override
323        public void refresh() {
324            post(new LayerListRefreshedEvent(this));
325        }
326    };
327
328    private final IMemberModelListener memberModelListener = index -> post(new MemberVisibleEvent(this, index));
329    // private final DownloadSourceListener downloadSourceListener = source -> post(new DownloadSourceAddedEvent(source)); // TODO: not public
330    private final ContentValidationListener contentValidationListener = isValid -> post(new ContentValidationEvent(this, isValid));
331    private final ProxyPreferenceListener proxyPreferenceListener = () -> post(new ProxyPreferenceChangedEvent(this));
332    private final CancelListener cancelListener = () -> post(new OperationCancelledEvent(this));
333    private final TaggingPresetListener taggingPresetListener = () -> post(new TaggingPresetModifiedEvent(this));
334
335    private final MapPaintSylesUpdateListener mapPaintSylesUpdateListener = new MapPaintSylesUpdateListener() {
336        @Override
337        public void mapPaintStylesUpdated() {
338            post(new MapPaintStylesUpdatedEvent(this));
339        }
340
341        @Override
342        public void mapPaintStyleEntryUpdated(int index) {
343            post(new MapPaintStyleEntryUpdatedEvent(this, index));
344        }
345    };
346
347    private final OsmApiInitializationListener osmApiInitializationListener = api -> post(new OsmApiInitializedEvent(this, api));
348    //private final AudioListener audioListener = url -> post(new AudioPlayingEvent(url)); // TODO: not public
349    private final ZoomChangeListener zoomChangeListener = () -> post(new ZoomChangedEvent(this));
350    private final MapModeChangeListener mapModeChangeListener = (oldMode, newMode) -> post(new MapModeChangeEvent(this, oldMode, newMode));
351    private final ModifierExListener modifierExListener = modifiers -> post(new ModifierExChangedEvent(this, modifiers));
352    private final KeyPressReleaseListener keyPressReleaseListener = new KeyPressReleaseListener() {
353        @Override
354        public void doKeyPressed(KeyEvent e) {
355            post(new KeyPressedEvent(this, e));
356        }
357
358        @Override
359        public void doKeyReleased(KeyEvent e) {
360            post(new KeyReleasedEvent(this, e));
361        }
362    };
363
364    /**
365     * Constructs a new {@code EventBusPlugin}.
366     * @param info plugin information
367     */
368    public EventBusPlugin(PluginInformation info) {
369        super(info);
370        registerAllJosmListeners();
371        // TODO: cannot add a listener for NavigationListener. It would require first new events for MultiValueCellEditor being created
372        Logging.debug("TODO: add" + navigationListener);
373        // TODO: cannot add a listener for IMemberModelListener. It would require first new events for GenericRelationEditor being created
374        Logging.debug("TODO: add" + memberModelListener);
375        // TODO: cannot add a listener for ContentValidationListener. It would require first new events for AddImageryDialog being created
376        Logging.debug("TODO: add" + contentValidationListener);
377        // TODO: cannot add a listener for CancelListener. It would require first new events for ProgressMonitor being created
378        Logging.debug("TODO: add" + cancelListener);
379    }
380
381    /**
382     * Registers all JOSM listeners.
383     */
384    void registerAllJosmListeners() {
385        Main.addProjectionChangeListener(projectionChangeListener);
386        MainApplication.getLayerManager().addLayerChangeListener(layerChangeListener);
387        MainApplication.getLayerManager().addActiveLayerChangeListener(activeLayerChangeListener);
388        MainApplication.getLayerManager().addLayerAvailabilityListener(layerAvailabilityListener);
389        MainApplication.undoRedo.addCommandQueueListener(commandQueueListener);
390        ChangesetCache.getInstance().addChangesetCacheListener(changesetCacheListener);
391        DataSet.addSelectionListener(selectionChangedListener);
392        ExpertToggleAction.addExpertModeChangeListener(expertModeChangeListener);
393        HistoryDataSet.getInstance().addHistoryDataSetListener(historyDataSetListener);
394        SystemOfMeasurement.addSoMChangeListener(soMChangeListener);
395        MapPaintStyles.addMapPaintSylesUpdateListener(mapPaintSylesUpdateListener);
396        ProxyPreference.addProxyPreferenceListener(proxyPreferenceListener);
397        MapFrame.addMapModeChangeListener(mapModeChangeListener);
398        NavigatableComponent.addZoomChangeListener(zoomChangeListener);
399        TaggingPresets.addListener(taggingPresetListener);
400        OsmApi.addOsmApiInitializationListener(osmApiInitializationListener);
401        Config.getPref().addPreferenceChangeListener(preferenceChangedListener);
402    }
403
404    /**
405     * Unregisters all JOSM listeners.
406     */
407    void unregisterAllJosmListeners() {
408        Main.removeProjectionChangeListener(projectionChangeListener);
409        MainApplication.getLayerManager().removeLayerChangeListener(layerChangeListener);
410        MainApplication.getLayerManager().removeActiveLayerChangeListener(activeLayerChangeListener);
411        MainApplication.getLayerManager().removeLayerAvailabilityListener(layerAvailabilityListener);
412        MainApplication.undoRedo.removeCommandQueueListener(commandQueueListener);
413        ChangesetCache.getInstance().removeChangesetCacheListener(changesetCacheListener);
414        DataSet.removeSelectionListener(selectionChangedListener);
415        ExpertToggleAction.removeExpertModeChangeListener(expertModeChangeListener);
416        HistoryDataSet.getInstance().removeHistoryDataSetListener(historyDataSetListener);
417        SystemOfMeasurement.removeSoMChangeListener(soMChangeListener);
418        MapPaintStyles.removeMapPaintSylesUpdateListener(mapPaintSylesUpdateListener);
419        ProxyPreference.removeProxyPreferenceListener(proxyPreferenceListener);
420        MapFrame.removeMapModeChangeListener(mapModeChangeListener);
421        NavigatableComponent.removeZoomChangeListener(zoomChangeListener);
422        TaggingPresets.removeListener(taggingPresetListener);
423        OsmApi.removeOsmApiInitializationListener(osmApiInitializationListener);
424        Config.getPref().removePreferenceChangeListener(preferenceChangedListener);
425    }
426
427    @Override
428    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
429        post(new MapFrameInitializedEvent(this, oldFrame, newFrame));
430        if (newFrame != null) {
431            LayerListDialog dlg = newFrame.getToggleDialog(LayerListDialog.class);
432            if (dlg != null) {
433                dlg.getModel().addLayerListModelListener(layerListModelListener);
434            }
435            newFrame.keyDetector.addKeyListener(keyPressReleaseListener);
436            newFrame.keyDetector.addModifierExListener(modifierExListener);
437        }
438        if (oldFrame != null) {
439            LayerListDialog dlg = oldFrame.getToggleDialog(LayerListDialog.class);
440            if (dlg != null) {
441                dlg.getModel().removeLayerListModelListener(layerListModelListener);
442            }
443            oldFrame.keyDetector.removeKeyListener(keyPressReleaseListener);
444            oldFrame.keyDetector.removeModifierExListener(modifierExListener);
445        }
446    }
447}