| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 flashx.textLayout.events |
| { |
| import flash.display.DisplayObject; |
| import flash.display.DisplayObjectContainer; |
| import flash.display.Sprite; |
| import flash.events.Event; |
| import flash.events.EventDispatcher; |
| import flash.events.KeyboardEvent; |
| import flash.events.MouseEvent; |
| import flash.geom.Matrix; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| import flash.text.engine.TextBlock; |
| import flash.text.engine.TextLine; |
| import flash.text.engine.TextLineValidity; |
| import flash.ui.Mouse; |
| import flash.ui.MouseCursor; |
| import flash.utils.Dictionary; |
| |
| import flashx.textLayout.compose.IFlowComposer; |
| import flashx.textLayout.compose.TextFlowLine; |
| import flashx.textLayout.container.ColumnState; |
| import flashx.textLayout.container.ContainerController; |
| import flashx.textLayout.container.ScrollPolicy; |
| import flashx.textLayout.debug.assert; |
| import flashx.textLayout.elements.*; |
| import flashx.textLayout.formats.BlockProgression; |
| import flashx.textLayout.formats.Direction; |
| import flashx.textLayout.tlf_internal; |
| import flashx.textLayout.utils.GeometryUtil; |
| import flashx.textLayout.utils.HitTestArea; |
| |
| use namespace tlf_internal; |
| |
| [ExcludeClass] |
| /** |
| * The ElementMouseEventManager class provides mouse element processing for visible |
| * FlowElements. The caller calls updateHitTests(), which traverses the FlowElement |
| * tree in the given character range, and collects hit test rectangles for FlowElements |
| * with active event mirrors, and LinkElements. The latter class exposes a number of |
| * mouse event handlers that this class calls directly. If no character range is |
| * supplied, the class makes an educated guess about the visible character range by |
| * inspecting the TextLine instances connected to the container widget as children. |
| * |
| * In edit mode, moving the mouse over the element and clicking it should not activate |
| * it, but rather allow for editing. Passing in the value true as an argument |
| * to updateHitTests() requires the Ctrl key to be held down in order to pass mouse |
| * events on the FlowElements. If the user presses or releases the Ctrl key while the |
| * mouse is over the container widget (and the pressing of the Ctrl key is required |
| * for activation, the class simulates mouseOut and mouseOver events to inform the |
| * FlowElement underneath about the change. The class dispatches only FlowElementMouseEvents. |
| * |
| * This class emits click events if the main mouse button is pressed and released over the |
| * same FlowElement. |
| * |
| * The owner may choose to forward several mouse and keyboard events to this class so it |
| * does not need to listen to these events at the container itself. The constructor takes |
| * an array of event names that this class needs to listen to itself. The events that this |
| * class needs to listen to are MouseEvent.MOUSE_OVER, MouseEvent.MOUSE_OUT, MouseEvent.MOUSE_DOWN, |
| * MouseEvent.MOUSE_UP, MouseEvent.MOUSE_MOVE, KeyboardEvent.KEY_DOWN, and KeyboardEvent.KEY_UP. |
| * Note that MouseEvent.CLICK is not needed. |
| */ |
| public class FlowElementMouseEventManager |
| { |
| // The container that emits the mouse events, and that contains the TextLines as children. |
| private var _container:DisplayObjectContainer; |
| // the hit test rectangles if there is anything to hit test. |
| private var _hitTests:HitTestArea = null; |
| // The current element that has been identified as being hit. |
| private var _currentElement:FlowElement = null; |
| // The element that has received the last museDown event. |
| private var _mouseDownElement:FlowElement = null; |
| // If true, the Ctrl key is needed to send mouse events to a FlowElement. |
| private var _needsCtrlKey:Boolean = false; |
| // Reflects the state of the Ctrl key. |
| private var _ctrlKeyState:Boolean = false; |
| // The last mouse event serves as a template for fake mouse events (x, y, buttonDown) |
| private var _lastMouseEvent:MouseEvent = null; |
| /** |
| * @private |
| * Applies only if Ctrl key is needed: if the mouse entered the FlowElement |
| * with the button wodn and the Ctrl key down, ignore the state of the Ctrl |
| * key and treat it as not set. You do not want any reaction if the FlowElement |
| * was e.g. entered during a mark operation. |
| */ |
| private var _blockInteraction:Boolean = false; |
| // Owner handles the event and calls dispatchEvent() |
| private const OWNER_HANDLES_EVENT:int = 0; |
| // This instance handles the event, but no event listener is registered. |
| private const THIS_HANDLES_EVENT:int = 1; |
| // This instance handles the event, and an an event listener is registered with the container/stage. |
| private const THIS_LISTENS_FOR_EVENTS:int = 2; |
| /** @private |
| * This object contains the event states that this class must |
| * register on its own behalf. The property name is the event name, |
| * and the property value is one of the above three constants. |
| */ |
| private var _eventListeners:Object; |
| /** @private |
| * This object contains objects that hold a bounding rectangle in its "rect", |
| * and a FlowElement in its "owner" property. The key is the toString() value |
| * of the rectangle. Every time updateHitTests() is called, the object is |
| * checked for rectangles that still intersect with a given clip area, and |
| * these rectangles are removed from the object before the new rectangles |
| * are added. After that, the new hit test structure is built using this |
| * object. The code that adds rectangles uses the key to store rectangles |
| * so updated rectangles are neatly overwritten. |
| */ |
| private var _hitRects:Object = null; |
| /** |
| * The constructor needs the container that contains the TextLines to be checked |
| * for visible, clicked elements. The container is also used as the event dispatcher |
| * for mouse events. |
| * |
| * @param container The container holding the TextLines and emitting mouse events |
| * @param eventNames An array of event names that the owner supplies itself. |
| */ |
| public function FlowElementMouseEventManager(container:DisplayObjectContainer, eventNames:Array) |
| { |
| _container = container; |
| _eventListeners = {}; |
| _eventListeners[MouseEvent.MOUSE_OVER] = |
| _eventListeners[MouseEvent.MOUSE_OUT] = |
| _eventListeners[MouseEvent.MOUSE_DOWN] = |
| _eventListeners[MouseEvent.MOUSE_UP] = |
| _eventListeners[MouseEvent.MOUSE_MOVE] = |
| _eventListeners[KeyboardEvent.KEY_DOWN] = |
| _eventListeners[KeyboardEvent.KEY_UP] = THIS_HANDLES_EVENT; |
| // change the state for any event that the owner will supply |
| for each (var name:String in eventNames) |
| _eventListeners[name] = OWNER_HANDLES_EVENT; |
| } |
| |
| /** |
| * Convert local mouse event coordinates, which are relative to the container |
| * or one of its children (the event's target is the current element) to |
| * container coordinates. |
| * |
| * TODO: This is temporary code. |
| * |
| * @param evt The mouse event containing the point to be converted. |
| * @return A new Point instance containing the converted coordinates. |
| */ |
| public function mouseToContainer(evt:MouseEvent):Point |
| { |
| // we have to use localX and localY because the UnitTests generate |
| // fake mouse events that are relative to a TextLine |
| var obj:DisplayObject = evt.target as DisplayObject; |
| CONFIG::debug { assert(obj != null, "Event target is not a DisplayObject"); } |
| var containerPoint:Point = new Point(evt.localX, evt.localY); |
| while (obj != _container) |
| { |
| var m:Matrix = obj.transform.matrix; |
| containerPoint.offset(m.tx, m.ty); |
| // TextLines sometimes do not have a parent for some reason |
| obj = obj.parent; |
| if (!obj) |
| break; |
| } |
| return containerPoint; |
| } |
| |
| /** |
| * Retrieve the status of the flag that controls whether the Ctrl key is needed |
| * to activate event generation. |
| */ |
| public function get needsCtrlKey():Boolean |
| { |
| return _needsCtrlKey; |
| } |
| |
| /** |
| * Set the status of the flag that controls whether the Ctrl key is needed |
| * to activate event generation. |
| */ |
| public function set needsCtrlKey(k:Boolean):void |
| { |
| _needsCtrlKey = k; |
| } |
| |
| /** |
| * Create an array of all FlowElements that are currently visible and that have an active |
| * event mirror, plus all LinkElements by default, and update the hit test area for these |
| * FlowElements. Clip the elements against the given clipping rectangle. All coordinates |
| * are assumed to be container coordinates. |
| * |
| * <p>If the start index is -1 and or the end index is -1, the method attempts to make an educated |
| * guess about the visible part of the text by inspecting the visible TextLine instances, and |
| * using their textBlockBeginIndex values as character offsets into the TextFlow tree. Note that |
| * this method may be slow if the container contains an entire tree of DisplayObjects, because |
| * the tree must be scanned recursively. It is always better to supply the start and end positions.</p> |
| * |
| * @param clipRect The clipping rectangle, in container coordinates. |
| * @param textFlow The TextFlow instance containing the elements to be tracked |
| * @param startPos The character start position. If -1, the method attempts to |
| * determine the first visible character. |
| * @param endPos The character end position. If -1, the method attempts to |
| * determine the last visible character. |
| * @param needsCtrlKey If true, the event handler does not emit events unless |
| * the Ctrl key is down. If the text is editable, simple clicks |
| * and moves should remain in the container rather than be |
| * forwarded to the element. |
| */ |
| public function updateHitTests(xoffset:Number, clipRect:Rectangle, |
| textFlow:TextFlow, |
| startPos:int, endPos:int, |
| container:ContainerController, |
| needsCtrlKey:Boolean=false):void |
| { |
| _needsCtrlKey = needsCtrlKey; |
| |
| var rect:Rectangle; |
| var obj:Object; |
| |
| var elements:Array = []; |
| if (textFlow.interactiveObjectCount != 0 && startPos != endPos) // check for empty container |
| { |
| //New algorithm here to improve performance when there are link elements |
| var uniqueDictionary:Dictionary = container.interactiveObjects; |
| var o:Object ; |
| var f:FlowElement |
| for each (o in uniqueDictionary) |
| { |
| f = o as FlowElement ; |
| if (f && f.getAbsoluteStart() < endPos && f.getAbsoluteStart() + f.textLength >= startPos) |
| elements.push(o) ; |
| } |
| //ensure there is no bug when you paste many words, which causes the link across containers |
| var interactiveObjects_LastTime:Array = container.oldInteractiveObjects; |
| for each (o in interactiveObjects_LastTime) |
| { |
| f = o as FlowElement ; |
| if (f && f.getAbsoluteStart() < endPos && f.getAbsoluteStart() + f.textLength >= startPos) |
| { |
| elements.push(o) ; |
| uniqueDictionary[o] = o;//push back the interactive object, make sure the total number is correct |
| } |
| |
| } |
| |
| CONFIG::debug |
| { |
| if (elements.length) |
| { |
| for each (var elem:FlowElement in elements) |
| { |
| // trace(startPos,endPos,elem.defaultTypeName,elem.getAbsoluteStart(),elem.textLength); |
| assert(elem.getAbsoluteStart() < endPos,"updateHitTests bad absoluteStart"); |
| assert(elem.getAbsoluteStart()+elem.textLength >= startPos,"updateHitTests bad absoluteEnd"); |
| } |
| } |
| } |
| } |
| |
| var newHitRects:Object; |
| var rectCount:int = 0; |
| |
| if (elements.length != 0) |
| { |
| newHitRects = {}; |
| |
| for each (var element:FlowElement in elements) |
| { |
| var elemStart:int = element.getAbsoluteStart(); |
| var elemEnd:int = Math.min(elemStart + element.textLength, endPos); |
| var tf:TextFlow = element.getTextFlow(); |
| //Previously make sure the textflow of element is not null. No logic changes within the curly braces below. |
| if(tf) |
| { |
| var elemRects:Array = GeometryUtil.getHighlightBounds(new TextRange(tf, elemStart, elemEnd)); |
| // this is an array of objects with textLine and rect properties |
| // Create an array of rectangle and owner objects to feed into a HitTestArea |
| for each (obj in elemRects) |
| { |
| rect = obj.rect; |
| //Fix for bug#2990689, handle the blockProgression == RL and direction == RTL case |
| var leftEdge:Number = clipRect.x; |
| var topEdge:Number = clipRect.y; |
| |
| //Fix for bug#2990689, 1st step: |
| // When the blockProgression == RL and scrollPolicy.OFF case, |
| // There will be a left/top adjust in ContainerController.fillShapeChildren() |
| // The code change tried to find the real left boundary of container |
| // by equalizing the adjustment in ContainerController.fillShapeChildren() |
| var wmode:String = element.computedFormat.blockProgression; |
| var adjustLines:Boolean = false; |
| |
| adjustLines = (wmode == BlockProgression.RL) && |
| (container.horizontalScrollPolicy == ScrollPolicy.OFF && |
| container.verticalScrollPolicy == ScrollPolicy.OFF); |
| if (adjustLines) |
| { |
| var width:Number = container.measureWidth? clipRect.width: container.compositionWidth; |
| leftEdge = clipRect.x - width + container.horizontalScrollPosition + clipRect.width; |
| } |
| |
| //Fix for bug#2990689, 2nd step: |
| // When the direction == RTL case, the left/top edge of he text boundary will be the |
| // text width to the right edge rather than |
| // the left boundary of container. |
| // The code change tried to find the real left/top boundary of container |
| // by setting the leftEdge to be the container's x and the topEdge to be container's y |
| if(wmode == BlockProgression.TB) |
| { |
| leftEdge = 0; |
| topEdge = 0; |
| } |
| else |
| { |
| topEdge = 0; |
| } |
| |
| rect.x = leftEdge + obj.textLine.x + rect.x + xoffset; |
| rect.y = topEdge + obj.textLine.y + rect.y; |
| //Fix for bug#2990689 end |
| // Only use the visible parts of the rectangle if any |
| rect = rect.intersection(clipRect); |
| if (!rect.isEmpty()) |
| { |
| // use integer rectangles for better toString() representation |
| // this decreases the number of stored rectangles due to FP errors |
| rect.x = int(rect.x); |
| rect.y = int(rect.y); |
| rect.width = int(rect.width); |
| rect.height = int(rect.height); |
| var name:String = rect.toString(); |
| var oldObj:Object = newHitRects[name]; |
| if (!oldObj || oldObj.owner != element) |
| { |
| // replace or add operation |
| newHitRects[name] = { rect: rect, owner: element }; |
| rectCount++; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (rectCount > 0) |
| { |
| if (!_hitTests) |
| startHitTests(); |
| _hitRects = newHitRects; |
| _hitTests = new HitTestArea(newHitRects); |
| } |
| else |
| stopHitTests(); |
| } |
| |
| /** @private Start hit testing. */ |
| tlf_internal function startHitTests():void |
| { |
| _currentElement = null; |
| _mouseDownElement = null; |
| _ctrlKeyState = false; |
| // conditionally attach required event listeners |
| addEventListener(MouseEvent.MOUSE_OVER, false); |
| addEventListener(MouseEvent.MOUSE_OUT, false); |
| addEventListener(MouseEvent.MOUSE_DOWN, false); |
| addEventListener(MouseEvent.MOUSE_UP, false); |
| addEventListener(MouseEvent.MOUSE_MOVE, false); |
| } |
| |
| /** |
| * Stop hit testing altogether. You must call this method if the |
| * FlowElement structure of the visible area has changed; you should |
| * also call this method if the visible area is rebuilt completely. |
| */ |
| public function stopHitTests():void |
| { |
| // conditionally remove required event listeners |
| removeEventListener(MouseEvent.MOUSE_OVER, false); |
| removeEventListener(MouseEvent.MOUSE_OUT, false); |
| removeEventListener(MouseEvent.MOUSE_DOWN, false); |
| removeEventListener(MouseEvent.MOUSE_UP, false); |
| removeEventListener(MouseEvent.MOUSE_MOVE, false); |
| removeEventListener(KeyboardEvent.KEY_DOWN, true); |
| removeEventListener(KeyboardEvent.KEY_UP, true); |
| _hitRects = null; |
| _hitTests = null; |
| _currentElement = null; |
| _mouseDownElement = null; |
| _ctrlKeyState = false; |
| } |
| |
| private function addEventListener(name:String, kbdEvent:Boolean = false):void |
| { |
| if (_eventListeners[name] === THIS_HANDLES_EVENT) |
| { |
| var target:DisplayObjectContainer; |
| var listener:Function; |
| if (kbdEvent) |
| { |
| target = _container.stage; |
| if (!target) |
| target = _container; |
| listener = hitTestKeyEventHandler; |
| } |
| else |
| { |
| target = _container; |
| listener = hitTestMouseEventHandler; |
| } |
| target.addEventListener(name, listener, false, 1); |
| _eventListeners[name] = THIS_LISTENS_FOR_EVENTS; |
| } |
| } |
| |
| private function removeEventListener(name:String, kbdEvent:Boolean):void |
| { |
| if (_eventListeners[name] === THIS_LISTENS_FOR_EVENTS) |
| { |
| var target:DisplayObjectContainer; |
| var listener:Function; |
| if (kbdEvent) |
| { |
| target = _container.stage; |
| if (!target) |
| target = _container; |
| listener = hitTestKeyEventHandler; |
| } |
| else |
| { |
| target = _container; |
| listener = hitTestMouseEventHandler; |
| } |
| target.removeEventListener(name, listener); |
| _eventListeners[name] = THIS_HANDLES_EVENT; |
| } |
| } |
| |
| /** @private |
| * Collect all FlowElements with an active event mirror plus all LinkElements in to the given |
| * array. This method is recursive so it can iterate over child FlowElements. |
| * |
| * @param parent The parent element to scan. |
| * @param startPosition The starting character position. |
| * @param endPosition The ending character position. |
| * @param results The array to fill with FlowElements that match. |
| */ |
| tlf_internal function collectElements(parent:FlowGroupElement, startPosition:int, endPosition:int, results:Array):void |
| { |
| CONFIG::debug { assert(startPosition >= 0,"Bad startPosition parameter"); } |
| CONFIG::debug { assert(endPosition >= startPosition,"Bad endPosition parameter"); } |
| |
| var i:int = parent.findChildIndexAtPosition(startPosition); |
| for (; i < parent.numChildren; i++) |
| { |
| var child:FlowElement = parent.getChildAt(i); |
| if (child.parentRelativeStart >= endPosition) |
| // behind end |
| break; |
| if (child.hasActiveEventMirror() || (child is LinkElement)) |
| results.push(child); |
| var group:FlowGroupElement = child as FlowGroupElement; |
| if (group) |
| collectElements(group, Math.max(startPosition-group.parentRelativeStart,0), endPosition-group.parentRelativeStart, results); |
| } |
| } |
| |
| /** |
| * Dispatch the mouse and keyboard events that the owner sends. |
| */ |
| public function dispatchEvent(evt:Event):void |
| { |
| var mouseEvt:MouseEvent = evt as MouseEvent; |
| if (mouseEvt) |
| hitTestMouseEventHandler(mouseEvt); |
| else |
| { |
| var keyEvt:KeyboardEvent = evt as KeyboardEvent; |
| if (keyEvt) |
| hitTestKeyEventHandler(keyEvt); |
| } |
| } |
| |
| /** @private |
| * Process the key down/key up messages for the Ctrl key. This handler |
| * is required to make a visual change for the element if the Ctrl key is |
| * pressed or released while not moving the mouse. If a Ctrl key change |
| * is detected, and the owner signalled that the Ctrl key is required, |
| * there will be a mouseOver element if the Ctrl key is pressed, and a |
| * mouseOut event if the Ctrl key is released. |
| */ |
| private function hitTestKeyEventHandler(evt:KeyboardEvent):void |
| { |
| if (!_blockInteraction) |
| checkCtrlKeyState(evt.ctrlKey); |
| } |
| |
| private function checkCtrlKeyState(curState:Boolean):void |
| { |
| // currently, support only LinkElements |
| var link:LinkElement = _currentElement as LinkElement; |
| // do nothing if the Ctrl key is not needed, or the key state did not change |
| // we also need the last mouse event for the button state to pass in to the link |
| if(!link || !_needsCtrlKey || !_lastMouseEvent || (curState == _ctrlKeyState)) |
| return; |
| _ctrlKeyState = curState; |
| |
| // the event type is irrelevant here - only the buttonDown state is relevant |
| if (_ctrlKeyState) |
| link.mouseOverHandler(this, _lastMouseEvent); |
| else |
| link.mouseOutHandler(this, _lastMouseEvent); |
| } |
| |
| /** @private |
| * Process mouse events. |
| * |
| * For event mirroring, hasActiveEventMirror() must be true; if so, the event mirror |
| * dispatches a FlowElementMouseEvent. The method generates fake rollOut and rollOver |
| * events; the original mouseOut and mouseOver events are container and/or TextLine events. |
| */ |
| private function hitTestMouseEventHandler(evt:MouseEvent):void |
| { |
| if (!_hitTests) |
| return; |
| |
| // note that mouseOver and mouseOut are used for hit-testing only |
| // need the last mouse event's button state to pass in to LinkElement |
| // in case the state of the Ctrl key changes (see hitTestKeyEventHandler()) |
| _lastMouseEvent = evt; |
| |
| var containerPoint:Point = mouseToContainer(evt); |
| var hitElement:FlowElement = _hitTests.hitTest(containerPoint.x, containerPoint.y); |
| if (hitElement != _currentElement) |
| { |
| _mouseDownElement = null; |
| if (_currentElement) |
| // generate a mouseOut event |
| localDispatchEvent(FlowElementMouseEvent.ROLL_OUT, evt); |
| else if (evt.buttonDown) |
| // do not interact if the button is down to not disturb e.g. |
| // a mark operation in the container |
| _blockInteraction = true; |
| _currentElement = hitElement; |
| if (_currentElement) |
| // generate a mouseOver event |
| localDispatchEvent(FlowElementMouseEvent.ROLL_OVER, evt); |
| else |
| // no FlowElement underneathmouse: reset interaction blocker |
| _blockInteraction = false; |
| } |
| |
| var isClick:Boolean = false; |
| var eventType:String = null; |
| switch (evt.type) |
| { |
| case MouseEvent.MOUSE_MOVE: |
| eventType = FlowElementMouseEvent.MOUSE_MOVE; |
| // Need to check the state of the event's Ctrl key in case |
| // the container lost focus, and the mouse was moved |
| if (!_blockInteraction) |
| checkCtrlKeyState(evt.ctrlKey); |
| break; |
| case MouseEvent.MOUSE_DOWN: |
| _mouseDownElement = _currentElement; |
| eventType = FlowElementMouseEvent.MOUSE_DOWN; |
| break; |
| case MouseEvent.MOUSE_UP: |
| eventType = FlowElementMouseEvent.MOUSE_UP; |
| isClick = (_currentElement == _mouseDownElement); |
| _mouseDownElement = null; |
| break; |
| } |
| |
| if (_currentElement && eventType) |
| { |
| localDispatchEvent(eventType, evt); |
| if (isClick) |
| localDispatchEvent(FlowElementMouseEvent.CLICK, evt); |
| } |
| } |
| |
| /** @private |
| * Dispatch a FlowElementMouseEvent with the given type. First, attempt to |
| * dispatch to an event mirror if attached and listening. If there was nobody |
| * listening at the event mirror, or the event did not stop propagation, dispatch |
| * the event to the TextFlow as well. |
| * |
| * @param type The event type, should be a constant defind in FlowElementMouseEvent. |
| * @param originalEvent The original mouse event. |
| * @return true if the event was dispatched and shgould not be distributed further. |
| */ |
| tlf_internal function dispatchFlowElementMouseEvent(type:String, originalEvent:MouseEvent):Boolean |
| { |
| // Mimick old behavior, and emit only rollOut events if Ctrl key is not down |
| if (_needsCtrlKey && !originalEvent.ctrlKey && type != FlowElementMouseEvent.ROLL_OUT) |
| return false; |
| |
| var locallyListening:Boolean = _currentElement.hasActiveEventMirror(); |
| var textFlow:TextFlow = _currentElement.getTextFlow(); |
| var textFlowListening:Boolean = false; |
| if (textFlow) |
| textFlowListening = textFlow.hasEventListener(type); |
| if (!locallyListening && !textFlowListening) |
| { |
| return false; |
| } |
| |
| var event:FlowElementMouseEvent = new FlowElementMouseEvent(type, false, true, _currentElement, originalEvent); |
| if (locallyListening) |
| { |
| _currentElement.getEventMirror().dispatchEvent(event); |
| if (event.isDefaultPrevented()) |
| return true; |
| } |
| if (textFlowListening) |
| { |
| textFlow.dispatchEvent(event); |
| if (event.isDefaultPrevented()) |
| return true; |
| } |
| return false; |
| } |
| |
| /** @private |
| * Dispatch a FlowElementMouseEvent, and call the correct LinkElement |
| * event handler if the current element is a LinkElement. |
| * |
| * LinkElements implement several mouse handlers; these are called directly |
| * so LinkElements do not needs to register themselves with their own |
| * event mirror. |
| */ |
| |
| private function localDispatchEvent(type:String, evt:MouseEvent):void |
| { |
| if (_blockInteraction || !_currentElement) |
| return; |
| |
| // Attach or detach listeners for the Ctrl key if needed |
| if (_needsCtrlKey) |
| switch (type) |
| { |
| case FlowElementMouseEvent.ROLL_OVER: |
| addEventListener(KeyboardEvent.KEY_DOWN, true); |
| addEventListener(KeyboardEvent.KEY_UP, true); |
| break; |
| case FlowElementMouseEvent.ROLL_OUT: |
| removeEventListener(KeyboardEvent.KEY_DOWN, true); |
| removeEventListener(KeyboardEvent.KEY_UP, true); |
| break; |
| } |
| |
| if (dispatchFlowElementMouseEvent(type, evt)) |
| return; |
| |
| // dispatch to a LinkElement only if Ctrl key conditions fit |
| var link:LinkElement = (!_needsCtrlKey || evt.ctrlKey) ? (_currentElement as LinkElement) : null; |
| if (!link) |
| return; |
| |
| // use the FlowElementMouseEvent type - the mouse event type may be unrelated |
| switch (type) |
| { |
| case FlowElementMouseEvent.MOUSE_DOWN: |
| link.mouseDownHandler(this, evt); |
| break; |
| case FlowElementMouseEvent.MOUSE_MOVE: |
| link.mouseMoveHandler(this, evt); |
| break; |
| case FlowElementMouseEvent.ROLL_OUT: |
| link.mouseOutHandler(this, evt); |
| break; |
| case FlowElementMouseEvent.ROLL_OVER: |
| link.mouseOverHandler(this, evt); |
| break; |
| case FlowElementMouseEvent.MOUSE_UP: |
| link.mouseUpHandler(this, evt); |
| break; |
| case FlowElementMouseEvent.CLICK: |
| link.mouseClickHandler(this, evt); |
| break; |
| } |
| } |
| |
| /** @private |
| * Utility method for LinkElement (and other elements in the future that |
| * might implement the same mouse handlers) to set and reset the Hand cursor. |
| */ |
| tlf_internal function setHandCursor(state:Boolean=true):void |
| { |
| if (_currentElement == null) |
| return; |
| |
| var tf:TextFlow = _currentElement.getTextFlow(); |
| if (tf != null && tf.flowComposer && tf.flowComposer.numControllers) |
| { |
| var sprite:Sprite = _container as Sprite; |
| if (sprite) |
| { |
| sprite.buttonMode = state; |
| sprite.useHandCursor = state; |
| } |
| if (state) |
| Mouse.cursor = MouseCursor.BUTTON; |
| else |
| { |
| var wmode:String = tf.computedFormat.blockProgression; |
| if (tf.interactionManager && (wmode != BlockProgression.RL)) |
| Mouse.cursor = MouseCursor.IBEAM; |
| else |
| Mouse.cursor = Configuration.getCursorString(tf.configuration, MouseCursor.AUTO); |
| } |
| Mouse.hide(); |
| Mouse.show(); |
| } |
| } |
| } |
| } |