//////////////////////////////////////////////////////////////////////////////// // // 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.elements { import flash.display.DisplayObject; import flash.display.Sprite; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.events.MouseEvent; import flash.net.*; import flash.text.engine.GroupElement; import flash.text.engine.TextLine; import flash.text.engine.TextLineMirrorRegion; import flash.text.engine.TextLineValidity; import flash.ui.Mouse; import flash.ui.MouseCursor; import flashx.textLayout.compose.IFlowComposer; import flashx.textLayout.container.ContainerController; import flashx.textLayout.debug.assert; import flashx.textLayout.edit.EditingMode; import flashx.textLayout.events.FlowElementMouseEvent; import flashx.textLayout.events.ModelChange; import flashx.textLayout.formats.BlockProgression; import flashx.textLayout.formats.ITextLayoutFormat; import flashx.textLayout.formats.TextLayoutFormat; import flashx.textLayout.formats.TextLayoutFormatValueHolder; import flashx.textLayout.tlf_internal; use namespace tlf_internal; /** * Dispatched when the mouse is pressed down over a link. * @eventType flashx.textLayout.events.FlowElementMouseEvent.MOUSE_DOWN * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="mouseDown", type="flashx.textLayout.events.FlowElementMouseEvent")] /** * Dispatched when the mouse is released over a link. * @eventType flashx.textLayout.events.FlowElementMouseEvent.MOUSE_UP * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="mouseUp", type="flashx.textLayout.events.FlowElementMouseEvent")] /** * Dispatched when the mouse passes over the link. * @eventType flashx.textLayout.events.FlowElementMouseEvent.MOUSE_MOVE * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="mouseMove", type="flashx.textLayout.events.FlowElementMouseEvent")] /** * Dispatched when the mouse first enters the link. * @eventType flashx.textLayout.events.FlowElementMouseEvent.ROLL_OVER * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="rollOver", type="flashx.textLayout.events.FlowElementMouseEvent")] /** * Dispatched when the mouse goes out of the link. * @eventType flashx.textLayout.events.FlowElementMouseEvent.ROLL_OUT * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="rollOut", type="flashx.textLayout.events.FlowElementMouseEvent")] /** * Dispatched when the link is clicked. * Clients may override how the link handles the event by handling it themselves, and calling preventDefault(). * @eventType flashx.textLayout.events.FlowElementMouseEvent.CLICK * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ [Event(name="click", type="flashx.textLayout.events.FlowElementMouseEvent")] /** The LinkElement class defines a link to a URI (Universal Resource Identifier), which is executed when the user clicks it. * The LinkElement class is a subclass of the SubParagraphGroupElement class and it can contain * one or more FlowElement objects, such as a SpanElement object that stores the link text. An empty * LinkElement, which does not contain a FlowElement object, is ignored. *
If you specify a target, it must be one of the following values: *
Target value | *description | *
---|---|
_self | *Replaces the current HTML page. If it is in a frame or frameset, it will load within that frame. If it is * the full browser, it opens to replace the page from which it came. | *
_blank | *Opens a new browser name with no name. | *
_parent | *Replaces the HTML page from which it came. | *
_top | *Loads in the current browser, replacing anything within it, such as a frameset. | *
function(evt:Event):void
useCapture
is set to true
, the
* listener processes the event only during the capture phase and not in the target or
* bubbling phase. If useCapture
is false
, the listener processes the event only
* during the target or bubbling phase. To listen for the event in all three phases, call
* addEventListener()
twice, once with useCapture
set to true
,
* then again with useCapture
set to false
.
* @param priority The priority level of the event listener. Priorities are designated by a 32-bit integer. The higher the number, the higher the priority. All listeners with priority n are processed before listeners of priority n-1. If two or more listeners share the same priority, they are processed in the order in which they were added. The default priority is 0.
* @param useWeakReference Determines whether the reference to the listener is strong or weak. A strong
* reference (the default) prevents your listener from being garbage-collected. A weak
* reference does not. Class-level member functions are not subject to garbage
* collection, so you can set useWeakReference
to true
for
* class-level member functions without subjecting them to garbage collection. If you set
* useWeakReference
to true
for a listener that is a nested inner
* function, the function will be garbge-collected and no longer persistent. If you create
* references to the inner function (save it in another variable) then it is not
* garbage-collected and stays persistent.
removeEventListener()
are required to remove both: one call with useCapture
set to true
, and another call with useCapture
set to false
.
*
* @copy flash.events.IEventDispatcher#removeEventListener().
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false): void
{
eventDispatcher.removeEventListener(type, listener, useCapture);
}
/**
* @copy flash.events.IEventDispatcher#willTrigger()
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function willTrigger(type:String):Boolean
{
return eventDispatcher.willTrigger(type);
}
// end of IEventDispatcher functions
/** @private */
override protected function get abstract():Boolean
{
return false;
}
/**
* The Uniform Resource Identifier (URI) associated with the LinkElement object. The URI can be any URI
* supported by the flash.net.navigateToURL()
method. This property maps
* to the request
parameter for that method.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see ../../../flash/net/package.html#navigateToURL() flash.net.navigateToURL()
*/
public function get href():String
{
return _uriString;
}
public function set href(newUriString:String):void
{
_uriString = newUriString;
modelChanged(ModelChange.ELEMENT_MODIFIED,0,textLength);
}
/**
* The Target value associated with the LinkElement. Possible values are "_self", "_blank",
* "_parent", and "_top". This value maps to the window
parameter of the
* flash.net.navigateToURL()
method.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see ../../../flash/net/package.html#navigateToURL() flash.net.navigateToURL()
*/
public function get target():String
{
return _targetString;
}
public function set target(newTargetString:String):void
{
_targetString = newTargetString;
modelChanged(ModelChange.ELEMENT_MODIFIED,0,textLength);
}
/**
* The current state of the link.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see LinkState
*/
public function get linkState():String
{ return _linkState; }
/** @private */
public override function shallowCopy(startPos:int = 0, endPos:int = -1):FlowElement
{
if (endPos == -1)
endPos = textLength;
var retFlow:LinkElement = super.shallowCopy(startPos, endPos) as LinkElement;
retFlow.href = href;
retFlow.target = target;
return retFlow;
}
/** @private */
tlf_internal override function mergeToPreviousIfPossible():Boolean
{
// in links the eventMirror exists. TLF ignores that when merging. The risk is that everything matches but the user has added a custom listener to the eventMirror.
var theParent:FlowGroupElement = parent;
if (theParent && !bindableElement)
{
var myidx:int = theParent.getChildIndex(this);
if (textLength == 0)
{
theParent.replaceChildren(myidx, myidx + 1, null);
return true;
}
if (myidx != 0 && (attachedListenerStatus & SubParagraphGroupElement.CLIENT_ATTACHED_LISTENERS) == 0)
{
var sib:LinkElement = theParent.getChildAt(myidx-1) as LinkElement;
if (sib != null && (sib.attachedListenerStatus & SubParagraphGroupElement.CLIENT_ATTACHED_LISTENERS) == 0)
{
if ((this.href == sib.href) && (this.target == sib.target) && equalStylesForMerge(sib))
{
var curFlowElement:FlowElement = null;
if (numChildren > 0)
{
while (numChildren > 0)
{
curFlowElement = getChildAt(0);
replaceChildren(0, 1);
sib.replaceChildren(sib.numChildren, sib.numChildren, curFlowElement);
}
}
theParent.replaceChildren(myidx, myidx + 1, null);
return true;
}
}
}
}
return false;
}
/**
* Specifies the name of the text format element of a LinkElement when the link is in the normal state.
* @private
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
static tlf_internal const LINK_NORMAL_FORMAT_NAME:String = "linkNormalFormat";
/**
* Specifies the name of the text format element of a LinkElement when the link is in the active state.
* @private
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
static tlf_internal const LINK_ACTIVE_FORMAT_NAME:String = "linkActiveFormat";
/** Specifies the name of the text format element of a LinkElement when the cursor is hovering over the link.
* @private
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
static tlf_internal const LINK_HOVER_FORMAT_NAME:String = "linkHoverFormat";
private function computeLinkFormat(formatName:String):ITextLayoutFormat
{
var linkStyle:Object = getStyle(formatName);
if (linkStyle == null)
{
var tf:TextFlow = getTextFlow();
var formatStr:String = formatName.substr(1);
return tf == null ? null : tf.configuration["defaultL" + formatStr];
}
else if (linkStyle is ITextLayoutFormat)
return ITextLayoutFormat(linkStyle);
// We need to convert the linkStyle object into a ITextLayoutFormat object
var ca:TextLayoutFormatValueHolder = new TextLayoutFormatValueHolder;
var desc:Object = TextLayoutFormat.description;
for (var prop:String in desc)
{
if (linkStyle[prop] != undefined)
ca[prop] = linkStyle[prop];
}
return ca;
}
/**
* The state-dependent character attributes for the link.
* @private
*/
tlf_internal function get effectiveLinkElementTextLayoutFormat():ITextLayoutFormat
{
var cf:ITextLayoutFormat;
if (_linkState == LinkState.ACTIVE)
{
cf = computeLinkFormat(LINK_ACTIVE_FORMAT_NAME);
if (cf)
return cf;
}
if (_linkState == LinkState.HOVER)
{
cf = computeLinkFormat(LINK_HOVER_FORMAT_NAME);
if (cf)
return cf;
}
return computeLinkFormat(LINK_NORMAL_FORMAT_NAME);
}
/** @private */
tlf_internal override function get formatForCascade():ITextLayoutFormat
{
var superFormat:ITextLayoutFormat = format;
var effectiveFormat:ITextLayoutFormat = effectiveLinkElementTextLayoutFormat;
if (effectiveFormat || superFormat)
{
if (effectiveFormat && superFormat)
{
var resultingTextLayoutFormat:TextLayoutFormatValueHolder = new TextLayoutFormatValueHolder(effectiveFormat);
if (superFormat)
resultingTextLayoutFormat.concatInheritOnly(superFormat);
return resultingTextLayoutFormat;
}
return superFormat ? superFormat : effectiveFormat;
}
return null;
}
/** @private */
CONFIG::debug tlf_internal override function setParentAndRelativeStart(newParent:FlowGroupElement,newStart:int):void
{
if (groupElement)
{
var groupTextLength:int = groupElement.rawText ? groupElement.rawText.length : null;
assert(groupTextLength == this.textLength, "LinkElement - gc = " + this.groupElement.rawText + " this.textLength = " + this.textLength);
}
super.setParentAndRelativeStart(newParent,newStart);
}
private function redrawLink(ignoreNextMouseOut:Boolean=true):void
{
parent.formatChanged(true);
var tf:TextFlow = getTextFlow();
if (tf && tf.flowComposer)
{
tf.flowComposer.updateAllControllers();
if (_linkState != LinkState.HOVER)
_ignoreNextMouseOut = ignoreNextMouseOut;
}
}
private function setToState(linkState:String, ignoreNextMouseOut:Boolean=true):void
{
if (_linkState != linkState)
{
var oldCharAttrs:ITextLayoutFormat = effectiveLinkElementTextLayoutFormat;
_linkState = linkState;
var newCharAttrs:ITextLayoutFormat = effectiveLinkElementTextLayoutFormat;
if (!(TextLayoutFormat.isEqual(oldCharAttrs, newCharAttrs)))
{
redrawLink(ignoreNextMouseOut);
}
}
}
private function setHandCursor(state:Boolean=true):void
{
var tf:TextFlow = getTextFlow();
if (tf != null && tf.flowComposer && tf.flowComposer.numControllers)
{
doToAllControllers(setContainerHandCursor);
if (state)
Mouse.cursor = MouseCursor.AUTO;
else
{
var wmode:String = tf.computedFormat.blockProgression;
if (tf.interactionManager && (wmode != BlockProgression.RL))
Mouse.cursor = MouseCursor.IBEAM;
else
Mouse.cursor = MouseCursor.AUTO;
}
}
function setContainerHandCursor(controller:ContainerController):void
{
var container:Sprite = controller.container as Sprite;
if (container)
{
container.buttonMode = state;
container.useHandCursor = state;
}
}
}
private function handleEvent(event:FlowElementMouseEvent):Boolean
{
eventDispatcher.dispatchEvent(event);
if (event.isDefaultPrevented())
return true;
var textFlow:TextFlow = getTextFlow();
if (textFlow)
{
textFlow.dispatchEvent(event);
if (event.isDefaultPrevented())
return true;
}
return false;
}
private function mouseDownHandler(evt:MouseEvent):void
{
CONFIG::debug { assert(getTextFlow() != null,"Invalid TextFlow"); }
if ((evt.ctrlKey) || (getTextFlow().interactionManager == null) || (getTextFlow().interactionManager.editingMode == EditingMode.READ_SELECT))
{
if (_mouseInLink)
{
_mouseDownInLink = true;
var event:FlowElementMouseEvent = new FlowElementMouseEvent("mouseDown", false, true, this, evt);
if (handleEvent(event)) return;
setHandCursor(true);
setToState(LinkState.ACTIVE, false);
}
evt.stopImmediatePropagation();
}
else
{
setHandCursor(false);
setToState(LinkState.LINK);
}
}
private function mouseMoveHandler(evt:MouseEvent):void
{
CONFIG::debug { assert(getTextFlow() != null,"Invalid TextFlow"); }
if (_isSelecting) return;
if ((evt.ctrlKey) || (getTextFlow().interactionManager == null) || (getTextFlow().interactionManager.editingMode == EditingMode.READ_SELECT))
{
if (_mouseInLink)
{
var event:FlowElementMouseEvent = new FlowElementMouseEvent("mouseMove", false, true, this, evt);
if (handleEvent(event)) return;
setHandCursor(true);
if (evt.buttonDown)
{
setToState(LinkState.ACTIVE, false);
}
else
{
setToState(LinkState.HOVER);
}
}
}
else
{
_mouseInLink = true;
setHandCursor(false);
setToState(LinkState.LINK);
}
}
private function mouseOutHandler(evt:MouseEvent):void
{
if (!_ignoreNextMouseOut)
{
_mouseInLink = false;
LinkElement.removeLinkFromMouseInArray(this);
_isSelecting = false;
_mouseDownInLink = false;
var event:FlowElementMouseEvent = new FlowElementMouseEvent(MouseEvent.ROLL_OUT, false, true, this, evt);
if (handleEvent(event)) return;
setHandCursor(false);
setToState(LinkState.LINK);
}
_ignoreNextMouseOut = false;
}
private static function addLinkToMouseInArray(linkEl:LinkElement):void
{
CONFIG::debug { assert(_mouseInLinkElement == null, "Multiple links active"); }
_mouseInLinkElement = linkEl;
attachContainerEventHandlers(linkEl);
}
private static function removeLinkFromMouseInArray(linkEl:LinkElement):void
{
_mouseInLinkElement = null;
detachContainerEventHandlers(linkEl);
}
/** @private */
// This function is a workaround for Flash Player bug in Astro where mouseOut event is not sent from eventMirror if mouse
// has left the TextLine at the next sample point (i.e., mouse leaves TextLine rapidly).
private static function stageMouseMoveHandler(evt:MouseEvent):void
{
CONFIG::debug { assert(_mouseInLinkElement != null, "containerMouseMoveHandler shouldn't be active listener now"); }
// Check to see if we are still in the link. We could be over an InlineGraphicElement, which could be part of the link.
// In that case, we don't want to turn off the link.
var target:DisplayObject = evt.target as DisplayObject;
while (target && !(target is TextLine))
target = target.parent;
if (target)
{
var textLine:TextLine = target as TextLine;
if (textLine.validity != TextLineValidity.INVALID)
{
var mirrorRegions:Vector.