| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| package { |
| |
| import flash.display.DisplayObject; |
| import flash.display.DisplayObjectContainer; |
| import flash.display.InteractiveObject; |
| import flash.display.Shape; |
| import flash.events.Event; |
| import flash.events.MouseEvent; |
| import flash.events.TimerEvent; |
| import flash.filesystem.File; |
| import flash.filesystem.FileMode; |
| import flash.filesystem.FileStream; |
| import flash.geom.Point; |
| import flash.system.Capabilities; |
| import flash.text.TextField; |
| import flash.utils.Timer; |
| |
| import mx.core.IVisualElement; |
| import mx.core.IVisualElementContainer; |
| import mx.core.mx_internal; |
| import mx.graphics.SolidColor; |
| import mx.graphics.SolidColorStroke; |
| import mx.utils.GetTimerUtil; |
| |
| import spark.components.Group; |
| import spark.components.Scroller; |
| import spark.core.SpriteVisualElement; |
| import spark.primitives.Ellipse; |
| import spark.primitives.Path; |
| |
| use namespace mx_internal; |
| |
| /** |
| * Exposes some helper methods that are useful for testing touch scrolling |
| * on a mobile device. These methods can be used in a standalone application |
| * to reproduce or demonstrate a bug and is also used in the SimulateMouseGesture |
| * Mustella test step. |
| * |
| * Example usage 1 - recording events: |
| * |
| * <s:actionContent> |
| * <s:Button label="enable" click="TouchScrollingUtil.enableMouseEventTracking(target)" /> |
| * <s:Button label="disable" click="TouchScrollingUtil.disableMouseEventTracking(target)" /> |
| * <s:Button label="write" click="TouchScrollingUtil.writeFileToDisk('/sdcard/Flex/QA/List/mouseEvents.txt', |
| * TouchScrollingUtil.getEventsAsMXMLString(TouchScrollingUtil.recordedMouseEvents));" /> |
| * </s:actionContent> |
| * |
| * Example usage 2 - playing back events: |
| * |
| * <s:actionContent> |
| * <s:Button label="play" click="TouchScrollingUtil.simulateTouchScrollFrameBased(target, eventsArray)" /> |
| * </s:actionContent> |
| * |
| */ |
| public class TouchScrollingUtil |
| { |
| |
| /** |
| * The name of the event that is fired when all mouse events have been fired |
| */ |
| public static const SIMULATION_COMPLETE:String = "simulationComplete"; |
| |
| /** |
| * Keeps track of all the mouse events that have fired while tracking was enabled |
| */ |
| public static var recordedMouseEvents:Array = new Array(); |
| |
| /** |
| * Controls whether to trace out extra debug information, for example tracing |
| * out information about every mouse event as it is dispatched. |
| */ |
| public static var enableVerboseTraceOuput:Boolean = true; |
| |
| /** |
| * Dispatches a series of MouseEvents that simulate how a user scrolls |
| * using touch scrolling on a mobile device. |
| * |
| * @param actualTarget - the target component to scroll |
| * |
| * @param events - An array of mouse events to dispatch. |
| * |
| * Use this for realistic simulation as you have complete control over the |
| * type, location, and time of each event. |
| * |
| * These events should be defined as an array of objects in this form: |
| * |
| * <fx:Object type="mouseDown" localX="150" localY="150" fakeTimeValue="0" /> |
| * <fx:Object type="mouseMove" localX="149" localY="149" fakeTimeValue="16" /> |
| * ... |
| * <fx:Object type="mouseUp" localX="100" localY="100" fakeTimeValue="343" /> |
| * |
| * If this property is not null then it takes precedence over any |
| * dragX/dragY values that might also be defined. |
| * |
| * TODO: Possible Enhancement: Allow each individual mouse event entry to specify its own |
| * waitEvent before continuing on to the next. This would allow for more control |
| * over when the events are fired, but may not be of any value. |
| * |
| * TODO: Possible Enhancement: Fully support sequences that go outside the bounds of the target. |
| * |
| * @param dragXFrom - the x coordinate of the target to start the drag motion |
| * @param dragYFrom - the y coordinate of the target to start the drag motion |
| * @param dragXTo - the x coordinate of the target to end the drag motion |
| * @param dragYTo - the y coordinate of the target to end the drag motion |
| * @param delay - the time between events when an events array isn't defined |
| * |
| */ |
| public static function simulateTouchScroll(actualTarget:Object, |
| events:Array, |
| recordedDPI:Number = NaN, |
| dragXFrom:Number = NaN, dragYFrom:Number = NaN, |
| dragXTo:Number = NaN, dragYTo:Number = NaN, |
| delay:Number = 17):void |
| { |
| // reset the index into the event list |
| var eventIndex:int = 0; |
| |
| // if a specific event sequence wasn't provided, then create one |
| if (events == null) |
| events = createEventsArray(dragXFrom, dragYFrom, dragXTo, dragYTo, delay); |
| |
| // shove the target into each element of the events array |
| for (var j:int = 0; j < events.length; j++) |
| events[j].target = actualTarget; |
| |
| // setup the timer based firing |
| var eventTimer:Timer = new Timer(1); |
| |
| // the method that loops through the events to fire |
| var tickerFunction:Function = function(e:TimerEvent):void |
| { |
| if (eventIndex >= events.length) |
| { |
| // all mouse events have been fired at this point |
| |
| // turn off fake time |
| GetTimerUtil.fakeTimeValue = undefined; |
| |
| // turn mouse event thinning back on |
| Scroller.dragEventThinning = true; |
| |
| // signal that this test step is ready for completion |
| actualTarget.dispatchEvent(new Event(SIMULATION_COMPLETE)); |
| |
| // stop the timer loop |
| eventTimer.stop(); |
| eventTimer.removeEventListener(TimerEvent.TIMER, tickerFunction); |
| return; |
| } |
| |
| // trace details on the event we are firing |
| traceLog("Dispatching MouseEvent:", |
| events[eventIndex].type, |
| events[eventIndex].localX, |
| events[eventIndex].localY, |
| events[eventIndex].fakeTimeValue); |
| |
| // update the fake time |
| GetTimerUtil.fakeTimeValue = events[eventIndex].fakeTimeValue; |
| |
| |
| // turn off mouse event thinning |
| // ---- |
| // Late in the 4.5 release the runtime changed their behavior on Android that |
| // fired too many mouseMove events making Flex sluggish on drag scrolls. We |
| // put logic in the SDK to thin out excess events and this logic is |
| // non-deterministic so we need to turn that logic off in Mustella. |
| // ---- |
| // TODO: This is a dependency on the SDK and a possible risk for automation |
| // not following the same code path as a user. |
| // We're stuck with it for now until the runtime allows us more control |
| // over the mouse event firing rate. |
| // ---- |
| // See http://bugs.adobe.com/jira/browse/SDK-29188 for a full explanation |
| // |
| Scroller.dragEventThinning = false; |
| |
| // scale the localX/localY co-ordinates if requested |
| var adjustedLocalX:Number = scaleByDPIRatio(events[eventIndex].localX, recordedDPI); |
| var adjustedLocalY:Number = scaleByDPIRatio(events[eventIndex].localY, recordedDPI); |
| |
| // fire the next event |
| dispatchMouseEvent(events[eventIndex].target, |
| events[eventIndex].type, |
| adjustedLocalX, |
| adjustedLocalY); |
| |
| // update the timer delay to be the difference between time values of the next and current events |
| if (eventIndex + 1 < events.length) |
| { |
| var nextTimeValue:int = events[eventIndex + 1].fakeTimeValue; |
| var currTimeValue:int = events[eventIndex].fakeTimeValue; |
| |
| eventTimer.delay = (nextTimeValue - currTimeValue); |
| traceLog('timer is now', eventTimer.delay); |
| } |
| |
| // move on to the next event |
| eventIndex++; |
| }; |
| |
| // start firing the mouse events |
| eventTimer.addEventListener(TimerEvent.TIMER, tickerFunction); |
| eventTimer.start(); |
| |
| } |
| |
| /** |
| * Similar to simulateTouchScroll(), but used in Mustella so it's not based |
| * on a timer but rather fires off a couple mouse events per enterFrame. |
| * |
| * @see simulateTouchScroll() |
| */ |
| public static function simulateTouchScrollFrameBased(actualTarget:Object, |
| events:Array, |
| recordedDPI:Number = NaN, |
| dragXFrom:Number = NaN, dragYFrom:Number = NaN, |
| dragXTo:Number = NaN, dragYTo:Number = NaN, |
| delay:Number = 17):void |
| { |
| // reset the index into the event list |
| var eventIndex:int = 0; |
| |
| // if a specific event sequence wasn't provided, then create one |
| if (events == null) |
| events = createEventsArray(dragXFrom, dragYFrom, dragXTo, dragYTo, delay); |
| |
| // shove the target into each element of the events array |
| for (var j:int = 0; j < events.length; j++) |
| events[j].target = actualTarget; |
| |
| // the method that loops through the events to fire |
| var tickerFunction:Function = function(e:Event):void |
| { |
| // fire a few mouse events per enterFrame |
| var numEventsPerEnterFrame:int = 3; |
| for (var i:int = 0; i < numEventsPerEnterFrame; i++) |
| { |
| if (eventIndex >= events.length) |
| { |
| // all mouse events have been fired at this point |
| |
| // turn off fake time |
| GetTimerUtil.fakeTimeValue = undefined; |
| |
| // turn mouse event thinning back on |
| Scroller.dragEventThinning = true; |
| |
| // signal that this test step is ready for completion |
| actualTarget.dispatchEvent(new Event(SIMULATION_COMPLETE)); |
| |
| // remove the enterFrame listener |
| actualTarget.removeEventListener("enterFrame", tickerFunction) |
| return; |
| } |
| |
| // trace details on the event we are firing |
| traceLog("Dispatching MouseEvent:", |
| events[eventIndex].type, |
| events[eventIndex].localX, |
| events[eventIndex].localY, |
| events[eventIndex].fakeTimeValue); |
| |
| // update the fake time |
| GetTimerUtil.fakeTimeValue = events[eventIndex].fakeTimeValue; |
| |
| // turn off mouse event thinning |
| // ---- |
| // Late in the 4.5 release the runtime changed their behavior on Android that |
| // fired too many mouseMove events making Flex sluggish on drag scrolls. We |
| // put logic in the SDK to thin out excess events and this logic is |
| // non-deterministic so we need to turn that logic off in Mustella. |
| // ---- |
| // TODO: This is a dependency on the SDK and a possible risk for automation |
| // not following the same code path as a user. |
| // We're stuck with it for now until the runtime allows us more control |
| // over the mouse event firing rate. |
| // ---- |
| // See http://bugs.adobe.com/jira/browse/SDK-29188 for a full explanation |
| // |
| Scroller.dragEventThinning = false; |
| |
| // scale the localX/localY co-ordinates if requested |
| var adjustedLocalX:Number = scaleByDPIRatio(events[eventIndex].localX, recordedDPI); |
| var adjustedLocalY:Number = scaleByDPIRatio(events[eventIndex].localY, recordedDPI); |
| |
| trace(adjustedLocalX, adjustedLocalY); |
| |
| // fire the next event |
| dispatchMouseEvent(events[eventIndex].target, |
| events[eventIndex].type, |
| adjustedLocalX, |
| adjustedLocalY); |
| |
| // move on to the next event |
| eventIndex++; |
| } |
| } |
| |
| // start firing the mouse events |
| actualTarget.addEventListener("enterFrame", tickerFunction); |
| |
| } |
| |
| /** |
| * This takes an x/y value and scales it by the current device's exact DPI over the recorded |
| * device's exact dpi. |
| * |
| * This allows support for recording a mouseEvent sequence at one DPI and have it scale automatically |
| * on different DPIs to maintain the same physical distance scrolled (in inches). |
| */ |
| private static function scaleByDPIRatio(value:Number, recordedDPI:Number):Number |
| { |
| if (!isNaN(recordedDPI)) |
| return Math.round(value * (Capabilities.screenDPI / recordedDPI)); |
| else |
| return value; |
| } |
| |
| /** |
| * TODO: This is an untyped version of MouseEventEntry in Mustella. |
| */ |
| private static function createMouseEventEntry(type:String, localX:Number, localY:Number, fakeTimeValue:Number):Object |
| { |
| var mouseEventEntry:Object = new Object(); |
| mouseEventEntry.type = type; |
| mouseEventEntry.localX = localX; |
| mouseEventEntry.localY = localY; |
| mouseEventEntry.fakeTimeValue = fakeTimeValue; |
| return mouseEventEntry; |
| } |
| |
| /** |
| * Creates a sequence of mouse events to perform the requested drag scroll |
| */ |
| private static function createEventsArray(dragXFrom:Number, dragYFrom:Number, dragXTo:Number, dragYTo:Number, delay:Number):Array |
| { |
| var arr:Array = new Array(); |
| |
| var deltaX:Number = isNaN(dragXFrom - dragXTo) ? 0 : dragXFrom - dragXTo; |
| var deltaY:Number = isNaN(dragYFrom - dragYTo) ? 0 : dragYFrom - dragYTo; |
| var numSteps:Number = 6; |
| |
| var chunkX:Number = deltaX / numSteps; |
| var chunkY:Number = deltaY / numSteps; |
| |
| // first need a move, rollOver, over, mouseDown at the start position |
| arr.push(createMouseEventEntry(MouseEvent.MOUSE_MOVE, dragXFrom, dragYFrom, arr.length * delay)); |
| arr.push(createMouseEventEntry(MouseEvent.ROLL_OVER, dragXFrom, dragYFrom, arr.length * delay)); |
| arr.push(createMouseEventEntry(MouseEvent.MOUSE_OVER, dragXFrom, dragYFrom, arr.length * delay)); |
| arr.push(createMouseEventEntry(MouseEvent.MOUSE_DOWN, dragXFrom, dragYFrom, arr.length * delay)); |
| |
| for (var i:int = 0; i < numSteps; i++) |
| { |
| // then a couple mouseMoves along the way |
| arr.push(createMouseEventEntry(MouseEvent.MOUSE_MOVE, |
| Math.round(dragXFrom - (chunkX * i)), |
| Math.round(dragYFrom - (chunkY * i)), |
| arr.length * delay)); |
| } |
| |
| // then a few mouseMoves near/at the end to take away any potential |
| // velocity so we guarantee a drag instead of a throw |
| arr.push(createMouseEventEntry(MouseEvent.MOUSE_MOVE, dragXTo, dragYTo, arr.length * delay)); |
| arr.push(createMouseEventEntry(MouseEvent.MOUSE_MOVE, dragXTo, dragYTo, arr.length * delay)); |
| arr.push(createMouseEventEntry(MouseEvent.MOUSE_MOVE, dragXTo, dragYTo, arr.length * delay)); |
| arr.push(createMouseEventEntry(MouseEvent.MOUSE_MOVE, dragXTo, dragYTo, arr.length * delay)); |
| |
| // then a mouseMove at the destination |
| arr.push(createMouseEventEntry(MouseEvent.MOUSE_MOVE, dragXTo, dragYTo, arr.length * delay)); |
| |
| // finally a mouseUp at the destination |
| arr.push(createMouseEventEntry(MouseEvent.MOUSE_UP, dragXTo, dragYTo, arr.length * delay)); |
| |
| return arr; |
| } |
| |
| /** |
| * Fires a mouseEvent of the given type and location on the target. |
| * |
| * This method inspired by DispatchMouseClickEvent and tweaked to be |
| * a static method available outside of Mustella too. This means a few |
| * more optional parameters. |
| */ |
| public static function dispatchMouseEvent(actualTarget:Object, |
| type:String, |
| localX:Number = NaN, |
| localY:Number = NaN, |
| stageX:Number = NaN, |
| stageY:Number = NaN, |
| ctrlKey:Boolean = false, |
| shiftKey:Boolean = false, |
| delta:Number = 0, |
| relatedObject:Object = null):void |
| { |
| var event:MouseEvent = new MouseEvent(type, true); // all mouse events bubble |
| event.ctrlKey = ctrlKey; |
| event.shiftKey = shiftKey; |
| event.buttonDown = type == "mouseDown"; |
| event.delta = delta; |
| if (relatedObject && relatedObject.length > 0) |
| { |
| // SEJS: Removing Mustella specific context stuff for now so this static method |
| // is available outside of Mustella too. TODO: Look into refactoring to allow this support |
| // |
| //event.relatedObject = InteractiveObject(context.stringToObject(relatedObject)); |
| } |
| |
| var stagePt:Point; |
| if (!isNaN(localX) && !isNaN(localY)) |
| { |
| stagePt = actualTarget.localToGlobal(new Point(localX, localY)); |
| } |
| else if (!isNaN(stageX) && !isNaN(stageY)) |
| { |
| stagePt = new Point(stageX, stageY); |
| } |
| else |
| { |
| stagePt = actualTarget.localToGlobal(new Point(0, 0)); |
| } |
| // SEJS: Removing Mustella specific context stuff for now so this static method |
| // is available outside of Mustella too. TODO: Look into refactoring to allow this support |
| // |
| // This class was inspired by DispatchMouseClickEvent, but in a mobile sense we don't care |
| // about other sandboxes for now so we can hopefully remove this mustella context sensitive code |
| // |
| //root[mouseX] = stagePt.x; |
| //root[mouseY] = stagePt.y; |
| //UnitTester.setMouseXY(stagePt); |
| // |
| //if (root["topLevelSystemManager"] != root) |
| //{ |
| // root["topLevelSystemManager"][mouseX] = stagePt.x; |
| // root["topLevelSystemManager"][mouseY] = stagePt.y; |
| //} |
| |
| if (actualTarget is DisplayObjectContainer) |
| { |
| var targets:Array = actualTarget.stage.getObjectsUnderPoint(stagePt); |
| // SEJS: Removing Mustella specific context stuff for now so this static method |
| // is available outside of Mustella too. TODO: Look into refactoring to allow this support |
| // |
| // This class was inspired by DispatchMouseClickEvent, but in a mobile sense we don't care |
| // about other sandboxes for now so we can hopefully remove this mustella context sensitive code |
| // |
| //var arr:Array = UnitTester.getObjectsUnderPoint(DisplayObject(actualTarget), stagePt); |
| //targets = targets.concat(arr); |
| |
| for (var i:int = targets.length - 1; i >= 0; i--) |
| { |
| if (targets[i] is InteractiveObject) |
| { |
| if (targets[i] is TextField && !targets[i].selectable) |
| { |
| actualTarget = targets[i].parent; |
| break; |
| } |
| |
| if (InteractiveObject(targets[i]).mouseEnabled) |
| { |
| actualTarget = targets[i]; |
| break; |
| } |
| } |
| else if (targets[i] is Shape) |
| { |
| // Looks like getObjectsUnderPoint() returns a Shape for FXG elements. |
| // Here we assume that if we see a Shape like this then we check its parent |
| // DisplayObjectContainer to see if it has mouseEnabled true |
| // FIXME: Talk to Brian and Alex to see if this is the right fix and get it into DispatchMouseEvent/DispatchMouseClickEvent and here. is this a player bug? |
| var shapeParent:DisplayObjectContainer = (targets[i] as Shape).parent; |
| if (shapeParent.mouseEnabled) |
| { |
| actualTarget = targets[i]; |
| break; |
| } |
| } |
| else |
| { |
| try |
| { |
| actualTarget = targets[i].parent; |
| while (actualTarget) |
| { |
| if (actualTarget is InteractiveObject) |
| { |
| if (InteractiveObject(actualTarget).mouseEnabled) |
| { |
| break; |
| } |
| } |
| actualTarget = actualTarget.parent; |
| } |
| if (actualTarget) |
| break; |
| } |
| catch (e:Error) |
| { |
| trace('error'); |
| if (actualTarget) |
| break; |
| } |
| } |
| } |
| } |
| |
| // Examine parent chain for "mouseChildren" set to false: |
| try |
| { |
| var parent:DisplayObjectContainer = actualTarget.parent; |
| while (parent) |
| { |
| if (!parent.mouseChildren){ |
| trace('mouseChildren step: set actualTarget to parent:', parent); |
| actualTarget = parent; |
| } |
| parent = parent.parent; |
| } |
| } |
| catch (e1:Error) |
| { |
| } |
| |
| var localPt:Point = actualTarget.globalToLocal(stagePt); |
| event.localX = localPt.x; |
| event.localY = localPt.y; |
| |
| if (actualTarget is TextField) |
| { |
| if (type == "mouseDown") |
| { |
| var charIndex:int = actualTarget.getCharIndexAtPoint(event.localX, event.localY); |
| actualTarget.setSelection(charIndex + 1, charIndex + 1); |
| } |
| } |
| |
| try |
| { |
| actualTarget.dispatchEvent(event); |
| } |
| catch (e2:Error) |
| { |
| trace("Error: Exception thrown in TouchScrollingUtil.dispatchMouseEvent()"); |
| // SEJS: Removing Mustella specific context stuff for now so this static method |
| // is available outside of Mustella too. TODO: Look into refactoring to allow this support |
| // |
| //TestOutput.logResult("Exception thrown in DispatchMouseClickEvent."); |
| //testResult.doFail (e2.getStackTrace()); |
| return; |
| } |
| } |
| |
| /** placeholder for the function closure so the listener can be removed later */ |
| private static var traceFunctionHolder:Function = null; |
| |
| /** placeholder for the function closure so the listener can be removed later */ |
| private static var enterFunctionHolder:Function = null; |
| |
| /** |
| * Traces all of the relevant mouse events that happen on the target. You can access an array of |
| * these events via the recordedMoueEvents static property. |
| * |
| * @param target - the component to track mouse events on |
| * @param showEnterFrameEvents - set this to true to show when enterFrames are being fired within the sequence |
| */ |
| public static function enableMouseEventTracking(target:DisplayObject, showEnterFrameEvents:Boolean = false):void |
| { |
| // closure for tracing mouse events |
| var traceMouseEvents:Function = function (event:MouseEvent):void |
| { |
| // convert the stage value to be relative to the main target |
| var stagePt:Point = event.target.localToGlobal(new Point(event.localX, event.localY)); |
| var targetPt:Point = target.globalToLocal(stagePt); |
| |
| var newEvent:Object = new Object(); |
| newEvent.target = target; |
| newEvent.type = event.type; |
| newEvent.localX = targetPt.x; |
| newEvent.localY = targetPt.y; |
| newEvent.fakeTimeValue = flash.utils.getTimer(); |
| |
| // track this event |
| recordedMouseEvents.push(newEvent); |
| |
| // trace the event |
| trace(newEvent.type, newEvent.localX, newEvent.localY, newEvent.fakeTimeValue); |
| } |
| |
| // closure for tracing enterFrame events |
| var traceEnterFrameEvents:Function = function (event:Event):void |
| { |
| if (showEnterFrameEvents) |
| trace(event.type); |
| } |
| |
| // throw the closures into the static placeholders so they can be removed later |
| traceFunctionHolder = traceMouseEvents; |
| enterFunctionHolder = traceEnterFrameEvents; |
| |
| // listen to enterFrame and all mouse events (use a higher priority than Scroller) |
| // TODO: Containers seem to need a different useCapture value than elements |
| if (target is IVisualElementContainer) |
| { |
| target.addEventListener(MouseEvent.CLICK, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.CONTEXT_MENU, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.DOUBLE_CLICK, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.MIDDLE_CLICK, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.MIDDLE_MOUSE_DOWN, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.MIDDLE_MOUSE_UP, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.MOUSE_DOWN, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.MOUSE_MOVE, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.MOUSE_OUT, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.MOUSE_OVER, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.MOUSE_UP, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.MOUSE_WHEEL, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.RIGHT_CLICK, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.RIGHT_MOUSE_UP, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.ROLL_OUT, traceFunctionHolder, true, 1); |
| target.addEventListener(MouseEvent.ROLL_OVER, traceFunctionHolder, true, 1); |
| } |
| else |
| { |
| target.addEventListener(MouseEvent.CLICK, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.CONTEXT_MENU, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.DOUBLE_CLICK, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.MIDDLE_CLICK, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.MIDDLE_MOUSE_DOWN, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.MIDDLE_MOUSE_UP, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.MOUSE_DOWN, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.MOUSE_MOVE, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.MOUSE_OUT, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.MOUSE_OVER, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.MOUSE_UP, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.MOUSE_WHEEL, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.RIGHT_CLICK, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.RIGHT_MOUSE_UP, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.ROLL_OUT, traceFunctionHolder, false, 1); |
| target.addEventListener(MouseEvent.ROLL_OVER, traceFunctionHolder, false, 1); |
| } |
| |
| target.addEventListener(Event.ENTER_FRAME, enterFunctionHolder); |
| |
| } |
| |
| /** |
| * Removes the mouse and enterFrame event listeners from the target. |
| */ |
| public static function disableMouseEventTracking(target:DisplayObject):void |
| { |
| if (target is IVisualElementContainer) |
| { |
| target.removeEventListener(MouseEvent.CLICK, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.CONTEXT_MENU, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.DOUBLE_CLICK, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.MIDDLE_CLICK, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.MIDDLE_MOUSE_DOWN, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.MIDDLE_MOUSE_UP, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.MOUSE_DOWN, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.MOUSE_MOVE, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.MOUSE_OUT, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.MOUSE_OVER, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.MOUSE_UP, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.MOUSE_WHEEL, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.RIGHT_CLICK, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.RIGHT_MOUSE_DOWN, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.RIGHT_MOUSE_UP, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.ROLL_OUT, traceFunctionHolder, true); |
| target.removeEventListener(MouseEvent.ROLL_OVER, traceFunctionHolder, true); |
| } |
| else |
| { |
| target.removeEventListener(MouseEvent.CLICK, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.CONTEXT_MENU, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.DOUBLE_CLICK, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.MIDDLE_CLICK, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.MIDDLE_MOUSE_DOWN, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.MIDDLE_MOUSE_UP, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.MOUSE_DOWN, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.MOUSE_MOVE, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.MOUSE_OUT, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.MOUSE_OVER, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.MOUSE_UP, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.MOUSE_WHEEL, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.RIGHT_CLICK, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.RIGHT_MOUSE_DOWN, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.RIGHT_MOUSE_UP, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.ROLL_OUT, traceFunctionHolder, false); |
| target.removeEventListener(MouseEvent.ROLL_OVER, traceFunctionHolder, false); |
| } |
| |
| target.removeEventListener(Event.ENTER_FRAME, enterFunctionHolder); |
| } |
| |
| /** |
| * Returns a String representation of the given array of events as MouseEventEntry tags |
| * |
| * Example: <MouseEventEntry type="mouseMove" localX="8" localY="0" fakeTimeValue="21515" /> |
| */ |
| public static function getEventsAsMXMLString(events:Array):String |
| { |
| var output:String = ""; |
| for each (var e:Object in events) |
| output += '<MouseEventEntry type="' + e.type + '" localX="' + e.localX + '" localY="' + e.localY + '" fakeTimeValue="' + e.fakeTimeValue + '" />\n'; |
| return output; |
| } |
| |
| /** |
| * Returns a visual representation of the given sequence of mouse events. |
| */ |
| public static function getEventsAsPath(events:Array, recordedDPI:Number = NaN):Group |
| { |
| // create a new group to hold the path and circles |
| var g:Group = new Group(); |
| g.mouseEnabled = false; |
| |
| var p:Path = new Path(); |
| p.stroke = new SolidColorStroke(0xFFFF00, 3); |
| p.data = ""; |
| g.addElement(p); |
| |
| // add a point to the path for each event |
| for (var i:int = 0; i < events.length; i++) |
| { |
| var e:Object = events[i]; |
| |
| // scale the co-ordinates if DPI scaling is requested |
| var adjustedLocalX:Number = scaleByDPIRatio(e.localX, recordedDPI); |
| var adjustedLocalY:Number = scaleByDPIRatio(e.localY, recordedDPI); |
| |
| if (i == 0) |
| p.data += "M " + adjustedLocalX + " " + adjustedLocalY + " "; |
| else |
| p.data += "L " + adjustedLocalX + " " + adjustedLocalY + " "; |
| |
| if (e.type == MouseEvent.MOUSE_DOWN) |
| { |
| // draw a mouse down circle (green) |
| var downCircle:Ellipse = createCircle(10, 0x00FF00); |
| downCircle.x = adjustedLocalX - downCircle.width / 2; |
| downCircle.y = adjustedLocalY - downCircle.height / 2; |
| |
| g.addElement(downCircle); |
| } |
| |
| if (e.type == MouseEvent.MOUSE_UP) |
| { |
| // draw a mouse up circle (red) |
| var upCircle:Ellipse = createCircle(10, 0xFF0000); |
| upCircle.x = adjustedLocalX - upCircle.width / 2; |
| upCircle.y = adjustedLocalY - upCircle.height / 2; |
| |
| g.addElement(upCircle); |
| } |
| } |
| |
| return g; |
| } |
| |
| /** |
| * Returns a circle of the given radius and fill color |
| */ |
| private static function createCircle(radius:Number, fillColor:uint):Ellipse |
| { |
| var c:Ellipse = new Ellipse(); |
| c.width = radius; |
| c.height = radius; |
| var solidColor:SolidColor = new SolidColor(fillColor); |
| c.fill = solidColor; |
| return c; |
| } |
| |
| /** |
| * Writes the given content string to the disk at location fileName. |
| * |
| * Useful for writing sequences of mouse events to disk on the phone and |
| * transferring that off of the device. |
| * |
| * Sample usage: |
| * |
| * writeFileToDisk('/sdcard/Flex/QA/List/mouseEvents.txt', |
| * TouchScrollingUtil.getEventsAsMXMLString(TouchScrollingUtil.recordedMouseEvents)); |
| */ |
| public static function writeFileToDisk(fileName:String, content:String):void |
| { |
| var file:File = new File (fileName); |
| var fileStream:FileStream = new FileStream(); |
| |
| fileStream.open(file, FileMode.WRITE); |
| fileStream.writeUTF(content); |
| fileStream.close(); |
| } |
| |
| /** |
| * Basically just calls trace(), but only does so if the verbose flag |
| * is set to true. This helps us avoid inflating the log with hundreds |
| * of extra trace statements while running in Mustella. |
| */ |
| private static function traceLog(... rest):void |
| { |
| // don't trace this message if debugging isn't enabled |
| if (!enableVerboseTraceOuput) |
| return; |
| |
| // format the output the same way trace() does with a space |
| // in between each piece of the rest array |
| |
| var output:String = ""; |
| var i:int = 0; |
| const restLength:int = rest.length; |
| |
| for (i = 0; i < restLength; i++) |
| { |
| output += rest[i]; |
| if (i < restLength - 1) |
| output += " "; |
| } |
| |
| trace(output); |
| } |
| |
| } |
| |
| } |
| |