/* * "Flush" * Copyright (c) Zoomorama 2008. * * Original developers: * Olivier Gambier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.dmp.library.bugfix { import flash.display.DisplayObject; import flash.display.Stage; import flash.events.Event; import flash.events.EventPhase; import flash.events.IEventDispatcher; import flash.events.MouseEvent; import flash.events.TimerEvent; import flash.system.Capabilities; import flash.utils.Timer; /** * Firefox 3.6 plugin mouse position snafu * Bugzilla537269 */ public class MouseEventSnafu { // GC private static var m_KeepAlive: MouseEventSnafu; // Ref to the stage, bomb timer, affected nodes, phase, counter private var m_Stage : Stage; private var m_Timer : Timer; private var m_SnafuNode : IEventDispatcher; private var m_SnafuTwin : IEventDispatcher; private var m_SnafuPhase : int; private var m_SnafuCounter: int; // Dbl click emulation private var m_ClickyNode : IEventDispatcher; private var m_ClickyBomb : Timer; public function MouseEventSnafu( sl: SingletonLock ) { } public static function get instance(): MouseEventSnafu { if (!m_KeepAlive) m_KeepAlive = new MouseEventSnafu( new SingletonLock() ); return m_KeepAlive; } public function enable( rootNode: DisplayObject, dblClickDelay: int = 0 ):void { if( !Capabilities.os.match(/^mac os/i) ) return; // Just an idiot safety if( m_Stage ) destroy( null ); // Remove cleanly when the app goes away rootNode.addEventListener( Event.REMOVED_FROM_STAGE, destroy, false, 0, true ); // Store a ref to the stage. m_Stage = rootNode.stage; // Possibly add support for dblclick if( dblClickDelay ) emulateDoubleClick( dblClickDelay ); // Hook it up registerEventSnafuHack(); } /** * Call this as well if you really need double-click handling */ private function emulateDoubleClick( delay: int ): void { trace( " + Double click emulation is enabled " ); m_Stage.addEventListener( MouseEvent.CLICK , dblClickSnafoo, false, 1000, true ); m_Stage.addEventListener( MouseEvent.CLICK , dblClickSnafoo, true , 1000, true ); m_ClickyBomb = new Timer( delay, 1 ); m_ClickyBomb.addEventListener( TimerEvent.TIMER_COMPLETE, resetClickSnafoo, false, 0, true ); } private function resetClickSnafoo( e: Event ): void { trace( " - Forgetting dbl click mechanic"); m_Stage.removeEventListener( MouseEvent.MOUSE_MOVE , resetClickSnafoo, false ); m_ClickyNode = null; m_ClickyBomb.stop(); } private function dblClickSnafoo( e: MouseEvent ): void { if( e.eventPhase == EventPhase.AT_TARGET || e.eventPhase == EventPhase.CAPTURING_PHASE ) // Did we already have a stored node? And this was the same node? Then it is a doubleClick if( m_ClickyNode && (m_ClickyNode == e.target) ){ trace( " ++++ Dispatching a dblClick"); // Ok, dispatch the event var dblClick: MouseEvent = new MouseEvent( MouseEvent.DOUBLE_CLICK, true, false, e.localX, e.localY, null, e.ctrlKey, e.altKey, e.shiftKey, e.buttonDown, 0 ); m_ClickyNode.dispatchEvent( dblClick ); // Reset the stuff resetClickSnafoo( e ); // Prevent the click from getting through e.stopImmediatePropagation(); }else if( e.target.doubleClickEnabled ){ trace( " + Got a reason to think we might get a dbl click" ); m_Stage.addEventListener( MouseEvent.MOUSE_MOVE , resetClickSnafoo, false, 900, true ); // First click. Store the node m_ClickyNode = e.target as DisplayObject; // Start the time bomb m_ClickyBomb.reset(); m_ClickyBomb.start(); } } /** * Called to get the hack in motion */ private function registerEventSnafuHack(): void { trace(" + Attaching snafu hack mechanics"); // Declare a timer to help clean-up the mess m_Timer = new Timer( 1, 2 ); m_Timer.addEventListener( TimerEvent.TIMER, timerListener, false, 1000, true ); // Declare the actual hook on click events m_Stage.addEventListener( MouseEvent.DOUBLE_CLICK, onClickHackListener, true, 1000, true ); m_Stage.addEventListener( MouseEvent.DOUBLE_CLICK, onClickHackListener, false, 1000, true ); m_Stage.addEventListener( MouseEvent.CLICK, onClickHackListener, true, 1000, true ); m_Stage.addEventListener( MouseEvent.CLICK, onClickHackListener, false, 1000, true ); m_SnafuPhase = 0; } /** * Called if we obtain the guarantee you don't need it */ private function destroy( e: Event ): void { trace(" - Removing snafu hack mechanics"); // Remove the clicks hooks m_Stage.removeEventListener( MouseEvent.CLICK, onClickHackListener, true ); m_Stage.removeEventListener( MouseEvent.CLICK, onClickHackListener, false ); // Get rid of the timer m_Timer.stop(); m_Timer.removeEventListener( TimerEvent.TIMER_COMPLETE, timerListener, false ); m_Timer = null; m_KeepAlive = null; } /** * This gets triggered when we catch a click. Declares stuff for interception. */ private function onClickHackListener( e: MouseEvent ): void { // Don't re-attach if we already declared on capture (and are bubbling) if( m_SnafuPhase ) return; trace(" + Snafu mechanics triggering up"); // Setup initial m_SnafuPhase = 1; m_SnafuCounter = 0; m_SnafuNode = e.target as IEventDispatcher; // We will have to eat that stuff m_Stage.addEventListener( MouseEvent.MOUSE_OVER , snafEventSwallow, true, 1000, true ); m_Stage.addEventListener( MouseEvent.MOUSE_OUT , snafEventSwallow, true, 1000, true ); m_Stage.addEventListener( MouseEvent.MOUSE_MOVE , snafEventSwallow, true, 1000, true ); m_Stage.addEventListener( MouseEvent.MOUSE_OVER , snafEventSwallow, false, 1000, true ); m_Stage.addEventListener( MouseEvent.MOUSE_OUT , snafEventSwallow, false, 1000, true ); m_Stage.addEventListener( MouseEvent.MOUSE_MOVE , snafEventSwallow, false, 1000, true ); m_Stage.addEventListener( Event.MOUSE_LEAVE , snafEventSwallow, false, 1000, true ); // And this might get triggered on the node itself (and probably on a related node as well) if( m_SnafuNode != m_Stage ){ m_SnafuNode.addEventListener( MouseEvent.ROLL_OUT , snafEventSwallow, false, 1000, true ); m_SnafuNode.addEventListener( MouseEvent.ROLL_OVER , snafEventSwallow, false, 1000, true ); } // Set the time bomb m_Timer.reset(); m_Timer.start(); } private function timerListener( e: TimerEvent ): void { trace( " / Snafu phase controller triggering on end of phase", m_SnafuPhase ); m_SnafuPhase++; if( m_SnafuPhase > 2){ // Didn't get *anything*? Then there is no bug. Get out of the way. if( !m_SnafuCounter ) destroy( e ); // Either way, end the current mechanic onEndSequence( e ); } } // Call this whenever the spurious flow is done getting through private function onEndSequence( e: Event ): void { trace(" - Snafu mechanics triggering down"); m_Stage.removeEventListener( MouseEvent.MOUSE_OVER , snafEventSwallow, true ); m_Stage.removeEventListener( MouseEvent.MOUSE_OUT , snafEventSwallow, true ); m_Stage.removeEventListener( MouseEvent.MOUSE_MOVE , snafEventSwallow, true ); m_Stage.removeEventListener( MouseEvent.MOUSE_OVER , snafEventSwallow, false ); m_Stage.removeEventListener( MouseEvent.MOUSE_OUT , snafEventSwallow, false ); m_Stage.removeEventListener( MouseEvent.MOUSE_MOVE , snafEventSwallow, false ); m_Stage.removeEventListener( Event.MOUSE_LEAVE , snafEventSwallow, false ); if( m_SnafuNode != m_Stage ){ m_SnafuNode.removeEventListener( MouseEvent.ROLL_OUT , snafEventSwallow, false ); m_SnafuNode.removeEventListener( MouseEvent.ROLL_OVER , snafEventSwallow, false ); } if( m_SnafuTwin ){ m_SnafuTwin.removeEventListener( MouseEvent.ROLL_OUT , snafEventSwallow, false ); m_SnafuTwin.removeEventListener( MouseEvent.ROLL_OVER , snafEventSwallow, false ); } m_SnafuNode = null; m_SnafuTwin = null m_SnafuPhase = 0; m_SnafuCounter = 0; } /** * This the function that actually swallows the stuff. * There is two phases: events that get dispatched due to the wrong mouse position being sent, and these due to the correct mouse repositioning. * Both phases are sandwiched between timerEvents triggers. * During the first phase, we need to get a grasp on the node that gets suddenly hovered if there is one, and prevent stuff to happen on him as well. * We *also* need to register long shot listeners if the position is out of the stage, that will get triggered out of the snafu time window */ private function snafEventSwallow( e: Event ): void { trace(" * Worker snafu phase ", m_SnafuPhase, " is swallowing", e); // Phase 1 handling if( m_SnafuPhase == 1 ){ // MouseLeave and not the stage? We need a defered swallower then if( (e.type == Event.MOUSE_LEAVE) && (m_SnafuNode != m_Stage )){ // When returning, (way) later, we will have to swallow back a mouseOver and a rollOver m_Stage.addEventListener( MouseEvent.MOUSE_OVER , deferedSwallower, true, 1000, true ); m_SnafuNode.addEventListener( MouseEvent.ROLL_OVER , deferedSwallower, false, 1000, true ); }else if( e.type == MouseEvent.MOUSE_MOVE ){ // So, we moved. Did we move far enough to hit another node? If yes, have it swallowed as well. if( e.target != m_SnafuNode ){ m_SnafuTwin = e.target as IEventDispatcher; m_SnafuTwin.addEventListener( MouseEvent.ROLL_OUT , snafEventSwallow, false, 1000, true ); m_SnafuTwin.addEventListener( MouseEvent.ROLL_OVER , snafEventSwallow, false, 1000, true ); } } } // Either way, swallow the event m_SnafuCounter++; e.stopImmediatePropagation(); } // This listener *may* get attached for long-shoot after-match snafus :-) private function deferedSwallower( e: Event ): void { trace(" ------ Defered swallower saved the day on event ", e); e.target.removeEventListener( e.type , deferedSwallower, true ); e.stopImmediatePropagation(); } } } /** * Compile-time singleton enforcement. */ class SingletonLock { }