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}