blob: a4e63f159a15411d6ad6c2801bfff5d7aef7f8d0 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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.container
{
import flash.display.BlendMode;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.InteractiveObject;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.ContextMenuEvent;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.IMEEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.TextEvent;
import flash.events.TimerEvent;
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.ContextMenu;
import flash.ui.ContextMenuClipboardItems;
import flash.utils.Dictionary;
import flash.utils.Timer;
import flashx.textLayout.compose.FloatCompositionData;
import flashx.textLayout.compose.FlowComposerBase;
import flashx.textLayout.compose.FlowDamageType;
import flashx.textLayout.compose.IFlowComposer;
import flashx.textLayout.compose.TextFlowLine;
import flashx.textLayout.compose.TextLineRecycler;
import flashx.textLayout.debug.Debugging;
import flashx.textLayout.debug.assert;
import flashx.textLayout.edit.EditingMode;
import flashx.textLayout.edit.IInteractionEventHandler;
import flashx.textLayout.edit.ISelectionManager;
import flashx.textLayout.edit.SelectionFormat;
import flashx.textLayout.elements.BackgroundManager;
import flashx.textLayout.elements.Configuration;
import flashx.textLayout.elements.ContainerFormattedElement;
import flashx.textLayout.elements.FlowElement;
import flashx.textLayout.elements.FlowLeafElement;
import flashx.textLayout.elements.FlowValueHolder;
import flashx.textLayout.elements.InlineGraphicElement;
import flashx.textLayout.elements.LinkElement;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.events.FlowElementMouseEvent;
import flashx.textLayout.events.FlowElementMouseEventManager;
import flashx.textLayout.events.ModelChange;
import flashx.textLayout.events.ScrollEvent;
import flashx.textLayout.events.ScrollEventDirection;
import flashx.textLayout.events.TextLayoutEvent;
import flashx.textLayout.events.UpdateCompleteEvent;
import flashx.textLayout.formats.BlockProgression;
import flashx.textLayout.formats.Float;
import flashx.textLayout.formats.FormatValue;
import flashx.textLayout.formats.ITextLayoutFormat;
import flashx.textLayout.formats.TextLayoutFormat;
import flashx.textLayout.property.Property;
import flashx.textLayout.tlf_internal;
import flashx.textLayout.utils.Twips;
use namespace tlf_internal;
/**
* The ContainerController class defines the relationship between a TextFlow object and a container.
* A TextFlow may have one or more rectangular areas that can hold text; the text is said to be flowing
* through the containers. Each container is a Sprite that is the parent DisplayObject for the TextLines.
* Each container has a ContainerController that manages the container; the controller holds the target
* width and height for the text area, populates the container with TextLines, and handles scrolling. A
* controller also has a format associated with it that allows some formatting attributes to be applied
* to the text in the container. This allows, for instance, a TextFlow to have one container where the
* text appears in a single column, and a second container in the same TextFlow with two column text. Not
* all formatting attributes that can be applied to the container will affect the text; only the ones that
* affect container-level layout. The diagram below illustrates the relationship between the TextFlow,
* its flowComposer, and the display list.
*
* <p><img src="../../../images/textLayout_multiController.gif" alt="IContainerController"></img></p>
*
* @includeExample examples\ContainerControllerExample1.as -noswf
* @includeExample examples\ContainerControllerExample2.as -noswf
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.compose.IFlowComposer
* @see flashx.textLayout.elements.TextFlow
* @see flash.text.engine.TextLine
*/
public class ContainerController implements IInteractionEventHandler, ITextLayoutFormat, ISandboxSupport
{
static tlf_internal var usesDiscretionaryHyphens:Boolean = true;
static tlf_internal var startComposeFromBeginning:Boolean = false;
private var _textFlowCache:TextFlow;
private var _rootElement:ContainerFormattedElement;
private var _absoluteStart:int;
private var _textLength:int;
private var _container:Sprite;
private var _mouseEventManager:FlowElementMouseEventManager;
// note must be protected - subclass sets or gets this variable but can't be public
/** computed container attributes. @private */
protected var _computedFormat:TextLayoutFormat;
// Generated column information
// Generated column information
private var _columnState:ColumnState;
/** Container size to be composed */
private var _compositionWidth:Number = 0;
private var _compositionHeight:Number = 0;
private var _measureWidth:Boolean; // true if we're measuring (isNaN(compositionWidth) optimization so we don't call isNaN too much
private var _measureHeight:Boolean; // true if we're measuring (isNaN(compositionHeight) optimization so we don't call isNaN too much
/* Text bounds after composition */
private var _contentLeft:Number;
private var _contentTop:Number;
private var _contentWidth:Number;
private var _contentHeight:Number;
private var _uncomposedTextLength:int; // 0 if composition was complete when contentHeight, etc registered, greater than one otherwise
private var _finalParcelStart:int;
// Scroll policy -- determines whether scrolling is enabled or not
private var _horizontalScrollPolicy:String;
private var _verticalScrollPolicy:String;
// x, y location of the text in the container relative to the underlying scrollable area
private var _xScroll:Number;
private var _yScroll:Number;
/** Are event listeners attached to the container */
private var _minListenersAttached:Boolean = false;
private var _allListenersAttached:Boolean = false;
private var _selectListenersAttached:Boolean = false;
tlf_internal var _mouseWheelListenerAttached:Boolean = false;
/** @private */
tlf_internal function get allListenersAttached():Boolean
{ return _allListenersAttached; }
/** Are the displayed shapes out of date? */
private var _shapesInvalid:Boolean = false;
private var _backgroundShape:Shape;
private var _scrollTimer:Timer = null;
/**
* @private use this boolean to determine if container.scrollRect is set. Accessing scrollRect when null changes the rendering behavior of flash player.
*/
protected var _hasScrollRect:Boolean;
private var _linesInView:Array; // lines that were in view according to the previous compose(). Empty if the lines have already been posted to the display list.
private var _updateStart:int;
private var _composedFloats:Array; // floats that were composed into the controller -- array of FloatCompositionData
private var _floatsInContainer:Array; // floats are currently in view -- array of DisplayObject
/** Interactive Objects **/
private var _interactiveObjects:Dictionary = new Dictionary(true);
private var _oldInteractiveObjects:Array = new Array();
tlf_internal function get interactiveObjects():Dictionary
{
return _interactiveObjects ;
}
tlf_internal function get oldInteractiveObjects():Array
{
return _oldInteractiveObjects ;
}
/**
* @private
*
* <p>This property enables a client to test for a ScrollRect object without accessing
* the DisplayObject.scrollRect property, which can have side effects in some cases.</p>
*
* @return true if the controller has attached a ScrollRect instance.
*/
tlf_internal function get hasScrollRect():Boolean
{ return _hasScrollRect; }
CONFIG::debug
{
protected var id:String;
private static var contCount:int = 0;
}
private var _shapeChildren:Array;
private var _format:FlowValueHolder;
private var _containerRoot:DisplayObject;
/* Controller have a non-zero default width and height so that if you construct a text example with a container and don't
* specify width and height you will still see some text so that you can then have a clue what to do to correct its appearance.
*/
/**
* Constructor - creates a ContainerController instance. The ContainerController has a default <code>compositionWidth</code>
* and <code>compositionHeight</code> so that some text appears in the container if you don't specify its width
* height.
*
* @param container The DisplayObjectContainer in which to manage the text lines.
* @param compositionWidth The initial width for composing text in the container.
* @param compositionHeight The initial height for composing text in the container.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function ContainerController(container:Sprite,compositionWidth:Number=100,compositionHeight:Number=100)
{
initialize(container,compositionWidth,compositionHeight);
}
private function initialize(container:Sprite,compositionWidth:Number,compositionHeight:Number):void
{
_container = container;
_containerRoot = null;
_textLength = 0;
_absoluteStart = -1;
_columnState = new ColumnState(null/*blockProgression*/, null/*columnDirection*/, null/*controller*/, 0/*compositionWidth*/, 0/*compositionHeight*/);
//_visibleRect = new Rectangle();
_xScroll = _yScroll = 0;
_contentWidth = _contentHeight = 0;
_uncomposedTextLength = 0;
// We have to set the flag so that we will get double click events. This
// is a change to the container we are given, but a minor one.
_container.doubleClickEnabled = true;
_horizontalScrollPolicy = _verticalScrollPolicy = String(ScrollPolicy.scrollPolicyPropertyDefinition.defaultValue);
_hasScrollRect = false;
CONFIG::debug { id = contCount.toString(); ++contCount; }
_shapeChildren = [ ];
_linesInView = [ ];
setCompositionSize(compositionWidth, compositionHeight);
format = _containerControllerInitialFormat;
}
/** @private */
tlf_internal function get effectiveBlockProgression():String
{
return _rootElement ? _rootElement.computedFormat.blockProgression : BlockProgression.TB;
}
/** @private Determine containerRoot in case the stage is not accessible. Normally the root is the stage. */
tlf_internal function getContainerRoot():DisplayObject
{
// safe to test for stage existence
if (_containerRoot == null && _container && _container.stage)
{
// if the stage is accessible lets use it.
// trace("BEFORE COMPUTING CONTAINERROOT");
try
{
var x:int = _container.stage.numChildren;
_containerRoot = _container.stage;
}
catch(e:Error)
{
// TODO: some way to find the highest level accessible root???
_containerRoot = _container.root;
}
// trace("AFTER COMPUTING CONTAINERROOT");
}
return _containerRoot;
}
/**
* Returns the flow composer object that composes and highlights text into the container that this
* controller manages.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.compose.IFlowComposer
*/
public function get flowComposer():IFlowComposer
{ return textFlow ? textFlow.flowComposer : null; }
/** @private */
tlf_internal function get shapesInvalid():Boolean
{ return _shapesInvalid; }
/** @private */
tlf_internal function set shapesInvalid(val:Boolean):void
{ _shapesInvalid = val; }
/**
* Returns a ColumnState object, which describes the number and characteristics of columns in
* the container. These values are updated when the text is recomposed, either as a result
* of <code>IFlowComposer.compose()</code> or <code>IFlowComposer.updateAllControllers()</code>.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see ColumnState
*/
public function get columnState():ColumnState
{
if (_rootElement == null)
return null;
if (_computedFormat == null)
computedFormat;
_columnState.computeColumns();
return _columnState;
}
/**
* Returns the container display object that holds the text lines for this ContainerController instance.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #ContainerController()
*/
public function get container():Sprite
{ return _container; }
/**
* Returns the horizontal extent allowed for text inside the container. The value is specified in pixels.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #setCompositionSize()
*/
public function get compositionWidth():Number
{ return _compositionWidth; }
/**
* Returns the vertical extent allowed for text inside the container. The value is specified in pixels.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #setCompositionSize()
*/
public function get compositionHeight():Number
{ return _compositionHeight; }
/** @private */
tlf_internal function get measureWidth():Boolean
{ return _measureWidth; }
/** @private */
tlf_internal function get measureHeight():Boolean
{ return _measureHeight; }
/**
* Sets the width and height allowed for text in the container. Width and height can be specified in pixels or <code>NaN</code> can be used for either value. <code>NaN</code> indicates measure that value.
* This can be used to find the widest line and/or the total height of all the content. When NaN is specified as the width lines are broken with a maximum width of <code>TextLine.MAX_LINE_WIDTH</code>.
* When <code>NaN</code> is specified as the height the container is assumed to have unlimited height. The actual measured values can be ready back in <code>getContentBounds</code>.
* When the computed <code>blockProgression</code> property of <code>TextFlow</code>
* is <code>BlockProgression.RL</code> the meanings of width and height are exchanged.
*
* @param w The width in pixels that's available for text in the container. <code>NaN</code> indicates no specified width.
* @param h The height in pixels that's available for text in the container. <code>NaN</code> indicates no specified height.
*
* @includeExample examples\ContainerController_setCompositionSizeExample.as -noswf
*
* @see flash.text.engine.TextLine#MAX_LINE_WIDTH
* @see flashx.textLayout.formats.BlockProgression
* @see #getContentBounds()
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function setCompositionSize(w:Number,h:Number):void
{
// trace("setCompositionSize(" + w + ", " + h + ")");
// note: NaN == NaN is always false
var widthChanged:Boolean = !(_compositionWidth == w || (isNaN(_compositionWidth) && isNaN(w)));
var heightChanged:Boolean = !(_compositionHeight == h || (isNaN(_compositionHeight) && isNaN(h)));
if (widthChanged || heightChanged)
{
_compositionHeight = h;
_measureHeight = isNaN(_compositionHeight);
_compositionWidth = w;
_measureWidth = isNaN(_compositionWidth);
// otherwise the reset will happen when the cascade is done
if (_computedFormat)
resetColumnState();
// Invalidate all the lines, forcing FTE rebuild if they changed in the logical width direction
if (effectiveBlockProgression == BlockProgression.TB ? widthChanged : heightChanged)
{
if (textFlow && _textLength)
textFlow.damage(absoluteStart, _textLength, TextLineValidity.INVALID, false);
}
else
invalidateContents(); // don't need to rebuild FTE lines, just reflow them
attachTransparentBackgroundForHit(false);
}
}
/**
* Returns the TextFlow object whose content appears in the container. Either the <code>textFlow</code> and
* <code>rootElement</code> values are the same, or this is the root element's TextFlow object. For example,
* if the container's root element is a DivElement, the value would be the TextFlow object to which the
* DivElement belongs.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.elements.TextFlow TextFlow
*/
public function get textFlow():TextFlow
{
if (!_textFlowCache && _rootElement)
_textFlowCache = _rootElement.getTextFlow();
return _textFlowCache;
}
// Reserve possibility for future use as a ContainerFormattedElement within the TextFlow.
/**
* Returns the root element that appears in the container. The root element could be a DivElement or TextFlow
* instance, for example.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.elements.ContainerFormattedElement
* @see flashx.textLayout.elements.DivElement
* @see flashx.textLayout.elements.TextFlow
*/
public function get rootElement():ContainerFormattedElement
{ return _rootElement; }
/** Protected method used when updating the rootElement.
* @param value new container to be controlled
*
* @private
*/
tlf_internal function setRootElement(value:ContainerFormattedElement):void
{
if (_rootElement != value)
{
if (_mouseEventManager)
_mouseEventManager.stopHitTests();
if (!value)
_mouseEventManager = null;
else if (!_mouseEventManager)
{
// Currently, the manager listens to all events itself.
// TODO: forward at least mouseOver and mouseDown events without
// causing side effects
_mouseEventManager = new FlowElementMouseEventManager(container, null);
// [MouseEvent.MOUSE_DOWN, MouseEvent.MOUSE_UP, MouseEvent.MOUSE_MOVE, MouseEvent.MOUSE_DOWN, MouseEvent.MOUSE_OUT,
// KeyboardEvent.KEY_DOWN, KeyboardEvent.KEY_UP]);
}
clearCompositionResults();
detachContainer();
_rootElement = value;
_textFlowCache = null;
_textLength = 0;
_absoluteStart = -1;
attachContainer();
if (_rootElement)
formatChanged();
if (_container && Configuration.playerEnablesSpicyFeatures)
_container["needsSoftKeyboard"] = (interactionManager && interactionManager.editingMode == EditingMode.READ_WRITE);
}
}
/**
* @copy flashx.textLayout.elements.TextFlow#interactionManager
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.elements.TextFlow#interactionManager
*/
public function get interactionManager():ISelectionManager
{
return textFlow ? textFlow.interactionManager : null;
}
/** @private */
tlf_internal function get uncomposedTextLength():int
{ return _uncomposedTextLength; }
/** @private */
tlf_internal function get finalParcelStart():int
{ return _finalParcelStart; }
/** @private */
tlf_internal function set finalParcelStart(val:int):void
{ _finalParcelStart = val; }
//--------------------------------------------------------------------------
//
// Start and length
//
//--------------------------------------------------------------------------
/**
* Returns the first character in the container. If this is not the first container in the flow,
* this value is updated when the text is composed, that is when the IFlowComposer's <code>compose()</code> or
* <code>updateAllControllers()</code> methods are called.
*
* @see flashx.textLayout.compose.IFlowComposer
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get absoluteStart():int
{
if (_absoluteStart != -1)
return _absoluteStart;
var rslt:int = 0;
var composer:IFlowComposer = flowComposer;
if (composer)
{
var stopIdx:int = composer.getControllerIndex(this);
if (stopIdx != 0)
{
var prevController:ContainerController = composer.getControllerAt(stopIdx-1);
rslt = prevController.absoluteStart + prevController.textLength;
}
}
_absoluteStart = rslt;
return rslt;
}
/** Returns the total number of characters in the container. This can include text that is not currently in view,
* if the container is scrollable. This value is updated when the text is composed (when the IFlowComposer's <code>compose()</code>
* or <code>updateAllControllers()</code> methods are called).
*
* @see flashx.textLayout.compose.IFlowComposer
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get textLength():int
{
return _textLength;
}
/** @private */
tlf_internal function setTextLengthOnly(numChars:int):void
{
if (_textLength != numChars)
{
_textLength = numChars;
_uncomposedTextLength = 0;
// all following containers must have absoluteStart invalidated
if (_absoluteStart != -1)
{
var composer:IFlowComposer = flowComposer;
if (composer)
{
var idx:int = composer.getControllerIndex(this)+1;
while (idx < flowComposer.numControllers)
{
var controller:ContainerController = composer.getControllerAt(idx++);
if (controller._absoluteStart == -1)
break;
controller._absoluteStart = -1;
controller._uncomposedTextLength = 0;
}
}
}
}
}
/** @private */
tlf_internal function setTextLength(numChars:int):void
{
CONFIG::debug { assert(numChars >= 0,"bad set textLength"); }
// If its a scrollable container, and it is the last one, then it gets all the characters even though we might not have composed them all
var uncomposedTextLength:int = 0;
if (textFlow)
{
var verticalText:Boolean = effectiveBlockProgression == BlockProgression.RL;
var flowComposer:IFlowComposer = textFlow.flowComposer;
if (numChars != 0 && flowComposer.getControllerIndex(this) == flowComposer.numControllers - 1 &&
((!verticalText && _verticalScrollPolicy != ScrollPolicy.OFF)||
(verticalText && _horizontalScrollPolicy != ScrollPolicy.OFF)))
{
var containerAbsoluteStart:int = absoluteStart;
CONFIG::debug { assert(textFlow.textLength >= containerAbsoluteStart,"ContainerController.setTextLength bad absoluteStart"); }
uncomposedTextLength = textFlow.textLength-(numChars+containerAbsoluteStart);
// _composeCompleteRatio = (textFlow.textLength-containerAbsoluteStart) == numChars ? 1 : 1.1;
// var scaledContentHeight:Number = _composeCompleteRatio * _contentHeight;
// trace("composeCompleteRatio:",_composeCompleteRatio,"composedContentHeight",_contentHeight,"scaledContentHeight",scaledContentHeight,"textLength",textFlow.textLength,"numChars",numChars);
// include all remaining characters in this container when scroll enabled
numChars = textFlow.textLength - containerAbsoluteStart;
}
}
// this call clears uncomposedTextLength - set it immediately afterwards
setTextLengthOnly(numChars);
_uncomposedTextLength = uncomposedTextLength;
CONFIG::debug
{
if (Debugging.debugOn && textFlow)
assert(Math.min(textFlow.textLength, absoluteStart)+_textLength <= textFlow.textLength, "container textLength may not extend past end of root element!");
}
}
/**
* Determines whether the container has text that requires composing.
*
* @return true if the container requires composing.
*
* @includeExample examples\ContainerController_isDamagedExample.as -noswf
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function isDamaged():Boolean
{
return flowComposer.isDamaged(absoluteStart + _textLength);
}
/** called whenever the container attributes are changed. Mark computed attributes and columnstate as out of date.
* @private
*/
tlf_internal function formatChanged():void
{
// The associated container, if there is one, inherits its container
// attributes from here. So we need to tell it that these attributes
// have changed.
_computedFormat = null;
invalidateContents();
}
/** This gets called when an element has changed its style selection related attributes. This may happen because an
* ancestor element changed it attributes.
* @private
*/
tlf_internal function styleSelectorChanged():void
{
modelChanged(ModelChange.STYLE_SELECTOR_CHANGED,this,0,this._textLength);
_computedFormat = null;
}
/** @private */
tlf_internal function modelChanged(changeType:String, element:ContainerController, changeStart:int, changeLen:int, needNormalize:Boolean = true, bumpGeneration:Boolean = true):void
{
var tf:TextFlow = _rootElement.getTextFlow();
if (tf)
tf.processModelChanged(changeType, element, absoluteStart+changeStart, changeLen, needNormalize, bumpGeneration);
}
/** @private */
tlf_internal function gatherVisibleLines(wmode:String, createShape:Boolean):void
{
CONFIG::debug { assert(_linesInView.length == 0,"gatherVisibleLines: bad _linesInView"); }
if (_textLength != 0)
{
// Similar computations also done in BaseCompose.getControllerVisibleBounds
var width:Number = _measureWidth ? _contentWidth : _compositionWidth;
var height:Number = _measureHeight ? _contentHeight : _compositionHeight;
var adjustX:Number = (wmode == BlockProgression.RL) ? _xScroll - width : _xScroll;
var adjustY:Number = _yScroll;
var scrollAdjustXTW:int = Twips.roundTo(adjustX);
var scrollAdjustYTW:int = Twips.roundTo(adjustY);
var scrollAdjustWidthTW:int = Twips.to(width);
var scrollAdjustHeightTW:int = Twips.to(height);
var flowComposer:IFlowComposer = this.flowComposer;
// Iterate over the lines in the container, setting the x and y positions and
// adding them to the list to go into the container. Keep track of the width
// and height of the actual text in the container.
var firstLine:int = flowComposer.findLineIndexAtPosition(absoluteStart);
var lastLine:int = flowComposer.findLineIndexAtPosition(absoluteStart + _textLength - 1);
var curLine:TextFlowLine;
var textLine:TextLine;
var lineIndex:int;
//Use binary search when there is one single column
if(columnCount == 1)
{
// First just test the firstLine - normal unscrolled case
curLine = flowComposer.getLineAt(firstLine);
textLine = testLineVisible(wmode, scrollAdjustXTW, scrollAdjustYTW, scrollAdjustWidthTW, scrollAdjustHeightTW, curLine, null) as TextLine;
firstLine++; // its been tested
if (textLine)
{
if (createShape)
curLine.createShape(wmode, textLine);
_linesInView.push(textLine);
}
else
{
var hi:int = lastLine;
while (firstLine <= hi)
{
var mid:int = (firstLine+hi)/2;
CONFIG::debug { assert(mid != 0,"ContainerController:gatherVisibleLines: bad mid"); }
curLine = flowComposer.getLineAt(mid);
var testRslt:* = testLineVisible(wmode, scrollAdjustXTW, scrollAdjustYTW, scrollAdjustWidthTW, scrollAdjustHeightTW, curLine, null);
textLine = testRslt as TextLine;
if (textLine)
{
// note that we tested firstLine above so going to mid-1 is always valid
var tempLine:TextFlowLine = flowComposer.getLineAt(mid-1);
if (!(testLineVisible(wmode, scrollAdjustXTW, scrollAdjustYTW, scrollAdjustWidthTW, scrollAdjustHeightTW, tempLine, null) is TextLine))
{
// Got the start
if (createShape)
curLine.createShape(wmode, textLine);
_linesInView.push(textLine);
firstLine = mid+1;
break;
}
testRslt = -1; // past the start
}
if (testRslt < 0 || testRslt == 2)
hi = mid-1;
else
firstLine = mid+1;
}
}
for (lineIndex = firstLine; lineIndex <= lastLine; lineIndex++)
{
curLine = flowComposer.getLineAt(lineIndex);
textLine = testLineVisible(wmode, scrollAdjustXTW, scrollAdjustYTW, scrollAdjustWidthTW, scrollAdjustHeightTW, curLine, null) as TextLine;
if (!textLine)
break;
if (createShape)
curLine.createShape(wmode, textLine);
_linesInView.push(textLine);
}
}
else //multiple columns
{
for (lineIndex = firstLine; lineIndex <= lastLine; lineIndex++)
{
curLine = flowComposer.getLineAt(lineIndex);
if (curLine == null || curLine.controller != this)
continue;
textLine = oldTestLineVisible(wmode, scrollAdjustXTW, scrollAdjustYTW, scrollAdjustWidthTW, scrollAdjustHeightTW, curLine, null);
if (textLine)
{
if (createShape)
curLine.createShape(wmode, textLine);
_linesInView.push(textLine);
}
}
}
}
_updateStart = absoluteStart; // we collected all lines from the start of the container
}
/** determines the shapechildren in the container and applies VJ. @private */
tlf_internal function fillShapeChildren():void
{
if (_textLength == 0)
return; // none
var wmode:String = effectiveBlockProgression;
if (_linesInView.length == 0) // no preexisting concpetion of what lines are in view: recalculate
gatherVisibleLines(wmode, true);
// If scrolling is turned off, and flow is vertical, then we need to adjust the positions of all the lines. With
// scrolling turned on, we don't need to do this because the adjustment is done in the Player when the scrollRect
// is set up correctly. But with the scrollRect, we also get clipping, and if scrolling is turned off we want to
// have the clipping turned off as well. So in this case we do the adjustment manually so the scrollRect can be null.
// NOTE: similar adjustments are made in TextContainerManager
var adjustLines:Boolean = (wmode == BlockProgression.RL) &&
(_horizontalScrollPolicy == ScrollPolicy.OFF &&
_verticalScrollPolicy == ScrollPolicy.OFF);
if (adjustLines)
{
var width:Number = _measureWidth ? _contentWidth : _compositionWidth;
var height:Number = _measureHeight ? _contentHeight : _compositionHeight;
var adjustX:Number = _xScroll - width; // vertical text: blockProgression is rl
var adjustY:Number = _yScroll;
// Iterate over the lines in the container, setting the x and y positions. Keep track of the width
// and height of the actual text in the container.
if (adjustX != 0 || adjustY != 0)
{
for each (var textLine:TextLine in _linesInView)
{
if (!textLine)
continue;
if (adjustLines)
{
textLine.x -= adjustX;
textLine.y -= adjustY;
}
}
_contentLeft -= adjustX;
_contentTop -= adjustY;
}
}
}
//--------------------------------------------------------------------------
//
// Scrolling
//
//--------------------------------------------------------------------------
/**
* Specifies the horizontal scrolling policy, which you can set by assigning one of the constants of
* the ScrollPolicy class: ON, OFF, or AUTO.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see ScrollPolicy
*/
public function get horizontalScrollPolicy():String
{
return _horizontalScrollPolicy;
}
public function set horizontalScrollPolicy(scrollPolicy:String):void
{
var newScrollPolicy:String = ScrollPolicy.scrollPolicyPropertyDefinition.setHelper(_horizontalScrollPolicy, scrollPolicy) as String;
if (newScrollPolicy != _horizontalScrollPolicy)
{
_horizontalScrollPolicy = newScrollPolicy;
if (_horizontalScrollPolicy == ScrollPolicy.OFF)
horizontalScrollPosition = 0;
formatChanged(); // scroll policy affects composition
}
}
/** @private */
tlf_internal function checkScrollBounds():void
{
var newHeight:Number;
var visibleHeight:Number;
var measuring:Boolean;
// If we've either grown the content past the composition bounds in the logical vertical direction,
// or shrunk it down under the composition bounds, signal a scrolling change
// If we're measuring we never scroll.
if (effectiveBlockProgression == BlockProgression.RL)
{
newHeight = _contentWidth;
visibleHeight = compositionWidth;
measuring = _measureWidth;
}
else
{
newHeight = _contentHeight;
visibleHeight = compositionHeight;
measuring = _measureHeight;
}
// Called when the bounds have changed and they now exceed the composition area, to see if we need to attach a mouse wheel listener for scrolling
if (textFlow && _container && !_minListenersAttached)
{
var needToScroll:Boolean = !measuring && newHeight > visibleHeight;
if (needToScroll != _mouseWheelListenerAttached)
{
if (_mouseWheelListenerAttached)
removeMouseWheelListener();
else
addMouseWheelListener();
}
}
}
/** Specifies the vertical scrolling policy, which you can set by assigning one of the constants of the ScrollPolicy
* class: ON, OFF, or, AUTO.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see ScrollPolicy
*/
public function get verticalScrollPolicy():String
{
return _verticalScrollPolicy;
}
public function set verticalScrollPolicy(scrollPolicy:String):void
{
var newScrollPolicy:String = ScrollPolicy.scrollPolicyPropertyDefinition.setHelper(_verticalScrollPolicy, scrollPolicy) as String;
if (newScrollPolicy != _verticalScrollPolicy)
{
_verticalScrollPolicy = newScrollPolicy;
if (_verticalScrollPolicy == ScrollPolicy.OFF)
verticalScrollPosition = 0;
formatChanged(); // scroll policy affects composition
}
}
/** Specifies the current horizontal scroll location on the stage. The value specifies the number of
* pixels from the left.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get horizontalScrollPosition():Number
{
return _xScroll;
}
public function set horizontalScrollPosition(x:Number):void
{
if (!_rootElement)
return;
if (_horizontalScrollPolicy == ScrollPolicy.OFF)
{
_xScroll = 0;
return;
}
var oldScroll:Number = _xScroll;
var newScroll:Number = computeHorizontalScrollPosition(x,true);
if (newScroll != oldScroll)
{
_shapesInvalid = true;
_xScroll = newScroll;
updateForScroll(ScrollEventDirection.HORIZONTAL, newScroll - oldScroll);
}
}
static private function pinValue(value:Number, minimum:Number, maximum:Number):Number
{
return Math.min(Math.max(value, minimum), maximum);
}
private function computeHorizontalScrollPosition(x:Number,okToCompose:Boolean):Number
{
var wmode:String = effectiveBlockProgression;
var curEstimatedWidth:Number = contentWidth;
var newScroll:Number = 0;
if (curEstimatedWidth > _compositionWidth && !_measureWidth)
{
// Pin the lower and upper bounds of _x. If we're doing vertical text, then the right edge is 0 and the left edge is negative
// We may not have composed all the way to the indicated position. If not, force composition so that we can be sure we're at
// a legal position.
if (wmode == BlockProgression.RL)
{
newScroll = pinValue(x, _contentLeft + _compositionWidth, _contentLeft + curEstimatedWidth);
if (okToCompose && _uncomposedTextLength != 0 && newScroll != _xScroll)
{
// in order to compose have to set _xScroll
_xScroll = x;
if (_xScroll > _contentLeft + _contentWidth)
_xScroll = _contentLeft + _contentWidth;
flowComposer.composeToController(flowComposer.getControllerIndex(this));
newScroll = pinValue(x, _contentLeft + _compositionWidth, _contentLeft + _contentWidth);
}
}
else
newScroll = pinValue(x, _contentLeft, (_contentLeft + curEstimatedWidth) - _compositionWidth);
}
return newScroll;
}
/** Specifies the current vertical scroll location on the stage. The value specifies the number of
* pixels from the top.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get verticalScrollPosition():Number
{
return _yScroll;
}
public function set verticalScrollPosition(y:Number):void
{
if (!_rootElement)
return;
if (_verticalScrollPolicy == ScrollPolicy.OFF)
{
_yScroll = 0;
return;
}
var oldScroll:Number = _yScroll;
var newScroll:Number = computeVerticalScrollPosition(y,true);
if (newScroll != oldScroll)
{
_shapesInvalid = true;
_yScroll = newScroll;
updateForScroll(ScrollEventDirection.VERTICAL, newScroll - oldScroll);
}
}
private function computeVerticalScrollPosition(y:Number,okToCompose:Boolean):Number
{
var newScroll:Number = 0;
var curcontentHeight:Number = contentHeight;
var wmode:String = effectiveBlockProgression;
// Only try to scroll if the content height is greater than the composition height, then there is text that is not visible to scroll to
if (curcontentHeight > _compositionHeight)
{
// new scroll value is somewhere between the topmost content, and the top of the last containerfull
newScroll = pinValue(y, _contentTop, _contentTop + (curcontentHeight - _compositionHeight));
// if we're not composed to the end, compose further so we can scroll to it. Sets the scroll position and then
// recomposes the container, which will compose through the end of the screenfull that starts at the requested position.
if (okToCompose && _uncomposedTextLength != 0 && wmode == BlockProgression.TB)
{
_yScroll = y;
if (_yScroll < _contentTop)
_yScroll = _contentTop;
flowComposer.composeToController(flowComposer.getControllerIndex(this));
newScroll = pinValue(y, _contentTop, _contentTop + (curcontentHeight - _compositionHeight));
}
}
return newScroll;
}
/**
* Returns the area that the text occupies, as reflected by the last compose or update operation.
* The width and the height might be estimated, if the container is scrollable and the text exceeds the
* visible area.
*
* @return describes the area that the text occupies.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\ContainerController_getContentBoundsExample.as -noswf
*
* @see flash.geom.Rectangle Rectangle
*/
public function getContentBounds():Rectangle
{
return new Rectangle(_contentLeft, _contentTop, contentWidth, contentHeight);
}
/**
* @private
*/
tlf_internal function get contentLeft():Number
{
return _contentLeft;
}
/**
* @private
*/
tlf_internal function get contentTop():Number
{
return _contentTop;
}
/** @private */
tlf_internal function computeScaledContentMeasure(measure:Number):Number
{
CONFIG::debug { assert(_finalParcelStart != -1 && _finalParcelStart >= this.absoluteStart && _finalParcelStart <= textFlow.textLength-_uncomposedTextLength,"computeScaledContentMeasure bad _finalParcelStart"); }
var charsInFinalParcel:int = textFlow.textLength-_finalParcelStart;
var composeCompleteRatio:Number = charsInFinalParcel / (charsInFinalParcel-_uncomposedTextLength);
// trace(measure*composeCompleteRatio,charsInFinalParcel,_uncomposedTextLength,measure,composeCompleteRatio);
return measure * composeCompleteRatio;
}
/**
* @private
*
* Returns the vertical extent of the text. For horizontal text, it includes space taken for descenders on the last line.
* If not all the text is composed, this returns an estimated value based on how much text is already composed; the
* more text that is composed, the more accurate s the estimate. To get a completely accurate value, recompose
* with the rootElement's flowComposer before accessing contentHeight.
* You can get the composed bounds of the text by getting the contentLeft, contentTop, contentWidth, contentHeight properties.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
tlf_internal function get contentHeight():Number
{
if (_uncomposedTextLength == 0 || effectiveBlockProgression != BlockProgression.TB)
return _contentHeight;
return computeScaledContentMeasure(_contentHeight);
}
/**
* @private
*
* Returns the horizontal extent of the text. For vertical text, it includes space taken for descenders on the last line.
* If not all the text is composed, this returns an estimated value based on how much text is already composed; the
* more text that is composed, the more accurate is the estimate. To get a completely accurate value, recompose
* with the rootElement's flowComposer before accessing contentWidth.
* You can get the composed bounds of the text by getting the contentLeft, contentTop, contentWidth, contentHeight properties.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
tlf_internal function get contentWidth():Number
{
if (_uncomposedTextLength == 0 || effectiveBlockProgression != BlockProgression.RL)
return _contentWidth;
return computeScaledContentMeasure(_contentWidth);
}
/** @private */
tlf_internal function setContentBounds(contentLeft:Number, contentTop:Number, contentWidth:Number, contentHeight:Number):void
{
_contentWidth = contentWidth;
_contentHeight = contentHeight;
_contentLeft = contentLeft;
_contentTop = contentTop;
checkScrollBounds();
}
private function updateForScroll(direction:String, delta:Number):void
{
_linesInView.length = 0; // zero out array of previously gathered up lines; its invalid because we've changed the visible area
var flowComposer:IFlowComposer = textFlow.flowComposer;
flowComposer.updateToController(flowComposer.getControllerIndex(this));
attachTransparentBackgroundForHit(false);
// notify client that we scrolled.
if (textFlow.hasEventListener(TextLayoutEvent.SCROLL))
textFlow.dispatchEvent(new ScrollEvent(TextLayoutEvent.SCROLL, false, false, direction, delta));
// trace("contentHeight", contentHeight, "contentWidth", contentWidth);
// trace("contentHeight", contentHeight, "contentWidth", contentWidth);
}
/** @private */
CONFIG::debug tlf_internal function validateLines():void
{
if (!Debugging.containerLineValidation)
return;
// Optimally we would recalculate which lines are in view and make sure they are parented
// to the container... but that causes side-effects (like creating TextLines) that affect
// regular execution. So we don't try that.
// Check all the children of the container. Verify that adornments go before or after lines, that lines are at the expected z-order position
// And that extraneous lines are not parented to the container.
var firstLineIndex:int = -1;
var lastLineIndex:int = -1;
var numChildren:int = _container.numChildren;
for (var childIndex:int = 0; childIndex < numChildren; ++childIndex)
{
var child:DisplayObject = _container.getChildAt(childIndex);
if (_shapeChildren.indexOf(child) < 0 && (!_floatsInContainer || _floatsInContainer.indexOf(child) < 0))
{
// the very last thing can be the selection sprite
if (childIndex == numChildren - 1)
assert(child == getSelectionSprite(false),"expected selectionsprite but not found");
assert(firstLineIndex == -1 || lastLineIndex == childIndex - 1, "Found adornment in the middle of TextLine children");
continue; // it's an adornment: skip
}
else
{
if (firstLineIndex == -1)
firstLineIndex = childIndex;
lastLineIndex = childIndex;
}
if (_floatsInContainer && _floatsInContainer.indexOf(child) >= 0) // it's a float
continue;
assert(child is TextLine, "Expected child to be a TextLine");
// Check that the line comes after previous lines, in z-order
var lineIndex:int = _shapeChildren.indexOf(child);
if (lineIndex > 0)
assert(_container.getChildIndex(_shapeChildren[lineIndex - 1]) < childIndex, "Line is visible but not at expected z-order position: earlier line is later in z-order");
else if (lineIndex < 0)
assert(false, "Found line that should not be in the container (its not considered visible)");
}
}
private function get containerScrollRectLeft():Number
{
var rslt:Number;
if (horizontalScrollPolicy == ScrollPolicy.OFF && verticalScrollPolicy == ScrollPolicy.OFF)
rslt = 0;
else
rslt= effectiveBlockProgression == BlockProgression.RL ? horizontalScrollPosition - compositionWidth : horizontalScrollPosition;
//CONFIG::debug { assert(container.scrollRect == null && rslt == 0 || int(rslt) == container.scrollRect.left,"Bad containerScrollRectLeft"); }
return rslt;
}
private function get containerScrollRectRight():Number
{
var rslt:Number = containerScrollRectLeft+compositionWidth;
//CONFIG::debug { assert(container.scrollRect == null && rslt == compositionWidth || int(rslt) == container.scrollRect.right,"Bad containerScrollRectRight"); }
return rslt;
}
private function get containerScrollRectTop():Number
{
var rslt:Number;
if (horizontalScrollPolicy == ScrollPolicy.OFF && verticalScrollPolicy == ScrollPolicy.OFF)
rslt = 0;
else
rslt = verticalScrollPosition;
//CONFIG::debug { assert(container.scrollRect == null && rslt == 0 || int(rslt) == container.scrollRect.top,"Bad containerScrollRectTop"); }
return rslt;
}
private function get containerScrollRectBottom():Number
{
var rslt:Number = containerScrollRectTop+compositionHeight;
//CONFIG::debug { assert(container.scrollRect == null && rslt == compositionHeight || int(rslt) == container.scrollRect.bottom,"Bad containerScrollRectBottom"); }
return rslt;
}
/**
* Scrolls so that the text range is visible in the container.
*
* @param activePosition The end of the selection that is changed when you extend the selection. It can be
* either the start or the end of the selection, expressed as an offset from the start of the text flow.
* @param anchorPosition The stable end of the selection when you extend the selection. It can be either
* the start or the end of the selection.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function scrollToRange(activePosition:int,anchorPosition:int):void
{
// return if we're not scrolling, or if it's not the last controller
if (!_hasScrollRect || !flowComposer || flowComposer.getControllerAt(flowComposer.numControllers-1) != this)
return;
// clamp values to range absoluteStart,absoluteStart+_textLength
var controllerStart:int = absoluteStart;
var lastPosition:int = Math.min(controllerStart+_textLength, textFlow.textLength - 1);
activePosition = Math.max(controllerStart,Math.min(activePosition,lastPosition));
anchorPosition = Math.max(controllerStart,Math.min(anchorPosition,lastPosition));
var verticalText:Boolean = effectiveBlockProgression == BlockProgression.RL;
var begPos:int = Math.min(activePosition,anchorPosition);
var endPos:int = Math.max(activePosition,anchorPosition);
// is part of the selection in view?
var begLineIndex:int = flowComposer.findLineIndexAtPosition(begPos,(begPos == textFlow.textLength));
var endLineIndex:int = flowComposer.findLineIndexAtPosition(endPos,(endPos == textFlow.textLength));
// no scrolling if any part of the selection is in view
var scrollRectLeft:Number = containerScrollRectLeft;
var scrollRectTop:Number = containerScrollRectTop;
var scrollRectRight:Number = containerScrollRectRight;
var scrollRectBottom:Number = containerScrollRectBottom;
if (flowComposer.damageAbsoluteStart <= endPos)
{
endPos = Math.min(begPos + 100, endPos + 1);
flowComposer.composeToPosition(endPos);
begLineIndex = flowComposer.findLineIndexAtPosition(begPos,(begPos == textFlow.textLength));
endLineIndex = flowComposer.findLineIndexAtPosition(endPos,(endPos == textFlow.textLength));
}
var rect:Rectangle = rangeToRectangle(begPos, endPos, begLineIndex, endLineIndex);
if (rect)
{
var lastVisibleLine:TextFlowLine;
var horizontalScrollOK:Boolean;
var verticalScrollOK:Boolean;
// vertical scroll
if (verticalText) {
// horizontal scroll
horizontalScrollOK = (rect.left < scrollRectLeft || rect.right > scrollRectLeft);
if (horizontalScrollOK)
{
if (rect.left < scrollRectLeft)
horizontalScrollPosition = rect.left + _compositionWidth;
if (rect.right > scrollRectRight)
horizontalScrollPosition = rect.right;
}
// If we're showing a blinking insertion point, we need to scroll far enough that
// we can see the insertion point, and it comes just after the character.
if (rect.top < scrollRectTop)
verticalScrollPosition = rect.top;
if (activePosition == anchorPosition)
rect.bottom += 2;
// vertical scroll
if (rect.bottom > scrollRectBottom)
verticalScrollPosition = rect.bottom - _compositionHeight;
}
else
{
// vertical scroll
// Don't scroll if the range extends both above and below
verticalScrollOK = (rect.top > scrollRectTop || rect.bottom < scrollRectBottom);
// vertical scroll
if (verticalScrollOK)
{
if (rect.top < scrollRectTop)
verticalScrollPosition = rect.top;
if (rect.bottom > scrollRectBottom)
verticalScrollPosition = rect.bottom - _compositionHeight;
}
// horizontal scroll
// If we're showing a blinking insertion point, we need to scroll far enough to see the
// insertion point, and it comes up to the right
if (activePosition == anchorPosition)
rect.right += 2;
// Don't scroll if range extends both to the left and right
horizontalScrollOK = (rect.left > scrollRectLeft || rect.right < scrollRectRight);
if (horizontalScrollOK && rect.left < scrollRectLeft)
horizontalScrollPosition = rect.left - _compositionWidth / 2;
if (horizontalScrollOK && rect.right > scrollRectRight)
horizontalScrollPosition = rect.right - _compositionWidth / 2;
}
}
}
private function rangeToRectangle(start:int, end:int, startLineIndex:int, endLineIndex:int):Rectangle
{
var bbox:Rectangle;
var blockProgression:String = effectiveBlockProgression;
var flowComposer:IFlowComposer = textFlow.flowComposer;
if (!container || !flowComposer)
return null;
if (startLineIndex == endLineIndex)
{
var line:TextFlowLine = flowComposer.getLineAt(startLineIndex);
if (line.isDamaged())
return null;
var textLine:TextLine = line.getTextLine(true);
var paragraphStart:int = line.paragraph.getAbsoluteStart();
var isTCY:Boolean = false;
if (blockProgression == BlockProgression.RL)
{
var leafElement:FlowLeafElement = _rootElement.getTextFlow().findLeaf(start);
isTCY = leafElement.getParentByType(flashx.textLayout.elements.TCYElement) != null;
}
var minAtomIndex:int = textLine.atomCount;
var maxAtomIndex:int = 0;
if (start == end)
{
minAtomIndex = textLine.getAtomIndexAtCharIndex(start - paragraphStart);
maxAtomIndex = minAtomIndex;
}
else
{
var atomIndex:int;
var lastPosition:int = end - paragraphStart;
for (var pos:int = start - paragraphStart; pos < lastPosition; ++pos)
{
atomIndex = textLine.getAtomIndexAtCharIndex(pos);
if (atomIndex < minAtomIndex)
minAtomIndex = atomIndex;
if (atomIndex > maxAtomIndex)
maxAtomIndex = atomIndex;
}
}
bbox = atomToRectangle(minAtomIndex, line, textLine, blockProgression, isTCY);
if (minAtomIndex != maxAtomIndex)
bbox = bbox.union(atomToRectangle(maxAtomIndex, line, textLine, blockProgression, isTCY));
}
else
{
bbox = new Rectangle(_contentLeft, _contentTop, _contentWidth, _contentHeight);
var startLine:TextFlowLine = flowComposer.getLineAt(startLineIndex);
var endLine:TextFlowLine = flowComposer.getLineAt(endLineIndex);
if (blockProgression == BlockProgression.TB)
{
bbox.top = startLine.y;
bbox.bottom = endLine.y + endLine.textHeight;
}
else
{
bbox.right = startLine.x + startLine.textHeight;
bbox.left = endLine.x;
}
}
return bbox;
}
private function atomToRectangle(atomIdx:int, line:TextFlowLine, textLine:TextLine, blockProgression:String, isTCY:Boolean):Rectangle
{
var atomBounds:Rectangle;
CONFIG::debug { assert(atomIdx > -1, "How'd we get here?"); }
if (atomIdx > -1)
atomBounds = textLine.getAtomBounds(atomIdx);
// special handling for TCY - no line height adjustments TCY is perpendicular to the height direction
if (blockProgression == BlockProgression.RL)
{
if (isTCY)
return new Rectangle(line.x+atomBounds.x,line.y+atomBounds.y,atomBounds.width,atomBounds.height);
return new Rectangle(line.x, line.y + atomBounds.y, line.height, atomBounds.height);
}
return new Rectangle(line.x + atomBounds.x, line.y-line.height+line.ascent, atomBounds.width, line.height+textLine.descent);
}
/**
* @private
*/
tlf_internal function resetColumnState():void
{
if (_rootElement)
_columnState.updateInputs(effectiveBlockProgression, _rootElement.computedFormat.direction, this, _compositionWidth, _compositionHeight);
}
/**
* Marks all the text in this container as needing composing.
*
* @includeExample examples\ContainerController_invalidateContentsExample.as -noswf
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
*/
public function invalidateContents():void
{
if (textFlow)
textFlow.damage(absoluteStart, Math.min(_textLength, 1), FlowDamageType.GEOMETRY, false);
}
/** @private */
private var _transparentBGX:Number;
/** @private */
private var _transparentBGY:Number;
/** @private */
private var _transparentBGWidth:Number;
/** @private */
private var _transparentBGHeight:Number;
/** No mouse clicks or moves will be generated for the container unless it has a background covering its area. Text Layout Framework
* wants those events so that clicking on a container will select the text in it. This code
* adds or updates (on size change) that background for Sprite containers only. This may cause clients problems
* - definitely no hits is a problem - add this code to explore the issues - expect feedback.
* We may have to make this configurable. @private */
tlf_internal function attachTransparentBackgroundForHit(justClear:Boolean):void
{
if ((_minListenersAttached || _mouseWheelListenerAttached) && attachTransparentBackground)
{
var s:Sprite = _container;
if (s)
{
if (justClear)
{
s.graphics.clear();
CONFIG::debug { Debugging.traceFTECall(null,s,"clearTransparentBackground()"); }
_transparentBGX = _transparentBGY = _transparentBGWidth = _transparentBGHeight = NaN;
}
else
{
var bgwidth:Number = _measureWidth ? _contentWidth : _compositionWidth;
var bgheight:Number = _measureHeight ? _contentHeight : _compositionHeight;
var adjustHorizontalScroll:Boolean = effectiveBlockProgression == BlockProgression.RL && _horizontalScrollPolicy != ScrollPolicy.OFF;
var bgx:Number = adjustHorizontalScroll ? _xScroll - bgwidth : _xScroll;
var bgy:Number = _yScroll;
CONFIG::debug { assert(!isNaN(bgx) && !isNaN(bgy) && !isNaN(bgwidth) && ! isNaN(bgheight),"Bad background rectangle"); }
if (bgx != _transparentBGX || bgy != _transparentBGY || bgwidth != _transparentBGWidth || bgheight != _transparentBGHeight)
{
s.graphics.clear();
CONFIG::debug { Debugging.traceFTECall(null,s,"clearTransparentBackground()"); }
if (bgwidth != 0 && bgheight != 0 )
{
s.graphics.beginFill(0, 0);
s.graphics.drawRect(bgx, bgy, bgwidth, bgheight);
s.graphics.endFill();
CONFIG::debug { Debugging.traceFTECall(null,s,"drawTransparentBackground",bgx, bgy, bgwidth, bgheight); }
}
_transparentBGX = bgx;
_transparentBGY = bgy;
_transparentBGWidth = bgwidth;
_transparentBGHeight = bgheight;
}
}
}
}
}
/** @private */
tlf_internal function interactionManagerChanged(newInteractionManager:ISelectionManager):void
{
if (!newInteractionManager)
detachContainer();
attachContainer();
checkScrollBounds();
// Need to forward whether the Ctrl key is needed to have
// hit-tested FlowElements emit events
if (_mouseEventManager)
_mouseEventManager.needsCtrlKey =
(interactionManager != null && interactionManager.editingMode == EditingMode.READ_WRITE);
// We have to tell the Player to bring up the soft keyboard on a
// keyboard edit gesture. Note that needsSoftKeyboard is new with 10.2, so
// have to check for it. This is a change to the container, but unavoidable
if (_container && Configuration.playerEnablesSpicyFeatures)
_container["needsSoftKeyboard"] = (interactionManager && interactionManager.editingMode == EditingMode.READ_WRITE);
}
//--------------------------------------------------------------------------
// Event handlers for editing
// Listeners are attached on first compose
//--------------------------------------------------------------------------
/** @private */
tlf_internal function attachContainer():void
{
if (!_minListenersAttached && textFlow && textFlow.interactionManager)
{
_minListenersAttached = true;
if (_container)
{
_container.addEventListener(FocusEvent.FOCUS_IN, requiredFocusInHandler);
_container.addEventListener(MouseEvent.MOUSE_OVER, requiredMouseOverHandler);
attachTransparentBackgroundForHit(false);
// If the container already has focus, we have to attach all listeners
if (_container.stage && _container.stage.focus == _container)
attachAllListeners();
}
}
}
/** @private */
tlf_internal function attachInteractionHandlers():void
{
// the receiver is either this or another class that is going to handle the methods.
var receiver:IInteractionEventHandler = getInteractionHandler();
// the required handlers are implemented here and forwarded to the receiver
_container.addEventListener(MouseEvent.MOUSE_DOWN, requiredMouseDownHandler);
_container.addEventListener(FocusEvent.FOCUS_OUT, requiredFocusOutHandler);
_container.addEventListener(MouseEvent.DOUBLE_CLICK, receiver.mouseDoubleClickHandler);
_container.addEventListener(Event.ACTIVATE, receiver.activateHandler);
_container.addEventListener(FocusEvent.MOUSE_FOCUS_CHANGE, receiver.focusChangeHandler);
_container.addEventListener(FocusEvent.KEY_FOCUS_CHANGE, receiver.focusChangeHandler);
_container.addEventListener(TextEvent.TEXT_INPUT, receiver.textInputHandler);
_container.addEventListener(MouseEvent.MOUSE_OUT, receiver.mouseOutHandler);
addMouseWheelListener();
_container.addEventListener(Event.DEACTIVATE, receiver.deactivateHandler);
// attach by literal event name to avoid Argo dependency
// normally this would be IMEEvent.START_COMPOSITION
_container.addEventListener("imeStartComposition", receiver.imeStartCompositionHandler);
if (_container.contextMenu)
_container.contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, receiver.menuSelectHandler);
_container.addEventListener(Event.COPY, receiver.editHandler);
_container.addEventListener(Event.SELECT_ALL, receiver.editHandler);
_container.addEventListener(Event.CUT, receiver.editHandler);
_container.addEventListener(Event.PASTE, receiver.editHandler);
_container.addEventListener(Event.CLEAR, receiver.editHandler);
}
/** @private */
tlf_internal function removeInteractionHandlers():void
{
var receiver:IInteractionEventHandler = getInteractionHandler();
_container.removeEventListener(MouseEvent.MOUSE_DOWN, requiredMouseDownHandler);
_container.removeEventListener(FocusEvent.FOCUS_OUT, requiredFocusOutHandler);
_container.removeEventListener(MouseEvent.DOUBLE_CLICK, receiver.mouseDoubleClickHandler);
_container.removeEventListener(Event.ACTIVATE, receiver.activateHandler);
_container.removeEventListener(FocusEvent.MOUSE_FOCUS_CHANGE, receiver.focusChangeHandler);
_container.removeEventListener(FocusEvent.KEY_FOCUS_CHANGE, receiver.focusChangeHandler);
_container.removeEventListener(TextEvent.TEXT_INPUT, receiver.textInputHandler);
_container.removeEventListener(MouseEvent.MOUSE_OUT, receiver.mouseOutHandler);
removeMouseWheelListener();
_container.removeEventListener(Event.DEACTIVATE, receiver.deactivateHandler);
// _container.removeEventListener(IMEEvent.IME_START_COMPOSITION, receiver.imeStartCompositionHandler);
// attach by literal event name to avoid Argo dependency
_container.removeEventListener("imeStartComposition", receiver.imeStartCompositionHandler);
if (_container.contextMenu)
_container.contextMenu.removeEventListener(ContextMenuEvent.MENU_SELECT, receiver.menuSelectHandler);
_container.removeEventListener(Event.COPY, receiver.editHandler);
_container.removeEventListener(Event.SELECT_ALL, receiver.editHandler);
_container.removeEventListener(Event.CUT, receiver.editHandler);
_container.removeEventListener(Event.PASTE, receiver.editHandler);
_container.removeEventListener(Event.CLEAR, receiver.editHandler);
clearSelectHandlers();
}
/** @private */
private function detachContainer():void
{
if (_minListenersAttached)
{
if (_container)
{
_container.removeEventListener(FocusEvent.FOCUS_IN, requiredFocusInHandler);
_container.removeEventListener(MouseEvent.MOUSE_OVER, requiredMouseOverHandler);
if(_allListenersAttached)
{
removeInteractionHandlers();
removeContextMenu();
attachTransparentBackgroundForHit(true);
_allListenersAttached = false;
}
}
_minListenersAttached = false;
}
removeMouseWheelListener();
}
private function attachAllListeners():void
{
if (!_allListenersAttached && textFlow && textFlow.interactionManager)
{
CONFIG::debug { assert(_minListenersAttached,"Bad call to attachAllListeners - won't detach"); }
_allListenersAttached = true;
if (_container)
{
attachContextMenu();
attachInteractionHandlers();
}
}
}
/** @private */
tlf_internal function addMouseWheelListener():void
{
if (!_mouseWheelListenerAttached)
{
_container.addEventListener(MouseEvent.MOUSE_WHEEL, getInteractionHandler().mouseWheelHandler);
_mouseWheelListenerAttached = true;
}
}
/** @private */
tlf_internal function removeMouseWheelListener():void
{
if (_mouseWheelListenerAttached)
{
_container.removeEventListener(MouseEvent.MOUSE_WHEEL, getInteractionHandler().mouseWheelHandler);
_mouseWheelListenerAttached = false;
}
}
/** @private */
tlf_internal function attachContextMenu():void
{ _container.contextMenu = createContextMenu(); }
/** @private */
tlf_internal function removeContextMenu():void
{ _container.contextMenu = null; }
/** @private
*
* Shared so that TextContainerManager can create the same ContextMenu.
*/
static tlf_internal function createDefaultContextMenu():ContextMenu
{
var contextMenu:ContextMenu = new ContextMenu();
contextMenu.clipboardMenu = true;
contextMenu.clipboardItems.clear = true;
contextMenu.clipboardItems.copy = true;
contextMenu.clipboardItems.cut = true;
contextMenu.clipboardItems.paste = true;
contextMenu.clipboardItems.selectAll = true;
return contextMenu;
}
/**
* Creates a context menu for the ContainerController. Use the methods of the ContextMenu class to
* add items to the menu.
* <p>You can override this method to define a custom context menu.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.ui.ContextMenu ContextMenu
*/
protected function createContextMenu():ContextMenu
{
return createDefaultContextMenu();
}
/** @private */
tlf_internal function scrollTimerHandler(event:Event):void
{
// trace("BEGIN scrollTimerHandler");
if (!_scrollTimer)
return;
// shut it down if not in this container
if (textFlow.interactionManager == null || textFlow.interactionManager.activePosition < absoluteStart || textFlow.interactionManager.activePosition > absoluteStart+textLength)
event = null;
// We're listening for MOUSE_UP so we can cancel autoscrolling
if (event is MouseEvent)
{
_scrollTimer.stop();
_scrollTimer.removeEventListener(TimerEvent.TIMER, scrollTimerHandler);
CONFIG::debug { assert(_container.stage == null || getContainerRoot() == event.currentTarget,"scrollTimerHandler bad target"); }
event.currentTarget.removeEventListener(MouseEvent.MOUSE_UP, scrollTimerHandler);
_scrollTimer = null;
}
else if (!event)
{
_scrollTimer.stop();
_scrollTimer.removeEventListener(TimerEvent.TIMER, scrollTimerHandler);
if (getContainerRoot())
getContainerRoot().removeEventListener( MouseEvent.MOUSE_UP, scrollTimerHandler);
_scrollTimer = null;
}
else if (_container.stage)
{
var containerPoint:Point = new Point(_container.stage.mouseX, _container.stage.mouseY);
containerPoint = _container.globalToLocal(containerPoint);
var scrollChange:int = autoScrollIfNecessaryInternal(containerPoint);
if (scrollChange != 0 && interactionManager) // force selection update if we actually scrolled and we have a selection manager
{
var mouseEvent:MouseEvent = new PsuedoMouseEvent(MouseEvent.MOUSE_MOVE,false,false,_container.stage.mouseX, _container.stage.mouseY,_container.stage,false,false,false,true);
var stashedScrollTimer:Timer = _scrollTimer;
try
{
_scrollTimer = null;
interactionManager.mouseMoveHandler(mouseEvent);
}
catch (e:Error)
{
throw(e);
}
finally
{
_scrollTimer = stashedScrollTimer;
}
}
}
// trace("AFTER scrollTimerHandler");
}
/**
* Handle a scroll event during a "drag" selection.
*
* @param mouseX The horizontal position of the mouse cursor on the stage.
* @param mouseY The vertical position of the mouse cursor on the stage.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function autoScrollIfNecessary(mouseX:int, mouseY:int):void
{
if (flowComposer.getControllerAt(flowComposer.numControllers-1) != this)
{
var verticalText:Boolean = (effectiveBlockProgression == BlockProgression.RL);
var lastController:ContainerController = flowComposer.getControllerAt(flowComposer.numControllers - 1);
if ((verticalText && _horizontalScrollPolicy == ScrollPolicy.OFF) ||
(!verticalText && _verticalScrollPolicy == ScrollPolicy.OFF))
return;
var r:Rectangle = lastController.container.getBounds(_container.stage);
if (verticalText)
{
if (mouseY >= r.top && mouseY <= r.bottom)
lastController.autoScrollIfNecessary(mouseX, mouseY);
}
else
{
if (mouseX >= r.left && mouseX <= r.right)
lastController.autoScrollIfNecessary(mouseX, mouseY);
}
}
// even if not the last container - may scroll if there are explicit linebreaks
if (!_hasScrollRect)
return;
var containerPoint:Point = new Point(mouseX, mouseY);
containerPoint = _container.globalToLocal(containerPoint);
autoScrollIfNecessaryInternal(containerPoint);
}
/**
* Handle a scroll event during a "drag" selection.
*
* @param mouseX The horizontal position of the mouse cursor on the stage.
* @param mouseY The vertical position of the mouse cursor on the stage.
* @returns positive number if scroll went forward in reading order, negative number if it went backwards, and 0 if no scroll
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
private function autoScrollIfNecessaryInternal(extreme:Point):int
{
CONFIG::debug
{
assert(_hasScrollRect, "internal scrolling function called on non-scrollable container");
}
var scrollDirection:int = 0;
if (extreme.y - containerScrollRectBottom > 0) {
verticalScrollPosition += textFlow.configuration.scrollDragPixels;
scrollDirection = 1;
}
else if (extreme.y - containerScrollRectTop < 0) {
verticalScrollPosition -= textFlow.configuration.scrollDragPixels;
scrollDirection = -1;
}
if (extreme.x - containerScrollRectRight > 0) {
horizontalScrollPosition += textFlow.configuration.scrollDragPixels;
scrollDirection = -1;
}
else if (extreme.x - containerScrollRectLeft < 0) {
horizontalScrollPosition -= textFlow.configuration.scrollDragPixels;
scrollDirection = 1;
}
// we need a timer so that the mouse doesn't have to continue moving when the mouse is outside the content area
if (scrollDirection != 0 && !_scrollTimer)
{
_scrollTimer = new Timer(textFlow.configuration.scrollDragDelay); // 35 ms is the default auto-repeat interval for ScrollBars.
_scrollTimer.addEventListener(TimerEvent.TIMER, scrollTimerHandler, false, 0, true);
if (getContainerRoot())
{
getContainerRoot().addEventListener(MouseEvent.MOUSE_UP, scrollTimerHandler, false, 0, true);
beginMouseCapture(); // TELL CLIENTS WE WANT mouseUpSomewhere events
}
_scrollTimer.start();
}
return scrollDirection;
}
/** @private */
tlf_internal function getFirstVisibleLine():TextFlowLine
{ return _shapeChildren.length ? _shapeChildren[0].userData : null; }
/** @private */
tlf_internal function getLastVisibleLine():TextFlowLine
{ return _shapeChildren.length ? _shapeChildren[_shapeChildren.length-1].userData : null; }
/**
* Figure out the scroll distance required to scroll up or down by the specified number of lines.
* Negative numbers scroll upward, bringing more of the top of the TextFlow into view. Positive numbers
* scroll downward, bringing the next line from the bottom into full view.
*
* <p>When scrolling up, for example, the method makes the next line fully visible. If the next line is partially
* obscured and the number of lines specified is 1, the partially obscured line becomes fully visible.</p>
*
* @param nLines The number of lines to scroll.
*
* @return the delta amount of space to scroll
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function getScrollDelta(numLines:int):Number
{
var flowComposer:IFlowComposer = textFlow.flowComposer;
if (flowComposer.numLines == 0)
return 0;
// Now we want to calculate the top & bottom lines within the scrollRect. It's ok if they're just partially
// visible. Once we determine these lines, we figure out how much we need to scroll in order to bring the
// lines completely into view.
var firstVisibleLine:TextFlowLine = getFirstVisibleLine();
if (!firstVisibleLine)
return 0;
var lastVisibleLine:TextFlowLine = getLastVisibleLine();
CONFIG::debug { assert(lastVisibleLine != null,"Expect lastVisibleLine when there is a firstVisibleLine"); }
// trace(" // findFirstAndLastVisibleLine ",flowComposer.findLineIndexAtPosition(firstVisibleLine.absoluteStart),flowComposer.findLineIndexAtPosition(lastVisibleLine.absoluteStart));
var newLineIndex:int;
var lineIndex:int;
if (numLines > 0)
{
lineIndex = flowComposer.findLineIndexAtPosition(lastVisibleLine.absoluteStart);
// If the last visible line is only partly visible, don't count it as visible. But make sure it overlaps by
// at least two pixels, otherwise it doesn't look like its clipped.
var lastTextLine:TextLine = lastVisibleLine.getTextLine(true);
if (effectiveBlockProgression == BlockProgression.TB)
{
if ((lastTextLine.y + lastTextLine.descent) - containerScrollRectBottom > 2)
--lineIndex;
}
else if (containerScrollRectLeft - (lastTextLine.x - lastTextLine.descent) > 2)
--lineIndex;
// if we hit the end, force composition so that we get more lines - I picked a random amount to scroll forward, if its not enough, it will keep going
while (lineIndex + numLines > flowComposer.numLines - 1 && flowComposer.damageAbsoluteStart < textFlow.textLength)
{
var previousDamageStart:int = flowComposer.damageAbsoluteStart;
flowComposer.composeToPosition(flowComposer.damageAbsoluteStart + 1000);
// if we've made no progress, abort
if (flowComposer.damageAbsoluteStart == previousDamageStart)
return 0;
}
newLineIndex = Math.min(flowComposer.numLines-1, lineIndex + numLines);
}
if (numLines < 0)
{
lineIndex = flowComposer.findLineIndexAtPosition(firstVisibleLine.absoluteStart);
// If the first visible line is only partly visible, don't count it as visible. But make sure it overlaps by
// at least two pixels, otherwise it doesn't look like its clipped.
if (effectiveBlockProgression == BlockProgression.TB)
{
if (firstVisibleLine.y + 2 < containerScrollRectTop)
++lineIndex;
}
else if (firstVisibleLine.x + firstVisibleLine.ascent > containerScrollRectRight + 2)
++lineIndex;
newLineIndex = Math.max(0, lineIndex + numLines);
}
var line:TextFlowLine = flowComposer.getLineAt(newLineIndex);
if (line.absoluteStart < absoluteStart) // don't scroll past the start of this controller -- previous text is in previous controller
return 0;
if (line.validity != TextLineValidity.VALID)
{
var leaf:FlowLeafElement = textFlow.findLeaf(line.absoluteStart);
var paragraph:ParagraphElement = leaf.getParagraph();
textFlow.flowComposer.composeToPosition(paragraph.getAbsoluteStart() + paragraph.textLength);
line = flowComposer.getLineAt(newLineIndex);
CONFIG::debug { assert(line.validity == TextLineValidity.VALID, "expected valid line after recomposing"); }
}
var verticalText:Boolean = effectiveBlockProgression == BlockProgression.RL;
var newScrollPosition:Number;
if (verticalText)
{
newScrollPosition = numLines < 0 ? line.x + line.textHeight : line.x - line.descent + _compositionWidth;
return newScrollPosition - horizontalScrollPosition;
}
newScrollPosition = numLines < 0 ? line.y : line.y + line.textHeight - _compositionHeight;
return newScrollPosition - verticalScrollPosition;
}
/**
* Processes the <code>MouseEvent.MOUSE_OVER</code> event when the client manages events.
*
* @param event The MouseEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\ContainerController_mouseOverHandlerExample.as -noswf
*
* @see flash.events.MouseEvent#MOUSE_OVER MouseEvent.MOUSE_OVER
*/
public function mouseOverHandler(event:MouseEvent):void
{
if (interactionManager && !event.isDefaultPrevented())
interactionManager.mouseOverHandler(event);
}
/** @private Does required mouseOver handling. Calls mouseOverHandler. @see #mouseOverHandler */
tlf_internal function requiredMouseOverHandler(event:MouseEvent):void
{
attachAllListeners();
getInteractionHandler().mouseOverHandler(event);
}
/** Processes the <code>MouseEvent.MOUSE_OUT</code> event when the client manages events.
*
* @param event The MouseEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.MouseEvent#MOUSE_OUT MouseEvent.MOUSE_OUT
*/
public function mouseOutHandler(event:MouseEvent):void
{
if (interactionManager && !event.isDefaultPrevented())
interactionManager.mouseOutHandler(event);
}
/** Processes the <code>MouseEvent.MOUSE_WHEEL</code> event when the client manages events.
*
* @param event The MouseEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.MouseEvent#MOUSE_WHEEL MouseEvent.MOUSE_WHEEL
*/
public function mouseWheelHandler(event:MouseEvent):void
{
if (event.isDefaultPrevented())
return;
// Do the scroll and call preventDefault only if the there is enough text to scroll. Otherwise
// we let the event bubble up and cause scrolling at the next level up in the client's container hierarchy.
var verticalText:Boolean = effectiveBlockProgression == BlockProgression.RL;
if (verticalText)
{
if (contentWidth > _compositionWidth && !_measureWidth)
{
horizontalScrollPosition += event.delta * textFlow.configuration.scrollMouseWheelMultiplier;
event.preventDefault();
}
}
else if (contentHeight > _compositionHeight && !_measureHeight)
{
verticalScrollPosition -= event.delta * textFlow.configuration.scrollMouseWheelMultiplier;
event.preventDefault();
}
}
/** Processes the <code>MouseEvent.MOUSE_DOWN</code> event when the client manages events.
*
* @param event The MouseEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.MouseEvent#MOUSE_DOWN MouseEvent.MOUSE_DOWN
*/
public function mouseDownHandler(event:MouseEvent):void
{
if (interactionManager && !event.isDefaultPrevented())
{
interactionManager.mouseDownHandler(event);
// grab the focus - alternative is to listen to keyevents on the Application
// is this necessary?
if ( interactionManager.hasSelection())
setFocus();
}
}
/** @private Does required mouseDown handling. Calls mouseDownHandler. @see #mouseDownHandler */
tlf_internal function requiredMouseDownHandler(event:MouseEvent):void
{
if (!_selectListenersAttached)
{
var containerRoot:DisplayObject = getContainerRoot();
if (containerRoot)
{
containerRoot.addEventListener(MouseEvent.MOUSE_MOVE, rootMouseMoveHandler, false, 0, true);
containerRoot.addEventListener(MouseEvent.MOUSE_UP, rootMouseUpHandler, false, 0, true);
beginMouseCapture(); // TELL CLIENTS THAT WE WANT moueUpSomewhere EVENTS
_selectListenersAttached = true;
}
}
getInteractionHandler().mouseDownHandler(event);
}
/**
* Processes the <code>MouseEvent.MOUSE_UP</code> event when the client manages events.
*
* @param event The MouseEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.MouseEvent#MOUSE_UP MouseEvent.MOUSE_UP
*
*/
public function mouseUpHandler(event:MouseEvent):void
{
if (interactionManager && event && !event.isDefaultPrevented())
{
interactionManager.mouseUpHandler(event);
}
}
/** @private */
tlf_internal function rootMouseUpHandler(event:MouseEvent):void
{
clearSelectHandlers();
getInteractionHandler().mouseUpHandler(event);
}
private function clearSelectHandlers():void
{
if (_selectListenersAttached)
{
CONFIG::debug { assert(getContainerRoot() != null,"No container root"); }
getContainerRoot().removeEventListener(MouseEvent.MOUSE_MOVE, rootMouseMoveHandler);
getContainerRoot().removeEventListener(MouseEvent.MOUSE_UP, rootMouseUpHandler);
endMouseCapture(); // TELL CLIENTS WE NO LONGER WANT mouseUpSomewhere EVENTS
_selectListenersAttached = false;
}
}
/**
* Called to request clients to begin the forwarding of mouseup and mousemove events from outside a security sandbox.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
*/
public function beginMouseCapture():void
{
// trace("BEGIN MOUSECAPTURE");
var sandboxManager:ISandboxSupport = getInteractionHandler() as ISandboxSupport
if (sandboxManager && sandboxManager != this)
sandboxManager.beginMouseCapture();
}
/**
* Called to inform clients that the the forwarding of mouseup and mousemove events from outside a security sandbox is no longer needed.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
*/
public function endMouseCapture():void
{
// trace("END MOUSECAPTURE");
var sandboxManager:ISandboxSupport = getInteractionHandler() as ISandboxSupport
if (sandboxManager && sandboxManager != this)
sandboxManager.endMouseCapture();
}
/** Client call to forward a mouseUp event from outside a security sandbox. Coordinates of the mouse up are not needed.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
*/
public function mouseUpSomewhere(event:Event):void
{
rootMouseUpHandler(null);
scrollTimerHandler(null);
}
/** Client call to forward a mouseMove event from outside a security sandbox. Coordinates of the mouse move are not needed.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
*/
public function mouseMoveSomewhere(event:Event):void
{
return; // do nothing right now
}
// What'd I hit???
private function hitOnMyFlowExceptLastContainer(event:MouseEvent):Boolean
{
if (event.target is TextLine)
{
var tfl:TextFlowLine = TextLine(event.target).userData as TextFlowLine;
if (tfl)
{
var para:ParagraphElement = tfl.paragraph;
if(para.getTextFlow() == textFlow)
return true;
}
}
else if (event.target is Sprite)
{
// skip the last container in the chain
for (var idx:int = 0; idx < textFlow.flowComposer.numControllers-1; idx++)
if (textFlow.flowComposer.getControllerAt(idx).container == event.target)
return true;
}
return false;
}
/**
* Processes the <code>MouseEvent.MOUSE_MOVE</code> event when the client manages events.
*
* @param event The MouseEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.MouseEvent#MOUSE_MOVE MouseEvent.MOUSE_MOVE
*/
public function mouseMoveHandler(event:MouseEvent):void
{
if (interactionManager && !event.isDefaultPrevented())
{
// only autoscroll if we haven't hit something on the stage related to this particular TextFlow
if (event.buttonDown && !hitOnMyFlowExceptLastContainer(event))
autoScrollIfNecessary(event.stageX, event.stageY);
interactionManager.mouseMoveHandler(event);
}
}
/** @private */
tlf_internal function rootMouseMoveHandler(event:MouseEvent):void
{
getInteractionHandler().mouseMoveHandler(event);
}
/** Processes the <code>MouseEvent.DOUBLE_CLICK</code> event when the client manages events.
*
* @param event The MouseEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\ContainerController_mouseDoubleClickHandlerExample.as -noswf
*
* @see flash.events.MouseEvent#DOUBLE_CLICK MouseEvent.DOUBLE_CLICK
*/
public function mouseDoubleClickHandler(event:MouseEvent):void
{
if (interactionManager && !event.isDefaultPrevented())
{
interactionManager.mouseDoubleClickHandler(event);
// grab the focus - alternative is to listen to keyevents on the Application
// is this necessary?
if ( interactionManager.hasSelection())
setFocus();
}
}
/** Give focus to the text container. @private */
tlf_internal function setFocus():void
{
//trace("setFocus container", id);
if (_container.stage)
_container.stage.focus = _container;
}
/** @private */
tlf_internal function getContainerController(container:DisplayObject):ContainerController
{
try
{
while (container)
{
var flowComposer:IFlowComposer = flowComposer;
for (var i:int = 0; i < flowComposer.numControllers; i++)
{
var controller:ContainerController = flowComposer.getControllerAt(i);
if (controller.container == container)
return controller;
}
container = container.parent;
}
}
catch (e:Error)
{ }
return null;
}
/**
* Processes the <code>FocusEvent.KEY_FOCUS_CHANGE</code> and <code>FocusEvent.MOUSE_FOCUS_CHANGE</code> events
* when the client manages events.
*
* @param event The FocusEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.FocusEvent#KEY_FOCUS_CHANGE FocusEvent.KEY_FOCUS_CHANGE
* @see flash.events.FocusEvent#MOUSE_FOCUS_CHANGE FocusEvent.MOUSE_FOCUS_CHANGE
*/
public function focusChangeHandler(event:FocusEvent):void
{
// Figure out which controllers, if any, correspond to the DisplayObjects passed in the event.
// Disallow the focus change if it comes back to this controller again -- this prevents
// a focusOut followed by a focusIn, which we would otherwise get after clicking in the
// container that already has focus.
// This is the controller that currently has the focus
var focusController:ContainerController = getContainerController(DisplayObject(event.target));
// This is the controller that is about to get the focus
var newFocusController:ContainerController = getContainerController(event.relatedObject);
/*trace("focusChange from controller",
focusController is ContainerControllerBase ? ContainerControllerBase(focusController).id : "unknownType",
newFocusController is ContainerControllerBase ? ContainerControllerBase(newFocusController).id : "unknownType");
*/
if (newFocusController == focusController)
{
// trace("prevent focus change");
event.preventDefault();
}
}
/** Processes the <code>FocusEvent.FOCUS_IN</code> event when the client manages events.
*
* @param event The FocusEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\ContainerController_focusInHandlerExample.as -noswf
*
* @see flash.events.FocusEvent#FOCUS_IN FocusEvent.FOCUS_IN
*/
public function focusInHandler(event:FocusEvent):void
{
var blinkRate:int = 0;
// trace("container", id, "focusIn");
if (interactionManager)
{
interactionManager.focusInHandler(event);
if (interactionManager.editingMode == EditingMode.READ_WRITE)
blinkRate = interactionManager.focusedSelectionFormat.pointBlinkRate;
}
setBlinkInterval(blinkRate);
}
/** @private - does whatever focusIn handling is required and cannot be overridden */
tlf_internal function requiredFocusInHandler(event:FocusEvent):void
{
attachAllListeners();
// trace("ContainerController requiredFocusInHandler adding key handlers");
_container.addEventListener(KeyboardEvent.KEY_DOWN, getInteractionHandler().keyDownHandler);
_container.addEventListener(KeyboardEvent.KEY_UP, getInteractionHandler().keyUpHandler);
_container.addEventListener(FocusEvent.KEY_FOCUS_CHANGE, getInteractionHandler().keyFocusChangeHandler);
if (Configuration.playerEnablesSpicyFeatures && Configuration.hasTouchScreen)
_container.addEventListener("softKeyboardActivating", getInteractionHandler().softKeyboardActivatingHandler);
getInteractionHandler().focusInHandler(event);
}
/** Processes the <code>FocusEvent.FOCUS_OUT</code> event when the client manages events.
*
* @param event The FocusEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.FocusEvent#FOCUS_OUT FocusEvent.FOCUS_OUT
*/
public function focusOutHandler(event:FocusEvent):void
{
if (interactionManager)
{
interactionManager.focusOutHandler(event);
setBlinkInterval(interactionManager.unfocusedSelectionFormat.pointBlinkRate);
}
else
setBlinkInterval(0);
}
/** @private Does required focusOut handling. Calls focusOutHandler. @see #focusOutHandler */
tlf_internal function requiredFocusOutHandler(event:FocusEvent):void
{
// trace("ContainerController requiredFocusOutHandler removing key handlers");
_container.removeEventListener(KeyboardEvent.KEY_DOWN, getInteractionHandler().keyDownHandler);
_container.removeEventListener(KeyboardEvent.KEY_UP, getInteractionHandler().keyUpHandler);
_container.removeEventListener(FocusEvent.KEY_FOCUS_CHANGE, getInteractionHandler().keyFocusChangeHandler);
if (Configuration.playerEnablesSpicyFeatures && Configuration.hasTouchScreen)
_container.removeEventListener("softKeyboardActivating", getInteractionHandler().softKeyboardActivatingHandler);
getInteractionHandler().focusOutHandler(event);
}
/** Processes the <code>Event.ACTIVATE</code> event when the client manages events.
*
* @param event The Event object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\ContainerController_activateHandlerExample.as -noswf
*
* @see flash.events.Event#ACTIVATE Event.ACTIVATE
*/
public function activateHandler(event:Event):void
{
if (interactionManager)
interactionManager.activateHandler(event);
}
/** Processes the <code>Event.DEACTIVATE</code> event when the client manages events.
*
* @param event The Event object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.Event#DEACTIVATE Event.DEACTIVATE
*/
public function deactivateHandler(event:Event):void
{
if (interactionManager)
interactionManager.deactivateHandler(event);
}
/** Processes the <code>KeyboardEvent.KEY_DOWN</code> event when the client manages events.
*
* @param The KeyboardEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.KeyboardEvent#KEY_DOWN KeyboardEvent.KEY_DOWN
*/
public function keyDownHandler(event:KeyboardEvent):void
{
if (interactionManager && !event.isDefaultPrevented())
interactionManager.keyDownHandler(event);
}
/** Processes the <code>Keyboard.KEY_UP</code> event when the client manages events.
*
* @param event The KeyboardEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\ContainerController_keyUpHandlerExample.as -noswf
*
* @see flash.events.KeyboardEvent#KEY_UP KeyboardEvent.KEY_UP
*/
public function keyUpHandler(event:KeyboardEvent):void
{
if (interactionManager && !event.isDefaultPrevented())
interactionManager.keyUpHandler(event);
}
/** Processes the <code>FocusEvent.KEY_FOCUS_CHANGE</code> event when the client manages events.
*
* @param event The FocusEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.FocusEvent#KEY_FOCUS_CHANGE FocusEvent.KEY_FOCUS_CHANGE
*/
public function keyFocusChangeHandler(event:FocusEvent):void
{
if (interactionManager)
interactionManager.keyFocusChangeHandler(event);
}
/** Processes the <code>TextEvent.TEXT_INPUT</code> event when the client manages events.
*
* @param event The TextEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\ContainerController_textInputHandlerExample.as -noswf
*
* @see flash.events.TextEvent#TEXT_INPUT TextEvent.TEXT_INPUT
*/
public function textInputHandler(event:TextEvent):void
{
if (interactionManager && !event.isDefaultPrevented())
interactionManager.textInputHandler(event);
}
/** Processes the <code>SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATING</code> event when the client manages events.
*
* @param event The SoftKeyboardEvent object.
*
* @playerversion Flash 10.2
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.SoftKeyboardEvent#SOFT_KEYBOARD_ACTIVATING SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATING
*/
public function softKeyboardActivatingHandler(event:Event):void
{
if (interactionManager)
interactionManager.softKeyboardActivatingHandler(event);
}
/** Processes the <code>IMEEvent.IME_START_COMPOSITION</code> event when the client manages events.
*
* @param event The IMEEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.events.IMEEvent.IME_START_COMPOSITION
*/
public function imeStartCompositionHandler(event:IMEEvent):void
{
if (interactionManager)
interactionManager.imeStartCompositionHandler(event);
}
/**
* Processes the <code>ContextMenuEvent.MENU_SELECT</code> event when the client manages events.
*
* @param The ContextMenuEvent object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\ContainerController_menuSelectHandlerExample.as -noswf
*
* @see flash.events.ContextMenuEvent#MENU_SELECT ContextMenuEvent.MENU_SELECT
*/
public function menuSelectHandler(event:ContextMenuEvent):void
{
if (interactionManager)
{
interactionManager.menuSelectHandler(event);
}
else
{
var contextMenu:ContextMenu = _container.contextMenu;
if (contextMenu)
{
var cbItems:ContextMenuClipboardItems = contextMenu.clipboardItems;
cbItems.copy = false;
cbItems.cut = false;
cbItems.paste = false;
cbItems.selectAll = false;
cbItems.clear = false;
}
}
}
/**
* Processes an edit event (CUT, COPY, PASTE, SELECT_ALL) when the client manages events.
*
* @param The Event object.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @includeExample examples\ContainerController_editHandlerExample.as -noswf
*
* @see flash.events.Event Event
*/
public function editHandler(event:Event):void
{
if (interactionManager && !event.isDefaultPrevented())
interactionManager.editHandler(event);
// re-enable context menu so following keyboard shortcuts will work
var contextMenu:ContextMenu = _container.contextMenu;
if (contextMenu)
{
contextMenu.clipboardItems.clear = true;
contextMenu.clipboardItems.copy = true;
contextMenu.clipboardItems.cut = true;
contextMenu.clipboardItems.paste = true;
contextMenu.clipboardItems.selectAll = true;
}
}
/**
* Sets the range of selected text in a component implementing ITextSupport.
* If either of the arguments is out of bounds the selection should not be changed.
* Components which wish to support inline IME should call into this method.
*
* @param anchorIndex The zero-based index value of the character at the anchor end of the selection
*
* @param activeIndex The zero-based index value of the character at the active end of the selection.
*
* @playerversion Flash 10.0
* @langversion 3.0
*/
public function selectRange(anchorIndex:int, activeIndex:int):void
{
if(interactionManager && interactionManager.editingMode != EditingMode.READ_ONLY)
{
interactionManager.selectRange(anchorIndex, activeIndex);
}
}
//--------------------------------------------------------------------------
//
// Cursor blinking code
//
//--------------------------------------------------------------------------
// TODO Want to evaluate whether there's a cleaner way to do this
private var blinkTimer:Timer;
private var blinkObject:DisplayObject;
/**
* Starts a DisplayObject cursor blinking by changing its alpha value
* over time.
*
* @param obj The DisplayObject to use as the cursor.
*
*/
private function startBlinkingCursor(obj:DisplayObject, blinkInterval:int):void
{
if (!blinkTimer)
blinkTimer = new Timer(blinkInterval,0);
blinkObject = obj;
blinkTimer.addEventListener(TimerEvent.TIMER,blinkTimerHandler, false, 0, true);
blinkTimer.start();
}
/**
* Stops cursor from blinking
* @private
*/
protected function stopBlinkingCursor():void
{
if (blinkTimer)
blinkTimer.stop();
blinkObject = null;
}
private function blinkTimerHandler(event:TimerEvent):void
{
blinkObject.alpha = (blinkObject.alpha == 1.0) ? 0.0 : 1.0;
}
/**
* Set the blink interval.
*
* @param intervalMS - number of microseconds between blinks
* @private
*/
protected function setBlinkInterval(intervalMS:int):void
{
var blinkInterval:int = intervalMS;
if (blinkInterval == 0)
{
// turn off the blinking
if (blinkTimer)
blinkTimer.stop();
if (blinkObject)
blinkObject.alpha = 1.0;
}
else if (blinkTimer)
{
blinkTimer.delay = blinkInterval;
if (blinkObject)
blinkTimer.start();
}
}
/** Draw the caret for a selection
* @param x x-location where caret is drawn
* @param y y-location where caret is drawn
* @param w width of caret
* @param h height of caret
* @private
*/
tlf_internal function drawPointSelection(selFormat:SelectionFormat, x:Number,y:Number,w:Number,h:Number):void
{
var selObj:Shape = new Shape();
// Oh, this is ugly. If we are in right aligned text, and there is no padding, and the scrollRect is set,
// then in an empty line (or if the point is at the right edge of the line), the blinking cursor is not
// visible because it is clipped out. Move it in so we can see it.
if (_hasScrollRect)
{
if (effectiveBlockProgression == BlockProgression.TB)
{
if (x >= containerScrollRectRight)
x -= w;
}
else if (y >= containerScrollRectBottom)
y -= h;
}
CONFIG::debug { assert(interactionManager.activePosition == interactionManager.anchorPosition,"bad call to drawPointSelection"); }
selObj.graphics.beginFill(selFormat.pointColor);
// pixel snap - works for unscaled text - scaled text will have to accept fuzzy cursors
selObj.graphics.drawRect(int(x),int(y),w,h);
selObj.graphics.endFill();
// make it blink. But we never blink unless the text is r/w
if (selFormat.pointBlinkRate != 0 && interactionManager.editingMode == EditingMode.READ_WRITE)
startBlinkingCursor(selObj, selFormat.pointBlinkRate);
addSelectionChild(selObj);
}
/** Add selection shapes to the displaylist. @private */
tlf_internal function addSelectionShapes(selFormat:SelectionFormat, selectionAbsoluteStart:int, selectionAbsoluteEnd:int): void
{
if (!interactionManager || _textLength == 0 || selectionAbsoluteStart == -1 || selectionAbsoluteEnd == -1)
return;
var prevLine:TextFlowLine;
var nextLine:TextFlowLine;
if (selectionAbsoluteStart != selectionAbsoluteEnd)
{
// adjust selectionAbsoluteStart and selectionAbsoluteEnd to be within this controller
var absoluteControllerStart:int = this.absoluteStart;
var absoluteControllerEnd:int = this.absoluteStart+this.textLength;
if (selectionAbsoluteStart < absoluteControllerStart)
selectionAbsoluteStart = absoluteControllerStart;
else if (selectionAbsoluteStart >= absoluteControllerEnd)
return; // nothing to do
// backup one so that
if (selectionAbsoluteEnd > absoluteControllerEnd)
selectionAbsoluteEnd = absoluteControllerEnd;
else if (selectionAbsoluteEnd < absoluteControllerStart)
return; // nothing to do
CONFIG::debug { assert(selectionAbsoluteStart <= selectionAbsoluteEnd,"addSelectionShapes: bad range"); }
CONFIG::debug { assert(selectionAbsoluteStart >= absoluteControllerStart,"addSelectionShapes: bad range"); }
CONFIG::debug { assert(selectionAbsoluteEnd <= absoluteControllerEnd,"addSelectionShapes: bad range"); }
var begLine:int = flowComposer.findLineIndexAtPosition(selectionAbsoluteStart);
var endLine:int = selectionAbsoluteStart == selectionAbsoluteEnd ? begLine : flowComposer.findLineIndexAtPosition(selectionAbsoluteEnd);
// watch for going past the end
if (endLine >= flowComposer.numLines)
endLine = flowComposer.numLines-1;
var selObj:Shape = new Shape();
prevLine = begLine ? flowComposer.getLineAt(begLine-1) : null;
var line:TextFlowLine = flowComposer.getLineAt(begLine);
for (var idx:int = begLine; idx <= endLine; idx++)
{
nextLine = idx != flowComposer.numLines - 1 ? flowComposer.getLineAt(idx+1) : null;
line.hiliteBlockSelection(selObj, selFormat, this._container,
selectionAbsoluteStart < line.absoluteStart ? line.absoluteStart : selectionAbsoluteStart,
selectionAbsoluteEnd > line.absoluteStart+line.textLength ? line.absoluteStart+line.textLength : selectionAbsoluteEnd, prevLine, nextLine);
var temp:TextFlowLine = line;
line = nextLine;
prevLine = temp;
}
addSelectionChild(selObj);
}
else
{
var lineIdx:int = flowComposer.findLineIndexAtPosition(selectionAbsoluteStart);
// TODO: there is ambiguity - are we at the end of the currentLine or the beginning of the next one?
// however must stick to the end of the last line
if (lineIdx == flowComposer.numLines)
lineIdx--;
if (flowComposer.getLineAt(lineIdx).controller == this)
{
prevLine = lineIdx != 0 ? flowComposer.getLineAt(lineIdx-1) : null;
nextLine = lineIdx != flowComposer.numLines-1 ? flowComposer.getLineAt(lineIdx+1) : null
flowComposer.getLineAt(lineIdx).hilitePointSelection(selFormat, selectionAbsoluteStart, this._container, prevLine, nextLine);
}
}
}
/** Remove all selection shapes. @private */
tlf_internal function clearSelectionShapes(): void
{
stopBlinkingCursor();
var selectionSprite:DisplayObjectContainer = getSelectionSprite(false);
if (selectionSprite != null)
{
if (selectionSprite.parent)
removeSelectionContainer(selectionSprite);
while (selectionSprite.numChildren > 0)
selectionSprite.removeChildAt(0);
return;
}
}
/** Add a selection child. @private */
tlf_internal function addSelectionChild(child:DisplayObject):void
{
// If there's no selectionSprite on this controller, we use the parent's.
// That means we have to translate the coordinates.
// TODO: this only supports one level of ntesting
var selectionSprite:DisplayObjectContainer = getSelectionSprite(true);
if (selectionSprite == null)
{
return;
}
var selFormat:SelectionFormat = interactionManager.currentSelectionFormat;
var curBlendMode:String = (interactionManager.activePosition == interactionManager.anchorPosition) ? selFormat.pointBlendMode : selFormat.rangeBlendMode;
var curAlpha:Number = (interactionManager.activePosition == interactionManager.anchorPosition) ? selFormat.pointAlpha : selFormat.rangeAlpha;
if (selectionSprite.blendMode != curBlendMode)
selectionSprite.blendMode = curBlendMode;
if (selectionSprite.alpha != curAlpha)
selectionSprite.alpha = curAlpha;
if (selectionSprite.numChildren == 0)
addSelectionContainer(selectionSprite);
selectionSprite.addChild(child);
}
/** Test for a selection child. @private */
tlf_internal function containsSelectionChild(child:DisplayObject):Boolean
{
var selectionSprite:DisplayObjectContainer = getSelectionSprite(false);
if (selectionSprite == null)
{
return false;
}
return selectionSprite.contains(child);
}
/** @private */
tlf_internal function getBackgroundShape():Shape
{
if(!_backgroundShape)
{
_backgroundShape = new Shape();
addBackgroundShape(_backgroundShape);
}
return _backgroundShape;
}
/* CONFIG::debug private function containsFloats(textFlow:TextFlow):Boolean
{
if (textFlow)
for (var leaf:FlowLeafElement = textFlow.getFirstLeaf(); leaf != null; leaf = leaf.getNextLeaf())
if (leaf is InlineGraphicElement && InlineGraphicElement(leaf).float != Float.NONE)
return true;
return false;
} */
/** @private */
tlf_internal function getEffectivePaddingLeft():Number
{ return computedFormat.paddingLeft == FormatValue.AUTO ? computedFormat.borderLeftWidth + computedFormat.marginLeft : computedFormat.paddingLeft + computedFormat.borderLeftWidth + computedFormat.marginLeft; }
/** @private */
tlf_internal function getEffectivePaddingRight():Number
{ return computedFormat.paddingRight == FormatValue.AUTO ? computedFormat.borderRightWidth + computedFormat.marginRight : computedFormat.paddingRight + computedFormat.borderRightWidth + computedFormat.marginRight; }
/** @private */
tlf_internal function getEffectivePaddingTop():Number
{ return computedFormat.paddingTop == FormatValue.AUTO ? computedFormat.borderTopWidth + computedFormat.marginTop : computedFormat.paddingTop + computedFormat.borderTopWidth + computedFormat.marginTop; }
/** @private */
tlf_internal function getEffectivePaddingBottom():Number
{ return computedFormat.paddingBottom == FormatValue.AUTO ? computedFormat.borderBottomWidth + computedFormat.marginBottom : computedFormat.paddingBottom + computedFormat.borderBottomWidth + computedFormat.marginBottom; }
/** @private */
tlf_internal function getTotalPaddingLeft():Number
{ return getEffectivePaddingLeft() + (_rootElement ? _rootElement.getEffectivePaddingLeft() : 0); }
/** @private */
tlf_internal function getTotalPaddingRight():Number
{ return getEffectivePaddingRight() + (_rootElement ? _rootElement.getEffectivePaddingRight() : 0); }
/** @private */
tlf_internal function getTotalPaddingTop():Number
{ return getEffectivePaddingTop() + (_rootElement ? _rootElement.getEffectivePaddingTop() : 0); }
/** @private */
tlf_internal function getTotalPaddingBottom():Number
{ return getEffectivePaddingBottom() + (_rootElement ? _rootElement.getEffectivePaddingBottom() : 0); }
private var _selectionSprite:Sprite;
/** @private */
tlf_internal function getSelectionSprite(createForDrawing:Boolean):DisplayObjectContainer
{
if (createForDrawing)
{
if (_selectionSprite == null)
{
_selectionSprite = new Sprite();
_selectionSprite.mouseEnabled = false;
_selectionSprite.mouseChildren = false;
}
}
return _selectionSprite;
}
static private function createContainerControllerInitialFormat():ITextLayoutFormat
{
var ccif:TextLayoutFormat = new TextLayoutFormat();
ccif.columnCount = FormatValue.INHERIT;
ccif.columnGap = FormatValue.INHERIT;
ccif.columnWidth = FormatValue.INHERIT;
ccif.verticalAlign = FormatValue.INHERIT;
return ccif;
}
static private var _containerControllerInitialFormat:ITextLayoutFormat = createContainerControllerInitialFormat();
/**
* @private
* Specifies the initial format (ITextLayoutFormat instance) for a new ContainerController. The runtime
* applies this to the format property of all new containers on creation.
*
* By default, sets the column format values to "inherit"; all other format values are inherited.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.elements.TextFlow TextFlow
*/
static public function get containerControllerInitialFormat():ITextLayoutFormat
{ return _containerControllerInitialFormat; }
static public function set containerControllerInitialFormat(val:ITextLayoutFormat):void
{ _containerControllerInitialFormat = val; }
/** @private */
protected function get attachTransparentBackground():Boolean
{ return true; }
/** @private */
tlf_internal function clearCompositionResults():void
{
setTextLength(0);
for each (var textLine:TextLine in _shapeChildren)
{
removeTextLine(textLine);
CONFIG::debug { Debugging.traceFTECall(null,_container,"removeTextLine",textLine); }
}
_shapeChildren.length = 0;
_linesInView.length = 0;
if (_floatsInContainer)
_floatsInContainer.length = 0;
if (_composedFloats)
_composedFloats.length = 0;
// trace("clear composedFloats for container", flowComposer ? flowComposer.getControllerIndex(this) : 0);
}
private static var scratchRectangle:Rectangle = new Rectangle();
/** Add DisplayObjects that were created by composition to the container. @private */
tlf_internal function updateCompositionShapes():void
{
if(!shapesInvalid)
{
return;
}
// reclamp vertical/horizontal scrollposition - addresses Watson 2380962
var originalYScroll:Number = _yScroll;
if (verticalScrollPolicy != ScrollPolicy.OFF && !_measureHeight)
_yScroll = computeVerticalScrollPosition(_yScroll,false);
var originalXScroll:Number = _xScroll;
if (horizontalScrollPolicy != ScrollPolicy.OFF && !_measureWidth)
_xScroll = computeHorizontalScrollPosition(_xScroll,false);
var scrolled:Boolean = (originalYScroll != _yScroll || originalXScroll != _xScroll); // true if scroll values were changed - we need to notify in this case
// If we've scrolled, force all lines to be regathered since lines may now be in view that
// previously were not.
if (scrolled)
_linesInView.length = 0;
// Post all the new TextLines to the display list, and remove any old TextLines left from last time. Do this
// in a non-destructive way so that lines that have not been changed are not touched. This reduces redraw time.
fillShapeChildren();
var newShapeChildren:Array = _linesInView;
var childIdx:int = getFirstTextLineChildIndex(); // index where the first text line must appear at in its container
var newIdx:int = 0; // offset into newShapeChildren
var shapeChildrenStartIdx:int = 0; // starting offset into shapeChildren
// If we composed starting at the middle of the container, then _linesInView will contain only the lines that were
// in view and changed. In that case, we want to skip the lines in the beginning that weren't changed, and start
// the iteration from the point where the new lines start. So we get the first new line, go back one, and find where
// we are in the old list. If the line before the first new line was not displayed before, thenwe start from the
// beginning as usual. This can happen if we're scrolled forward, and then edit the first visible line.
if (_updateStart > absoluteStart && newShapeChildren.length > 0)
{
var firstTextLine:TextLine = newShapeChildren[0];
var firstLine:TextFlowLine = TextFlowLine(firstTextLine.userData);
var prevLine:TextFlowLine = flowComposer.findLineAtPosition(firstLine.absoluteStart - 1);
var prevTextLine:TextLine = prevLine.peekTextLine();
shapeChildrenStartIdx = _shapeChildren.indexOf(prevTextLine);
if (shapeChildrenStartIdx >= 0)
{
shapeChildrenStartIdx++;
childIdx += shapeChildrenStartIdx;
}
else
shapeChildrenStartIdx = 0;
}
var oldIdx:int = shapeChildrenStartIdx; // offset into shapeChildren
while (newIdx != newShapeChildren.length)
{
var newChild:TextLine = newShapeChildren[newIdx];
if (newChild == _shapeChildren[oldIdx])
{
// Same shape is in both lists, no change necessary, advance to next item in each list
CONFIG::debug { assert(newChild.parent == _container, "updateCompositionShapes expected line was already a child of the container"); }
childIdx++;
newIdx++;
oldIdx++;
continue;
}
var newChildIdx:int = _shapeChildren.indexOf(newChild);
if (newChildIdx == -1)
{
// Shape is in the new list, but not in the old list, add it to the display list at the current location, and advance to next item
addTextLine(newChild, childIdx++);
CONFIG::debug { Debugging.traceFTECall(null,_container,"addTextLine",newChild); }
newIdx++;
}
else
{
// The shape is on both lists, but there are several intervening "old" shapes in between. We'll remove the old shapes that
// come before the new one we want to insert.
removeAndRecycleTextLines (oldIdx, newChildIdx);
oldIdx = newChildIdx;
}
}
// remove any trailing children no longer displayed
removeAndRecycleTextLines (oldIdx, _shapeChildren.length);
// Update shapeChildren to reflect all these changes
if (shapeChildrenStartIdx > 0)
{
// We only updated some of the lines. Remove the old versions off the end, and add in the new ones from _linesInView
_shapeChildren.length = shapeChildrenStartIdx; // truncate
_shapeChildren = _shapeChildren.concat(_linesInView); // append _linesInView to end of _shapeChildren
_linesInView.length = 0; // truncate
}
else
{ // We updated all of the lines.
_linesInView = _shapeChildren; // move the old array over to _linesInView, so we reuse its storage
_linesInView.length = 0;
_shapeChildren = newShapeChildren;
}
if ((_floatsInContainer && _floatsInContainer.length > 0) || (_composedFloats && _composedFloats.length > 0))
updateGraphics(_updateStart);
shapesInvalid = false;
// _textFrame.updateVisibleRectangle(this._visibleRect);
updateVisibleRectangle();
var tf:TextFlow = this.textFlow;
// Set the Ctrl key condition
var needsCtrlKey:Boolean = (interactionManager != null && interactionManager.editingMode == EditingMode.READ_WRITE);
// Generate the hit test area for the LinkElements of the visible lines
var firstVisibleLine:TextFlowLine = getFirstVisibleLine();
var lastVisibleLine:TextFlowLine = getLastVisibleLine();
scratchRectangle.left = _contentLeft;
scratchRectangle.top = _contentTop;
scratchRectangle.width = _contentWidth;
scratchRectangle.height = _contentHeight;
_mouseEventManager.updateHitTests(effectiveBlockProgression == BlockProgression.RL && _hasScrollRect ? _contentWidth : 0,
scratchRectangle, tf,
firstVisibleLine ? firstVisibleLine.absoluteStart : _absoluteStart,
lastVisibleLine ? lastVisibleLine.absoluteStart + lastVisibleLine.textLength - 1 : _absoluteStart,
this,
needsCtrlKey);
_updateStart = _rootElement.textLength;
// If we're measuring, then the measurement values may have changed since last time.
// Force the transparent background to redraw, so that mouse events will work for the
// entire content area.
if (_measureWidth || _measureHeight)
attachTransparentBackgroundForHit(false);
if (tf.backgroundManager)
{
tf.backgroundManager.onUpdateComplete(this);
}
// If we updated the scroll values, we need to send an event
if (scrolled && tf.hasEventListener(TextLayoutEvent.SCROLL))
{
if (originalYScroll != _yScroll)
tf.dispatchEvent(new ScrollEvent(TextLayoutEvent.SCROLL, false, false, ScrollEventDirection.VERTICAL, _yScroll - originalYScroll));
if (originalXScroll != _xScroll)
tf.dispatchEvent(new ScrollEvent(TextLayoutEvent.SCROLL, false, false, ScrollEventDirection.HORIZONTAL, _xScroll - originalXScroll));
}
if (tf.hasEventListener(UpdateCompleteEvent.UPDATE_COMPLETE))
{
tf.dispatchEvent(new UpdateCompleteEvent(UpdateCompleteEvent.UPDATE_COMPLETE,false,false,tf, this));
}
// CONFIG::debug { validateLines(); }
}
CONFIG::debug private function updateGraphicsCheck(previousGraphics:Array):void
{
var graphicDisplayObject:DisplayObject;
var flowComposer:IFlowComposer = textFlow.flowComposer;
// correctness check
// iterate all graphics
// for each one: is it visible?
// if so, it should be a descendent of the container
// if not, it should NOT be a descendent of the container
// iterate all graphics previously visible
// for each one: is it in the composedGraphics list?
// if not, it should NOT be a descendent of the container
for each (graphicDisplayObject in previousGraphics)
{
if (getIndexOfFloat(graphicDisplayObject) < 0)
assert(!container.contains(graphicDisplayObject), "Previously visible graphic should have been removed from display list");
}
// Calculate the last possible anchor position for a visible float. The float can't be past the line *after* the last line.
// In case that's an uncomposed line with the entire rest of the TextFlow in it, we cap what we'll look at to 2000 chars
// past the last visible line end. This keeps us from iterating forever in a long text flow.
// var followingLineIndex:int = flowComposer.findLineIndexAtPosition(lastVisiblePosition) + 1;
var firstLine:TextFlowLine = getFirstVisibleLine();
var lastLine:TextFlowLine = getLastVisibleLine();
var firstVisiblePosition:int = firstLine ? firstLine.absoluteStart : this.absoluteStart;
var lastVisiblePosition:int = lastLine ? lastLine.absoluteStart + lastLine.textLength : this.absoluteStart + textLength;
var followingLine:TextFlowLine = flowComposer.findLineAtPosition(lastVisiblePosition);
var lastPossibleFloatPosition:int = followingLine ? followingLine.absoluteStart + followingLine.textLength : this.absoluteStart + textLength;
lastPossibleFloatPosition = Math.min(lastPossibleFloatPosition, this.absoluteStart + textLength);
lastPossibleFloatPosition = Math.min(lastPossibleFloatPosition, lastVisiblePosition + 2000);
lastPossibleFloatPosition = Math.min(lastPossibleFloatPosition, flowComposer.damageAbsoluteStart);
CONFIG::debug { assert(lastPossibleFloatPosition <= this.absoluteStart + textLength, "Expected lastPossibleFloatPosition to be before end of container"); }
// Get visible area
var wmode:String = effectiveBlockProgression;
var width:Number = _measureWidth ? _contentWidth : _compositionWidth;
var height:Number = _measureHeight ? _contentHeight : _compositionHeight;
var adjustX:Number = (wmode == BlockProgression.RL) ? _xScroll - width : _xScroll;
var adjustY:Number = _yScroll;
var numGraphics:int = _composedFloats.length;
for (var graphicIndex:int = 0; graphicIndex < numGraphics; ++graphicIndex)
{
var floatInfo:FloatCompositionData = _composedFloats[graphicIndex];
graphicDisplayObject = floatInfo.graphic;
var shouldDisplayGraphic:Boolean;
if (!graphicDisplayObject)
shouldDisplayGraphic = false;
else
{
if (floatInfo.floatType == Float.NONE)
shouldDisplayGraphic = floatInfo.absolutePosition >= firstVisiblePosition && floatInfo.absolutePosition < lastVisiblePosition;
else
shouldDisplayGraphic = floatIsVisible(wmode, adjustX, adjustY, width, height, floatInfo) && floatInfo.absolutePosition < lastPossibleFloatPosition && floatInfo.absolutePosition >= this.absoluteStart;
}
if (shouldDisplayGraphic)
assert(shouldDisplayGraphic == container.contains(graphicDisplayObject),
shouldDisplayGraphic ? "Graphic should be in view, but is not a descendent of the container" : "Graphic should not be in view, but is a descendent of the container");
}
}
tlf_internal function updateGraphics(updateStart:int):void
{
var inlineHolder:DisplayObjectContainer;
var visibleFloats:Array = [];
// If we have new floats that have been composed into container, we add them here.
// Also, we remove any that are no longer in the container. Only change those
// floats that are within the area that was recomposed.
var floatInfo:FloatCompositionData;
var float:DisplayObject;
var firstLine:TextFlowLine = getFirstVisibleLine();
var lastLine:TextFlowLine = getLastVisibleLine();
var firstVisiblePosition:int = firstLine ? firstLine.absoluteStart : this.absoluteStart;
var lastVisiblePosition:int = lastLine ? lastLine.absoluteStart + lastLine.textLength : this.absoluteStart + textLength;
// Calculate the last possible anchor position for a visible float. The float can't be past the line *after* the last line.
// In case that's an uncomposed line with the entire rest of the TextFlow in it, we cap what we'll look at to 2000 chars
// past the last visible line end. This keeps us from iterating forever in a long text flow.
// var followingLineIndex:int = flowComposer.findLineIndexAtPosition(lastVisiblePosition) + 1;
var followingLine:TextFlowLine = flowComposer.findLineAtPosition(lastVisiblePosition);
var lastPossibleFloatPosition:int = followingLine ? followingLine.absoluteStart + followingLine.textLength : this.absoluteStart + textLength;
lastPossibleFloatPosition = Math.min(lastPossibleFloatPosition, this.absoluteStart + textLength);
lastPossibleFloatPosition = Math.min(lastPossibleFloatPosition, lastVisiblePosition + 2000);
lastPossibleFloatPosition = Math.min(lastPossibleFloatPosition, flowComposer.damageAbsoluteStart);
CONFIG::debug { assert(lastPossibleFloatPosition <= this.absoluteStart + textLength, "Expected lastPossibleFloatPosition to be before end of container"); }
// Get visible area
var wmode:String = effectiveBlockProgression;
var width:Number = _measureWidth ? _contentWidth : _compositionWidth;
var height:Number = _measureHeight ? _contentHeight : _compositionHeight;
var adjustX:Number = (wmode == BlockProgression.RL) ? _xScroll - width : _xScroll;
var adjustY:Number = _yScroll;
var floatIndex:int = findFloatIndexAtOrAfter(updateStart);
var containerListIndex:int = 0;
var childIdx:int = getFirstTextLineChildIndex(); // index where the first text line must appear at in its container
if (floatIndex > 0)
{
// starting from the middle, need to skip over the initial entries already in the
// container list that are not being changed. Add them to the list of visible graphics, so they don't get dropped off.
floatInfo = _composedFloats[floatIndex - 1];
containerListIndex = _floatsInContainer.indexOf(floatInfo.graphic);
while (containerListIndex == -1 && floatIndex > 0)
{
// Graphic anchor point appears earlier in the text, but the float isn't (or wasn't previously) visible
--floatIndex;
floatInfo = _composedFloats[floatIndex];
containerListIndex = _floatsInContainer.indexOf(floatInfo.graphic);
}
containerListIndex++;
CONFIG::debug { assert(containerListIndex >= 0, "Can't find previously visible float"); }
for (var m:int = 0; m < floatIndex; ++m)
{
CONFIG::debug { assert(_composedFloats[m].absolutePosition >= this.absoluteStart, "Found float from previous container"); }
if (_composedFloats[m].absolutePosition >= this.absoluteStart)
visibleFloats.push(_composedFloats[m].graphic);
}
}
var firstContainerListIndex:int = containerListIndex;
if (!_floatsInContainer)
_floatsInContainer = [];
var numContainerList:int = _floatsInContainer.length;
CONFIG::debug
{
var oldChanges:Array = [];
var visibleAtEnd:Array = [];
oldUpdateGraphics(_updateStart, oldChanges, visibleAtEnd);
var changeIndex:int = 0;
var originalAlgorithmChange:Array;
var matrix:Matrix;
var oldMatrix:Matrix;
var changes:Array = [];
}
// Add in the floats from the last compose, at the composed location
var numFloats:int = _composedFloats.length;
for (; floatIndex < numFloats; )
{
floatInfo = _composedFloats[floatIndex];
float = floatInfo.graphic;
var parent:DisplayObjectContainer = floatInfo.parent;
var shouldDisplayGraphic:Boolean;
if (!float)
shouldDisplayGraphic = false;
else
{
if (floatInfo.floatType == Float.NONE)
shouldDisplayGraphic = floatInfo.absolutePosition >= firstVisiblePosition && floatInfo.absolutePosition < lastVisiblePosition;
else
shouldDisplayGraphic = floatIsVisible(wmode, adjustX, adjustY, width, height, floatInfo) && floatInfo.absolutePosition < lastPossibleFloatPosition && floatInfo.absolutePosition >= this.absoluteStart;
}
if (!shouldDisplayGraphic) // skip to the next
{
// Float may be after the last visible line and still visible if it is anchored to the following line because it was too wide for the column,
// so we don't stop iterating until we've gone at least one line past the last visible line.
if (floatInfo.absolutePosition >= lastPossibleFloatPosition)
break;
++floatIndex;
continue;
}
// If we had to remove some no-longer visible graphics, we might have already gone past this graphic before. Only add once.
if (visibleFloats.indexOf(float) < 0)
visibleFloats.push(float);
// If it's an inline, the TextLine in the FloatCompositionData may have been replaced. Check for this and get a new line if necessary.
if (floatInfo.floatType == Float.NONE)
{
// Check to see if the TextLine has changed
var tl:TextLine = parent as TextLine;
if (tl)
{
var tfl:TextFlowLine = tl.userData as TextFlowLine;
if (!tfl || floatInfo.absolutePosition < tfl.absoluteStart || floatInfo.absolutePosition >= tfl.absoluteStart + tfl.textLength || tl.parent == null || tl.validity == TextLineValidity.INVALID)
{
// TextLine doesn't match TextFlowLine -- refetch the TextLine
tfl = flowComposer.findLineAtPosition(floatInfo.absolutePosition);
for (var i:int = 0; i < _shapeChildren.length; i++)
if ((_shapeChildren[i] as TextLine).userData == tfl) // if this is coded into the loop condition we get a warning
break;
parent = (i < _shapeChildren.length) ? _shapeChildren[i] : null;
}
}
}
inlineHolder = float.parent;
// Float is already visible in the right place in the z-order, leave it but update its position
if (containerListIndex < numContainerList && floatInfo.parent == _container && inlineHolder && inlineHolder.parent == _container && float == _floatsInContainer[containerListIndex])
{
if (floatInfo.matrix)
inlineHolder.transform.matrix = floatInfo.matrix;
else
{
inlineHolder.x = 0;
inlineHolder.y = 0;
}
inlineHolder.alpha = floatInfo.alpha;
inlineHolder.x += floatInfo.x;
inlineHolder.y += floatInfo.y;
CONFIG::debug { assert(inlineHolder.contains(float), "expected float is already in display list"); }
CONFIG::debug { changes.push(["update", floatInfo.absolutePosition, inlineHolder.transform.matrix ? inlineHolder.transform.matrix.clone() : null, inlineHolder.alpha, inlineHolder.x, inlineHolder.y]); }
++floatIndex;
++containerListIndex;
continue;
}
var index:int = _floatsInContainer.indexOf(float);
if (index > containerListIndex && parent == _container) // it's in the existing list, but not yet, remove the old items from the container
{
var floatToRemove:DisplayObject = _floatsInContainer[containerListIndex++];
if (floatToRemove.parent)
{
CONFIG::debug { changes.push(["remove", getIndexOfFloat(floatToRemove), floatToRemove, "going to add float", floatToRemove.parent.parent is TextLine ? "removing inline" : "removing float"]); }
removeInlineGraphicElement(floatInfo.parent, floatToRemove.parent);
}
}
else
{
if (containerListIndex < numContainerList && float == _floatsInContainer[containerListIndex]) // it was previously a Float.NONE; so we don't want to remove it later
containerListIndex++;
inlineHolder = new Sprite();
if (floatInfo.matrix)
inlineHolder.transform.matrix = floatInfo.matrix;
inlineHolder.alpha = floatInfo.alpha;
inlineHolder.x += floatInfo.x;
inlineHolder.y += floatInfo.y;
inlineHolder.addChild(float);
CONFIG::debug { changes.push(["add", floatInfo.absolutePosition, parent, parent == _container ? childIdx : 0,
inlineHolder.transform.matrix ? inlineHolder.transform.matrix.clone() : null, inlineHolder.alpha, inlineHolder.x, inlineHolder.y]); }
if (parent == _container) // it's float - add to container
{
childIdx = Math.min(childIdx, _container.numChildren);
addInlineGraphicElement(_container, inlineHolder, childIdx++);
}
else // it's an inline - add to TextLine
addInlineGraphicElement(parent, inlineHolder, 0);
++floatIndex;
}
}
while (containerListIndex < _floatsInContainer.length) // remove trailing items
{
float = _floatsInContainer[containerListIndex++];
if (float.parent && visibleFloats.indexOf(float) < 0)
{
CONFIG::debug { changes.push(["remove", getIndexOfFloat(float), float, "removeTrailing", float.parent is TextLine ? "removing inline" : "removing float"]); }
removeInlineGraphicElement(float.parent.parent, float.parent);
}
}
CONFIG::debug { updateGraphicsCheck(_floatsInContainer); }
// Update the container list
_floatsInContainer = visibleFloats;
// CONFIG::debug { compareUpdateGraphicsResults(changes, oldChanges, _floatsInContainer, visibleAtEnd); }
}
/*
tlf_internal function updateGraphics(updateStart:int):void
{
var inlineHolder:DisplayObjectContainer;
var visibleFloats:Array = [];
// If we have new floats that have been composed into container, we add them here.
// Also, we remove any that are no longer in the container. Only change those
// floats that are within the area that was recomposed.
var floatInfo:FloatCompositionData;
var float:DisplayObject;
var firstLine:TextFlowLine = getFirstVisibleLine();
var lastLine:TextFlowLine = getLastVisibleLine();
var firstVisiblePosition:int = firstLine ? firstLine.absoluteStart : this.absoluteStart;
var lastVisiblePosition:int = lastLine ? lastLine.absoluteStart + lastLine.textLength : this.absoluteStart + textLength;
// Calculate the last possible anchor position for a visible float. The float can't be past the line *after* the last line.
// In case that's an uncomposed line with the entire rest of the TextFlow in it, we cap what we'll look at to 2000 chars
// past the last visible line end. This keeps us from iterating forever in a long text flow.
// var followingLineIndex:int = flowComposer.findLineIndexAtPosition(lastVisiblePosition) + 1;
var followingLine:TextFlowLine = flowComposer.findLineAtPosition(lastVisiblePosition);
var lastPossibleFloatPosition:int = followingLine ? followingLine.absoluteStart + followingLine.textLength : this.absoluteStart + textLength;
lastPossibleFloatPosition = Math.min(lastPossibleFloatPosition, this.absoluteStart + textLength);
lastPossibleFloatPosition = Math.min(lastPossibleFloatPosition, lastVisiblePosition + 2000);
lastPossibleFloatPosition = Math.min(lastPossibleFloatPosition, flowComposer.damageAbsoluteStart);
CONFIG::debug { assert(lastPossibleFloatPosition <= this.absoluteStart + textLength, "Expected lastPossibleFloatPosition to be before end of container"); }
// Get visible area
var wmode:String = effectiveBlockProgression;
var width:Number = _measureWidth ? _contentWidth : _compositionWidth;
var height:Number = _measureHeight ? _contentHeight : _compositionHeight;
var adjustX:Number = (wmode == BlockProgression.RL) ? _xScroll - width : _xScroll;
var adjustY:Number = _yScroll;
var floatIndex:int = findFloatIndexAtOrAfter(updateStart);
var containerListIndex:int = 0;
var childIdx:int = getFirstTextLineChildIndex(); // index where the first text line must appear at in its container
if (floatIndex > 0)
{
// starting from the middle, need to skip over the initial entries already in the
// container list that are not being changed. Add them to the list of visible graphics, so they don't get dropped off.
floatInfo = _composedFloats[floatIndex - 1];
containerListIndex = _floatsInContainer.indexOf(floatInfo.graphic);
while (containerListIndex == -1 && floatIndex > 0)
{
// Graphic anchor point appears earlier in the text, but the float isn't (or wasn't previously) visible
floatIndex--;
floatInfo = _composedFloats[floatIndex - 1];
containerListIndex = _floatsInContainer.indexOf(floatInfo.graphic);
}
CONFIG::debug { assert(containerListIndex != -1, "Can't find previously visible float"); }
containerListIndex++;
for (var m:int = 0; m < floatIndex; ++m)
{
CONFIG::debug { assert(_composedFloats[m].absolutePosition >= this.absoluteStart, "Found float from previous container"); }
if (_composedFloats[m].absolutePosition >= this.absoluteStart)
visibleFloats.push(_composedFloats[m].graphic);
}
}
var firstContainerListIndex:int = containerListIndex;
if (!_floatsInContainer)
_floatsInContainer = [];
var numContainerList:int = _floatsInContainer.length;
CONFIG::debug
{
var oldChanges:Array = [];
var visibleAtEnd:Array = [];
oldUpdateGraphics(_updateStart, oldChanges, visibleAtEnd);
var changeIndex:int = 0;
var originalAlgorithmChange:Array;
var matrix:Matrix;
var oldMatrix:Matrix;
var changes:Array = [];
}
// Add in the floats from the last compose, at the composed location
var numFloats:int = _composedFloats.length;
for (; floatIndex < numFloats; )
{
floatInfo = _composedFloats[floatIndex];
float = floatInfo.graphic;
var parent:DisplayObjectContainer = floatInfo.parent;
var shouldDisplayGraphic:Boolean;
if (!float)
shouldDisplayGraphic = false;
else
{
if (floatInfo.floatType == Float.NONE)
shouldDisplayGraphic = floatInfo.absolutePosition >= firstVisiblePosition && floatInfo.absolutePosition < lastVisiblePosition;
else
shouldDisplayGraphic = floatIsVisible(wmode, adjustX, adjustY, width, height, floatInfo) && floatInfo.absolutePosition < lastPossibleFloatPosition && floatInfo.absolutePosition >= this.absoluteStart;
}
if (!shouldDisplayGraphic) // skip to the next
{
// Float may be after the last visible line and still visible if it is anchored to the following line because it was too wide for the column,
// so we don't stop iterating until we've gone at least one line past the last visible line.
if (floatInfo.absolutePosition >= lastPossibleFloatPosition)
break;
++floatIndex;
continue;
}
// If we had to remove some no-longer visible graphics, we might have already gone past this graphic before. Only add once.
if (visibleFloats.indexOf(float) < 0)
visibleFloats.push(float);
// If it's an inline, the TextLine in the FloatCompositionData may have been replaced. Check for this and get a new line if necessary.
if (floatInfo.floatType == Float.NONE)
{
// Check to see if the TextLine has changed
var tl:TextLine = parent as TextLine;
if (tl)
{
var tfl:TextFlowLine = tl.userData as TextFlowLine;
if (!tfl || floatInfo.absolutePosition < tfl.absoluteStart || floatInfo.absolutePosition >= tfl.absoluteStart + tfl.textLength || tl.parent == null || tl.validity == TextLineValidity.INVALID)
{
// TextLine doesn't match TextFlowLine -- refetch the TextLine
tfl = flowComposer.findLineAtPosition(floatInfo.absolutePosition);
for (var i:int = 0; i < _shapeChildren.length; i++)
if ((_shapeChildren[i] as TextLine).userData == tfl) // if this is coded into the loop condition we get a warning
break;
parent = (i < _shapeChildren.length) ? _shapeChildren[i] : null;
}
}
}
inlineHolder = float.parent;
// Float is already visible in the right place in the z-order, leave it but update its position
if (containerListIndex < numContainerList && floatInfo.parent == _container && inlineHolder && inlineHolder.parent == _container && float == _floatsInContainer[containerListIndex])
{
if (floatInfo.matrix)
inlineHolder.transform.matrix = floatInfo.matrix;
else
{
inlineHolder.x = 0;
inlineHolder.y = 0;
}
inlineHolder.alpha = floatInfo.alpha;
inlineHolder.x += floatInfo.x;
inlineHolder.y += floatInfo.y;
CONFIG::debug { assert(inlineHolder.contains(float), "expected float is already in display list"); }
CONFIG::debug { changes.push(["update", floatInfo.absolutePosition, inlineHolder.transform.matrix ? inlineHolder.transform.matrix.clone() : null, inlineHolder.alpha, inlineHolder.x, inlineHolder.y]); }
++floatIndex;
++containerListIndex;
continue;
}
var index:int = _floatsInContainer.indexOf(float);
if (index > containerListIndex && parent == _container) // it's in the existing list, but not yet, remove the old items from the container
{
var floatToRemove:DisplayObject = _floatsInContainer[containerListIndex++];
if (floatToRemove.parent)
{
CONFIG::debug { changes.push(["remove", getIndexOfFloat(floatToRemove), floatToRemove, "going to add float", floatToRemove.parent.parent is TextLine ? "removing inline" : "removing float"]); }
removeInlineGraphicElement(_container, floatToRemove.parent);
}
}
else
{
if (containerListIndex < numContainerList && float == _floatsInContainer[containerListIndex]) // it was previously a Float.NONE; so we don't want to remove it later
containerListIndex++;
inlineHolder = new Sprite();
if (floatInfo.matrix)
inlineHolder.transform.matrix = floatInfo.matrix;
inlineHolder.alpha = floatInfo.alpha;
inlineHolder.x += floatInfo.x;
inlineHolder.y += floatInfo.y;
inlineHolder.addChild(float);
CONFIG::debug { changes.push(["add", floatInfo.absolutePosition, parent, parent == _container ? childIdx : 0,
inlineHolder.transform.matrix ? inlineHolder.transform.matrix.clone() : null, inlineHolder.alpha, inlineHolder.x, inlineHolder.y]); }
if (parent == _container) // it's float - add to container
{
childIdx = Math.min(childIdx, _container.numChildren);
addInlineGraphicElement(_container, inlineHolder, childIdx++);
}
else // it's an inline - add to TextLine
addInlineGraphicElement(parent, inlineHolder, 0);
++floatIndex;
}
}
while (containerListIndex < _floatsInContainer.length) // remove trailing items
{
float = _floatsInContainer[containerListIndex++];
if (float.parent)
{
CONFIG::debug { changes.push(["remove", getIndexOfFloat(float), float, "removeTrailing", float.parent is TextLine ? "removing inline" : "removing float"]); }
removeInlineGraphicElement(_container, float.parent);
}
}
// Update the container list
_floatsInContainer = visibleFloats;
// CONFIG::debug { compareUpdateGraphicsResults(changes, oldChanges, _floatsInContainer, visibleAtEnd); }
}
*/
private function floatIsVisible(wmode:String, scrollX:Number, scrollY:Number, scrollWidth:Number, scrollHeight:Number, floatInfo:FloatCompositionData):Boolean
{
var inlineGraphicElement:InlineGraphicElement = textFlow.findLeaf(floatInfo.absolutePosition) as InlineGraphicElement;
return (wmode == BlockProgression.TB) ?
(floatInfo.y + inlineGraphicElement.elementHeight >= scrollY) &&
(floatInfo.y <= scrollY + scrollHeight) :
(floatInfo.x + inlineGraphicElement.elementWidth >= scrollX) &&
(floatInfo.x <= scrollX + scrollWidth);
}
CONFIG::debug private function processResults(changes:Array):Array
{
var newProcessedResults:Array = [];
var newResults:Array;
for each (newResults in changes)
{
var i:int;
var result:Object = new Object();
result.absolutePosition = newResults[1];
result.state = newResults;
for (i = 0; i < newProcessedResults.length; ++i)
if (newProcessedResults[i].absolutePosition == newResults[1])
break;
if (i < newProcessedResults.length)
newProcessedResults[i] = result;
newProcessedResults.push(result);
}
return newProcessedResults;
}
CONFIG::debug private function compareUpdateGraphicsResults(newAlgorithmChanges:Array, originalAlgorithmChanges:Array, visibleFloats:Array, oldVisibleFloats:Array):void
{
// var newResults:Array;
var changeIndex:int = 0;
var oldMatrix:Matrix;
var matrix:Matrix;
var result:Object;
var newProcessedResults:Array = processResults(newAlgorithmChanges);
var oldProcessedResults:Array = processResults(originalAlgorithmChanges);
for each (result in newProcessedResults)
{
for (var i:int = 0; i < oldProcessedResults.length; ++i)
if (oldProcessedResults[i].absolutePosition == result.absolutePosition)
break;
assert(i < oldProcessedResults.length, "Object at " + result.absolutePosition.toString() + "not changed by original algorithm is changed(" + result.state[0] + ") by new");
if (i < oldProcessedResults.length)
{
var oldResult:Object = oldProcessedResults[i];
var oldChanges:Array = oldResult.state;
var newChanges:Array = result.state;
assert (newChanges[0] == oldChanges[0], "New algorithm left object at " + newChanges[1].toString() + " in different state than old algorithm (now " + newChanges[0] + " was " + oldChanges[0]);
if (newChanges[0] != oldChanges[0])
continue;
switch (newChanges[0])
{
case 'update':
oldMatrix = oldChanges[2] as Matrix;
matrix = newChanges[2] as Matrix;
assert((matrix == null && oldMatrix == null) ||
(matrix.a == oldMatrix.a && matrix.b == oldMatrix.b &&
matrix.c == oldMatrix.c && matrix.d == oldMatrix.d &&
matrix.tx == oldMatrix.tx && matrix.ty == oldMatrix.ty), "Expected matrix of new alogorithm to match matrix of old");
assert(oldChanges[3] == newChanges[3], "Expected alpha of new alogirthm to match alpha of old");
assert(oldChanges[4] == newChanges[4], "Expected X of new alogirthm to match X of old on update");
assert(oldChanges[5] == newChanges[5], "Expected Y of new alogirthm to match Y of old on update");
break;
case 'add':
assert(oldChanges[2] == newChanges[2], "After add new and old algorithm get different parent");
assert(oldChanges[3] == newChanges[3], "After add new and old algorithm get different child index");
matrix = oldChanges[4] as Matrix;
oldMatrix = newChanges[4] as Matrix;
assert((matrix == null && oldMatrix == null) ||
(matrix.a == oldMatrix.a && matrix.b == oldMatrix.b &&
matrix.c == oldMatrix.c && matrix.d == oldMatrix.d &&
matrix.tx == oldMatrix.tx && matrix.ty == oldMatrix.ty), "After add new and old algorithm get different matrix");
assert(oldChanges[5] == newChanges[5], "After add new and old algorithm get different alpha");
assert(oldChanges[6] == newChanges[6], "After add new and old algorithm get different X");
assert(oldChanges[7] == newChanges[7], "After add new and old algorithm get different Y");
break;
case 'remove':
if (oldChanges[0] != "remove" && newChanges[3] == "going to add inline")
return;
if (oldChanges[2] != newChanges[2])
{
for (var j:int = 0; j < oldProcessedResults.length; ++j)
if (oldProcessedResults[j].state[0] == "remove" && oldProcessedResults[j].state[2] == newChanges[2])
break;
assert(j < oldProcessedResults.length, "Float at " + newChanges[1].toString() + " removed by new algorithm " + oldChanges[0] + " by the old");
}
break;
}
}
}
var float:DisplayObject;
var floatPosition:int = -1;
for each (float in visibleFloats)
{
if (oldVisibleFloats.indexOf(float) < 0)
{
floatPosition = getIndexOfFloat(float);
assert (false, "new algorithm _floatsInContainer has visible graphic at " + floatPosition.toString() + " not in old algorithm in container " + flowComposer.getControllerIndex(this).toString());
}
}
for each (float in oldVisibleFloats)
{
if (visibleFloats.indexOf(float) < 0)
{
floatPosition = getIndexOfFloat(float);
assert (false, "old algorithm _floatsInContainer has visible graphic at " + floatPosition.toString() + " not in new algorithm" + flowComposer.getControllerIndex(this).toString());
}
}
}
CONFIG::debug private function getIndexOfFloat(float:DisplayObject):int
{
var floatPosition:int = -1;
for (var m:int = 0; m < _composedFloats.length; m++)
{
if (_composedFloats[m].graphic == float)
{
floatPosition = _composedFloats[m].absolutePosition;
break;
}
}
return floatPosition;
}
// Add or remove graphis (floats or regular inlines) from the display list
CONFIG::debug private function oldUpdateGraphics(updateStart:int, changes:Array, visibleAtEnd:Array):void
{
var inlineHolder:DisplayObjectContainer;
// If we have new floats that have been composed into container, we add them here.
// Also, we remove any that are no longer in the container. Only change those
// floats that are within the area that was recomposed.
var floatInfo:FloatCompositionData;
var firstLine:TextFlowLine = getFirstVisibleLine();
var lastLine:TextFlowLine = getLastVisibleLine();
var firstVisiblePosition:int = firstLine ? firstLine.absoluteStart : this.absoluteStart;
var lastVisiblePosition:int = lastLine ? lastLine.absoluteStart + lastLine.textLength : this.absoluteStart + textLength;
var floatIndex:int = findFloatIndexAtOrAfter(updateStart);
var lastFloatIndex:int = findFloatIndexAtOrAfter(lastVisiblePosition);
var containerListIndex:int = 0;
var childIdx:int = getFirstTextLineChildIndex(); // index where the first text line must appear at in its container
if (floatIndex > 0)
{
// starting from the middle, need to skip over the initial entries already in the
// container list that are not being changed
floatInfo = _composedFloats[floatIndex - 1];
containerListIndex = _floatsInContainer.indexOf(floatInfo.graphic) + 1;
CONFIG::debug { assert(containerListIndex >= 0, "Can't find pre-existing float in container list"); }
}
if (!_floatsInContainer)
_floatsInContainer = [];
var numContainerList:int = _floatsInContainer.length;
// Add in the floats from the last compose, at the composed location
for (; floatIndex < lastFloatIndex; )
{
floatInfo = _composedFloats[floatIndex];
var float:DisplayObject = floatInfo.graphic;
var parent:DisplayObjectContainer = floatInfo.parent;
if (floatInfo.floatType == Float.NONE) // If the parent line is not in view, don't display the ILG
{
// Check to see if the TextLine has changed
var tl:TextLine = parent as TextLine;
if (tl)
{
var tfl:TextFlowLine = tl.userData as TextFlowLine;
if (!tfl || floatInfo.absolutePosition < tfl.absoluteStart || floatInfo.absolutePosition >= tfl.absoluteStart + tfl.textLength || tl.parent == null || tl.validity == TextLineValidity.INVALID)
{
// TextLine doesn't match TextFlowLine -- refetch the TextLine
tfl = flowComposer.findLineAtPosition(floatInfo.absolutePosition);
for (var i:int = 0; i < _shapeChildren.length; i++)
if ((_shapeChildren[i] as TextLine).userData == tfl) // if this is coded into the loop condition we get a warning
break;
parent = (i < _shapeChildren.length) ? _shapeChildren[i] : null;
}
}
if (!floatInfo.matrix || _shapeChildren.indexOf(parent) < 0)
{
++floatIndex;
continue;
}
}
if (floatInfo.floatType == Float.NONE && (!floatInfo.matrix || _shapeChildren.indexOf(parent) < 0)) // If the parent line is not in view, don't display the ILG
{
++floatIndex;
continue;
}
if (float)
inlineHolder = float.parent;
if (containerListIndex < numContainerList && floatInfo.parent == _container && inlineHolder && inlineHolder.parent == _container && float == _floatsInContainer[containerListIndex]) // its in both lists, just skip over
{
var updateSprite:Sprite = new Sprite();
updateSprite.transform.matrix = inlineHolder.transform.matrix;
updateSprite.x = inlineHolder.x;
updateSprite.y = inlineHolder.y;
var updateAlpha:Number;
// Update in place
if (floatInfo.matrix)
updateMatrix = floatInfo.matrix;
else
{
updateSprite.x = 0;
updateSprite.y = 0;
}
updateAlpha = floatInfo.alpha;
updateSprite.x += floatInfo.x;
updateSprite.y += floatInfo.y;
var updateX:Number = updateSprite.x;
var updateY:Number = updateSprite.y;
var updateMatrix:Matrix = updateSprite.transform.matrix;
updateMatrix = updateMatrix.clone();
CONFIG::debug { assert(inlineHolder.contains(float), "expected float is already in display list"); }
changes.push(["update", floatInfo.absolutePosition, updateMatrix, updateAlpha, updateX, updateY]);
++floatIndex;
++containerListIndex;
continue;
}
var index:int = _floatsInContainer.indexOf(float);
if (index > containerListIndex && parent == _container) // it's in the existing list, but not yet, remove the old items from the container
{
var floatToRemove:DisplayObject = _floatsInContainer[containerListIndex++];
if (floatToRemove.parent)
// removeInlineGraphicElement(_container, floatToRemove.parent);
changes.push(["remove", getIndexOfFloat(floatToRemove), floatToRemove, parent == _container ? "going to add float" : "going to add inline"]);
}
else
{
if (containerListIndex < numContainerList && float == _floatsInContainer[containerListIndex]) // it was previously a Float.NONE; so we don't want to remove it later
containerListIndex++;
var addSprite:Sprite = new Sprite();
var addAlpha:Number = floatInfo.alpha;
if (floatInfo.matrix)
addSprite.transform.matrix = floatInfo.matrix;
addSprite.x += floatInfo.x; // addSprite x & y have values set up by matrix assignment - keep twip rounding same by assigning into Sprite
addSprite.y += floatInfo.y;
var addMatrix:Matrix = addSprite.transform.matrix;
var addX:Number = addSprite.x;
var addY:Number = addSprite.y;
addMatrix = addMatrix ? addMatrix.clone() : null;
if (parent == _container) // it's float - add to container
changes.push(["add", floatInfo.absolutePosition, _container, childIdx++, addMatrix, addAlpha, addX, addY]);
// addInlineGraphicElement(_container, inlineHolder, childIdx++);
else // it's an inline - add to TextLine
changes.push(["add", floatInfo.absolutePosition, parent, 0, addMatrix, addAlpha, addX, addY]);
// addInlineGraphicElement(parent, inlineHolder, 0);
++floatIndex;
}
}
while (containerListIndex < _floatsInContainer.length) // remove trailing items
{
float = _floatsInContainer[containerListIndex++];
if (float.parent)
{
changes.push(["remove", getIndexOfFloat(float), float, "removeTrailing"]);
// removeInlineGraphicElement(_container, float.parent);
}
}
// Update the container list
visibleAtEnd.length = 0;
for each (floatInfo in _composedFloats)
if (floatInfo.absolutePosition >= firstVisiblePosition && floatInfo.absolutePosition < lastVisiblePosition)
visibleAtEnd.push(floatInfo.graphic);
}
private function releaseLinesInBlock(textBlock:TextBlock):void
{
// run through the textBlock and if all lines are not in view,
// release the lines from the TextBlock, and release the TextBlock itself.
for (var textLine:TextLine = textBlock.firstLine; textLine && textLine.parent == null; textLine = textLine.nextLine)
{ // do nothing
}
if (!textLine && textBlock.firstLine) // no lines in paragraph are in the view
{
var para:ParagraphElement;
var line:TextFlowLine = textBlock.firstLine.userData as TextFlowLine;
if (line)
para = line.paragraph;
textBlock.releaseLines(textBlock.firstLine, textBlock.lastLine);
if (para)
para.releaseTextBlock();
}
}
private function removeAndRecycleTextLines (beginIndex:int, endIndex:int):void
{
var backgroundManager:BackgroundManager = textFlow.backgroundManager;
var child:TextLine;
var textBlock:TextBlock;
for (var index:int = beginIndex; index < endIndex; index++)
{
child = _shapeChildren[index];
removeTextLine(child);
CONFIG::debug { Debugging.traceFTECall(null,_container,"removeTextLine",child); }
// when we've removed all the lines in the paragraph in shapeChildren, run through the textBlock and if all lines are not in view,
// release the lines from the TextBlock, and release the TextBlock itself.
if (child.textBlock != textBlock)
{
if (textBlock)
releaseLinesInBlock(textBlock);
textBlock = child.textBlock;
}
}
// To fix bug #2988783 -By Jin Huang
// releaseLinesInBlock() can only be called here when
// 1. the end of final container
// 2. the end line of TextBlock is the end line of the container
if (textBlock && (textBlock.lastLine == child || flowComposer.getControllerAt(flowComposer.numControllers - 1) == this))
releaseLinesInBlock(textBlock);
// Recycle lines not in view and not in the TextBlock
if (TextLineRecycler.textLineRecyclerEnabled)
{
while (beginIndex < endIndex)
{
child = _shapeChildren[beginIndex++];
// Recycle if its not displayed and not connected to the textblock
if (!child.parent)
{
if (child.userData == null)
{
TextLineRecycler.addLineForReuse(child);
if (backgroundManager)
backgroundManager.removeLineFromCache(child);
}
else
{
var tfl:TextFlowLine = child.userData as TextFlowLine;
if (tfl && tfl.controller != this) // don't release lines that belong to other containers
continue;
if (child.validity == TextLineValidity.INVALID || (child.nextLine == null && child.previousLine == null && (!child.textBlock || child.textBlock.firstLine != child)))
{
child.userData = null;
TextLineRecycler.addLineForReuse(child);
if (backgroundManager)
backgroundManager.removeLineFromCache(child);
}
}
}
}
}
}
/**
* Gets the index at which the first text line must appear in its parent.
* The default implementation of this method, which may be overriden, returns the child index
* of the first <code>flash.text.engine.TextLine</code> child of <code>container</code>
* if one exists, and that of the last child of <code>container</code> otherwise.
*
* @return the index at which the first text line must appear in its parent.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.text.engine.TextLine
* @see #container
*/
protected function getFirstTextLineChildIndex():int
{
// skip past any non-TextLine children below the text in the container,
// This also means that in a container devoid of text, we will always
// populate the text starting at index container.numChildren, which is intentional.
var firstTextLine:int;
for(firstTextLine = 0; firstTextLine<_container.numChildren; ++firstTextLine)
{
if(_container.getChildAt(firstTextLine) is TextLine)
{
break;
}
}
return firstTextLine;
}
/**
* Adds a <code>flash.text.engine.TextLine</code> object as a descendant of <code>container</code>.
* The default implementation of this method, which may be overriden, adds the object
* as a direct child of <code>container</code> at the specified index.
*
* @param textLine the <code>flash.text.engine.TextLine</code> object to add
* @param index insertion index of the text line in its parent
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.text.engine.TextLine
* @see #container
*
*/
protected function addTextLine(textLine:TextLine, index:int):void
{
if ( index > _container.numChildren )
index = _container.numChildren;
_container.addChildAt(textLine, index);
}
/**
* Removes a <code>flash.text.engine.TextLine</code> object from its parent.
* The default implementation of this method, which may be overriden, removes the object
* from <code>container</code> if it is a direct child of the latter.
*
* This method may be called even if the object is not a descendant of <code>container</code>.
* Any implementation of this method must ensure that no action is taken in this case.
*
* @param textLine the <code>flash.text.engine.TextLine</code> object to remove
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.text.engine.TextLine
* @see #container
*
*/
protected function removeTextLine(textLine:TextLine):void
{
if (_container.contains(textLine))
_container.removeChild(textLine);
}
/**
* Adds a <code>flash.display.Shape</code> object on which background shapes (such as background color) are drawn.
* The default implementation of this method, which may be overriden, adds the object to <code>container</code>
* just before the first <code>flash.text.engine.TextLine</code> child, if one exists, and after the last exisiting
* child otherwise.
*
* @param shape <code>flash.display.Shape</code> object to add
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.display.Shape
* @see flash.text.engine.TextLine
* @see #container
*
*/
protected function addBackgroundShape(shape:Shape):void // No PMD
{
_container.addChildAt(_backgroundShape, getFirstTextLineChildIndex());
}
/**
* Removes a <code>flash.display.Shape</code> object on which background shapes (such as background color) are drawn.
* The default implementation of this method, which may be overriden, removes the object from its <code>parent</code>.
*
* @param shape <code>flash.display.Shape</code> object to remove
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.display.Shape
* @see flash.text.engine.TextLine
* @see #container
*
*/
protected function removeBackgroundShape(shape:Shape):void
{
if (shape.parent)
shape.parent.removeChild(shape);
}
/**
* Adds a <code>flash.display.DisplayObjectContainer</code> object to which selection shapes (such as block selection highlight, cursor etc.) are added.
* The default implementation of this method, which may be overriden, has the following behavior:
* The object is added just before first <code>flash.text.engine.TextLine</code> child of <code>container</code> if one exists
* and the object is opaque and has normal blend mode.
* In all other cases, it is added as the last child of <code>container</code>.
*
* @param selectionContainer <code>flash.display.DisplayObjectContainer</code> object to add
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.display.DisplayObjectContainer
* @see flash.text.engine.TextLine
* @see #container
*/
protected function addSelectionContainer(selectionContainer:DisplayObjectContainer):void
{
if (selectionContainer.blendMode == BlendMode.NORMAL && selectionContainer.alpha == 1)
{
// don't put selection behind background color or existing content in container, put it behind first text line
_container.addChildAt(selectionContainer, getFirstTextLineChildIndex());
}
else
_container.addChild(selectionContainer);
}
/**
* Removes the <code>flash.display.DisplayObjectContainer</code> object which contains selection shapes (such as block selection highlight, cursor etc.).
* The default implementation of this method, which may be overriden, removes the object from its parent if one exists.
*
* @param selectionContainer <code>flash.display.DisplayObjectContainer</code> object to remove
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flash.display.DisplayObjectContainer
* @see #container
*
*/
protected function removeSelectionContainer(selectionContainer:DisplayObjectContainer):void
{
selectionContainer.parent.removeChild(selectionContainer);
}
/**
* Adds a <code>flash.display.DisplayObject</code> object as a descendant of <code>parent</code>.
* The default implementation of this method, which may be overriden, adds the object
* as a direct child of <code>parent</code> at the specified index. This is called to add
* InlineGraphicElements to the TextLine or container.
*
* @param parent the <code>flash.display.DisplayObjectContainer</code> object to add the inlineGraphicElement to
* @param inlineGraphicElement the <code>flash.display.DisplayObject</code> object to add
* @param index insertion index of the float in its parent
*
* @playerversion Flash 10
* @playerversion AIR 2.0
* @langversion 3.0
*
* @see flash.display.DisplayObjectContainer
* @see flash.display.DisplayObject
* @see #container
*
*/
protected function addInlineGraphicElement(parent:DisplayObjectContainer, inlineGraphicElement:DisplayObject, index:int):void
{
// We're adding the inline holder -- the float it owns should not be in the visible list
CONFIG::debug { assert (parent != _container || !parent.contains(inlineGraphicElement), "Float already in container"); }
if (parent)
parent.addChildAt(inlineGraphicElement, index);
}
/**
* Removes a <code>flash.display.DisplayObject</code> object from its parent.
* The default implementation of this method, which may be overriden, removes the object
* from <code>container</code> if it is a direct child of the latter.
*
* This method may be called even if the object is not a descendant of <code>parent</code>.
* Any implementation of this method must ensure that no action is taken in this case.
*
* @param float the <code>flash.display.DisplayObject</code> object to remove
*
* @playerversion Flash 10
* @playerversion AIR 2.0
* @langversion 3.0
*
* @see flash.display.DisplayObjectContainer
* @see flash.display.DisplayObject
* @see #container
*
*/
protected function removeInlineGraphicElement(parent:DisplayObjectContainer, inlineGraphicElement:DisplayObject):void
{
// We're removing the inline holder -- the float it owns should be in the visible list
CONFIG::debug { assert (parent != _container || _floatsInContainer.indexOf(DisplayObjectContainer(inlineGraphicElement).getChildAt(0)) >= 0, "Float *not* already in container"); }
if (null != parent && inlineGraphicElement.parent == parent)
parent.removeChild(inlineGraphicElement);
}
/**
* @private
*/
tlf_internal function get textLines():Array
{
return _shapeChildren;
}
/**
* If scrolling, sets the scroll rectangle to the container rectangle so that any lines that are
* halfway in view are clipped to the scrollable region. If not scrolling, clear the
* scroll rectangle so that no clipping occurs.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
protected function updateVisibleRectangle() :void
{
if (horizontalScrollPolicy == ScrollPolicy.OFF && verticalScrollPolicy == ScrollPolicy.OFF)
{
if (_hasScrollRect)
{
_container.scrollRect = null;
_hasScrollRect = false;
}
}
else
{
var contentRight:Number = _contentLeft+contentWidth;
var contentBottom:Number = _contentTop+contentHeight;
var width:Number;
var compositionRight:Number;
if (_measureWidth)
{
width = contentWidth;
compositionRight = _contentLeft + width
}
else
{
width = _compositionWidth;
compositionRight = width;
}
var height:Number;
var compositionBottom:Number;
if (_measureHeight)
{
height = contentHeight;
compositionBottom = _contentTop + height;
}
else
{
height = _compositionHeight;
compositionBottom = height;
}
var xOrigin:Number = (effectiveBlockProgression == BlockProgression.RL) ? -width : 0;
var xpos:int = horizontalScrollPosition + xOrigin;
var ypos:int = verticalScrollPosition;
//Fix bug 2869747 textFlow.flowComposer.getControllerAt(0)
if(!_hasScrollRect)
{
_container.scrollRect = null;
CONFIG::debug { Debugging.traceFTECall(null,_container,"clearContainerScrollRect()"); }
}
if (textLength == 0 || xpos == 0 && ypos == 0 && _contentLeft >= xOrigin && _contentTop >= 0 && contentRight <= compositionRight && contentBottom <= compositionBottom)
{
if(_hasScrollRect)
{
_container.scrollRect = null;
CONFIG::debug { Debugging.traceFTECall(null,_container,"clearContainerScrollRect()"); }
_hasScrollRect = false;
}
}
else
{
// don't look at hasScrollRect but do look at scrollRect - client may have messed with it; okay to touch it because about to set it
var rect:Rectangle = _container.scrollRect;
if (!rect || rect.x != xpos || rect.y != ypos || rect.width != width || rect.height != height)
{
_container.scrollRect = new Rectangle(xpos, ypos, width, height);
CONFIG::debug { Debugging.traceFTECall(null,_container,"setContainerScrollRect",xpos, ypos, width, height); }
_hasScrollRect = true;
}
}
}
//Fix for Watson 2347938 - re-add the transparent background as the dimension of the
//container are altered by sutting down the scrolls in vertical text.
this.attachTransparentBackgroundForHit(false);
}
include "../formats/TextLayoutFormatInc.as";
/** Allows you to read and write user styles on a ContainerController object. Note that reading this property
* makes a copy of the userStyles set in the format of this element.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
*/
public function get userStyles():Object
{ return _format ? _format.userStyles : null; }
public function set userStyles(styles:Object):void
{
var val:String;
// clear the existing userstyles
for (val in userStyles)
this.setStyle(val,undefined);
// set the new ones
for (val in styles)
this.setStyle(val,styles[val]);
}
/** Returns the <code>coreStyles</code> on this ContainerController. Note that the getter makes a copy of the core
* styles dictionary. The returned object includes the formats that are defined by TextLayoutFormat and are in TextLayoutFormat.description. The
* returned object consists of an array of <em>stylename-value</em> pairs.
*
* @see flashx.textLayout.formats.TextLayoutFormat
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get coreStyles():Object
{ return _format ? _format.coreStyles : null; }
/** Returns the styles on this ContainerController. Note that the getter makes a copy of the
* styles dictionary. The returned object includes all styles set in the format property including core and user styles. The
* returned object consists of an array of <em>stylename-value</em> pairs.
*
* @see flashx.textLayout.formats.TextLayoutFormat
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get styles():Object
{ return _format ? _format.styles : null; }
/**
* Stores the ITextLayoutFormat object that contains the attributes for this container.
* The controller inherits the container properties from the TextFlow of which it is part.
* This property allows different controllers in the same text flow to have, for example,
* different column settings or padding.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see flashx.textLayout.formats.ITextLayoutFormat
*/
public function get format():ITextLayoutFormat
{ return _format; }
public function set format(value:ITextLayoutFormat):void
{
if (value == _format)
return;
var oldStyleName:String = this.styleName;
if (value == null)
_format.clearStyles();
else
writableTextLayoutFormat().copy(value);
formatChanged();
if (oldStyleName != this.styleName)
styleSelectorChanged();
}
private function writableTextLayoutFormat():FlowValueHolder
{
if (_format == null)
_format = new FlowValueHolder();
return _format;
}
/** Returns the value of the style specified by the <code>styleProp</code> parameter.
*
* @param styleProp The name of the style property whose value you want.
*
* @return The current value for the specified style.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function getStyle(styleProp:String):*
{
if (TextLayoutFormat.description.hasOwnProperty(styleProp))
return computedFormat.getStyle(styleProp);
var tf:TextFlow = _rootElement.getTextFlow();
if (!tf || !tf.formatResolver)
return computedFormat.getStyle(styleProp);
return getUserStyleWorker(styleProp);
}
/** @private worker function - any styleProp */
tlf_internal function getUserStyleWorker(styleProp:String):*
{
CONFIG::debug { assert(TextLayoutFormat.description[styleProp] === undefined,"bad call to getUserStyleWorker"); }
var userStyle:* = _format.getStyle(styleProp)
if (userStyle !== undefined)
return userStyle;
var tf:TextFlow = _rootElement ? _rootElement.getTextFlow() : null;
if (tf && tf.formatResolver)
{
userStyle = tf.formatResolver.resolveUserFormat(this,styleProp);
if (userStyle !== undefined)
return userStyle;
}
// or should it go to the container?
return _rootElement ? _rootElement.getUserStyleWorker(styleProp) : undefined;
}
/**
* Sets the value of the style specified by the <code>styleProp</code> parameter to the value
* specified by the <code>newValue</code> parameter.
*
* @param styleProp The name of the style property whose value you want to set.
* @param newValue The value that you want to assign to the style.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function setStyle(styleProp:String,newValue:*):void
{
if (TextLayoutFormat.description[styleProp])
this[styleProp] = newValue;
else
{
writableTextLayoutFormat().setStyle(styleProp,newValue);
formatChanged();
}
}
/** Clears the style specified by <code>styleProp</code> from this FlowElement. Sets the value to
* <code>undefined</code>.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function clearStyle(styleProp:String):void
{ setStyle(styleProp,undefined); }
/**
* Returns an ITextLayoutFormat instance with the attributes applied to this container, including the attributes inherited from its
* root element.
*
* @return object that describes the container's attributes.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*
* @see #rootElement
*/
public function get computedFormat():ITextLayoutFormat
{
if (!_computedFormat)
{
// TODO: revise cascade so it goes up through the container chain
var parentPrototype:TextLayoutFormat = _rootElement ? TextLayoutFormat(_rootElement.computedFormat): null;
_computedFormat = FlowElement.createTextLayoutFormatPrototype(formatForCascade,parentPrototype);
resetColumnState();
}
return _computedFormat;
}
/** @private */
tlf_internal function get formatForCascade():ITextLayoutFormat
{
if (_rootElement)
{
var tf:TextFlow = _rootElement.getTextFlow();
if (tf)
{
var elemStyle:TextLayoutFormat = tf.getTextLayoutFormatStyle(this);
if (elemStyle)
{
var localFormat:ITextLayoutFormat = _format;
if (localFormat == null)
return elemStyle;
var rslt:TextLayoutFormat = new TextLayoutFormat(elemStyle);
rslt.apply(localFormat);
return rslt;
}
}
}
return _format;
}
/** @private
* @returns -1 BEFORE visible, TextLine visible, 1 AFTER visible, 2 AFTER visible by auto-size image(bug #2988852)
* */
tlf_internal function testLineVisible(wmode:String, scrollXTW:int, scrollYTW:int, scrollWidthTW:int, scrollHeightTW:int, textFlowLine:TextFlowLine, textLine:TextLine):*
{
// So this is another take on figuring out whether the line bounds intersects the visible area of the container.
// This code figures out the logical bounds of the line, and uses that for the intersection. There was a
// previous version of this code that uses the DisplayObject's getBounds function, which will include information
// about the children, and also the bounds of visible glyphs. We decided that the logical bounds is close enough,
// and is much faster to obtain. However, there may be some lines, that get a different result using the logical
// bounds than the getBounds. I've left the old code here for verification.
CONFIG::debug { assert(textFlowLine != null,"testLineVisible"); }
//Bug #2988852, scrolling in the application causes all text to disappear. When auto-size images make the line "after visible"
//It's "after visible", but it cannot return 1. Because if it were 1, the binary-search in gatherVisibleLines() would make all the lines invisible.
if(textFlowLine.controller == null)
{
var ilgs:Array = textFlow.getElementsByTypeName("img");
var ilg:InlineGraphicElement;
for(var idx:int = 0; idx < ilgs.length; idx++)
{
ilg = ilgs[idx] as InlineGraphicElement;
if(ilg && ilg.getAbsoluteStart() < textFlowLine.absoluteStart)
{
if(wmode == BlockProgression.TB)
{
if(ilg.height == undefined || ilg.height == "auto")
return 2;
}
else
{
if(ilg.width == undefined || ilg.width == "auto")
return 2;
}
}
else
break;
}
}
// assume the same textFlow and that it's composed.
if (textFlowLine.controller != this)
return textFlowLine.absoluteStart < this.absoluteStart ? -1 : 1;
if (!textFlowLine.hasLineBounds())
{
if (!textLine)
textLine = textFlowLine.getTextLine(true);
textFlowLine.createShape(wmode, textLine);
if (textLine.numChildren == 0)
{
// Get it the new way
if (wmode == BlockProgression.TB)
textFlowLine.cacheLineBounds(wmode, textLine.x, textLine.y - textLine.ascent, textLine.textWidth, textLine.textHeight);
else
textFlowLine.cacheLineBounds(wmode, textLine.x - textLine.descent, textLine.y, textLine.textHeight, textLine.textWidth);
}
else // Phase this out after composition is updated to handle inline case correctly
{
var lineBounds:Rectangle = getPlacedTextLineBounds(textLine);
if (textLine.hasGraphicElement)
lineBounds = computeLineBoundsWithGraphics(textFlowLine, textLine, lineBounds);
textFlowLine.cacheLineBounds(wmode, lineBounds.x, lineBounds.y, lineBounds.width, lineBounds.height);
}
}
var rslt:* = (wmode == BlockProgression.TB ? _measureHeight : _measureWidth) ? 0 : textFlowLine.testLineVisible(wmode, scrollXTW, scrollYTW, scrollWidthTW, scrollHeightTW)
return (rslt == 0) ? (textLine ? textLine : textFlowLine.getTextLine(true)) : rslt;
}
/** @private
* function testLineVisible that was used in old algorithm of gatherVisibleLines()
* */
tlf_internal function oldTestLineVisible(wmode:String, scrollXTW:int, scrollYTW:int, scrollWidthTW:int, scrollHeightTW:int, textFlowLine:TextFlowLine, textLine:TextLine):TextLine
{
// So this is another take on figuring out whether the line bounds intersects the visible area of the container.
// This code figures out the logical bounds of the line, and uses that for the intersection. There was a
// previous version of this code that uses the DisplayObject's getBounds function, which will include information
// about the children, and also the bounds of visible glyphs. We decided that the logical bounds is close enough,
// and is much faster to obtain. However, there may be some lines, that get a different result using the logical
// bounds than the getBounds. I've left the old code here for verification.
if (!textFlowLine.hasLineBounds())
{
if (!textLine)
textLine = textFlowLine.getTextLine(true);
textFlowLine.createShape(wmode, textLine);
if (textLine.numChildren == 0)
{
// Get it the new way
if (wmode == BlockProgression.TB)
textFlowLine.cacheLineBounds(wmode, textLine.x, textLine.y - textLine.ascent, textLine.textWidth, textLine.textHeight);
else
textFlowLine.cacheLineBounds(wmode, textLine.x - textLine.descent, textLine.y, textLine.textHeight, textLine.textWidth);
}
else // Phase this out after composition is updated to handle inline case correctly
{
var lineBounds:Rectangle = getPlacedTextLineBounds(textLine);
if (textLine.hasGraphicElement)
lineBounds = computeLineBoundsWithGraphics(textFlowLine, textLine, lineBounds);
textFlowLine.cacheLineBounds(wmode, lineBounds.x, lineBounds.y, lineBounds.width, lineBounds.height);
}
}
if ((wmode == BlockProgression.TB ? _measureHeight : _measureWidth) || textFlowLine.oldTestLineVisible(wmode, scrollXTW, scrollYTW, scrollWidthTW, scrollHeightTW))
return textLine ? textLine : textFlowLine.getTextLine(true);
return null;
}
/* This code may turn out to be useful for iterating through the floats and doing some function,
similar to applyFunctionToElements.
tlf_internal function applyFunctionToFloats(absoluteStart:int, absoluteEnd:int, func:Function):void
{
if (_composedFloats)
{
var floatIndex:int = findFloatIndexAtOrAfter(absoluteStart);
var lastFloatIndex:int = findFloatIndexAtOrAfter(absoluteEnd);
while (floatIndex < lastFloatIndex)
{
var floatInfo:FloatCompositionData = _composedFloats[floatIndex];
if (!func(floatInfo))
break;
++floatIndex;
}
}
} */
private function computeLineBoundsWithGraphics(line:TextFlowLine, textLine:TextLine, boundsRect:Rectangle):Rectangle
{
if (_composedFloats)
{
var floatIndex:int = findFloatIndexAtOrAfter(line.absoluteStart);
var lastFloatIndex:int = findFloatIndexAtOrAfter(line.absoluteStart + line.textLength);
var inlineRect:Rectangle = new Rectangle();
var topLeft:Point = new Point();
while (floatIndex < lastFloatIndex)
{
var floatInfo:FloatCompositionData = _composedFloats[floatIndex];
if (floatInfo.floatType == Float.NONE)
{
var inlineGraphicElement:InlineGraphicElement = textFlow.findLeaf(floatInfo.absolutePosition) as InlineGraphicElement;
var inlineHolder:DisplayObject = inlineGraphicElement.placeholderGraphic.parent;
if (inlineHolder)
{
inlineRect.x = textLine.x + inlineHolder.x;
inlineRect.y = textLine.y + inlineHolder.y;
inlineRect.width = inlineGraphicElement.elementWidth;
inlineRect.height = inlineGraphicElement.elementHeight;
boundsRect = boundsRect.union(inlineRect);
}
}
++floatIndex;
}
}
return boundsRect;
}
/** @private */
tlf_internal function getPlacedTextLineBounds(textLine:TextLine):Rectangle
{
var curBounds:Rectangle;
curBounds = textLine.getBounds(textLine);
curBounds.x += textLine.x;
curBounds.y += textLine.y;
CONFIG::debug { verifyPlacedTextLineBounds(textLine,curBounds); }
return curBounds;
}
CONFIG::debug
{
import flash.system.Capabilities;
// OLD style calculation - lets make sure its the same.
static private var tempLineHolder:Sprite = new Sprite();
/** @private */
tlf_internal function verifyPlacedTextLineBounds(textLine:TextLine,newBounds:Rectangle):void
{
// But AIR has a bug so they don't match in AIR
if (Capabilities.playerType == "Desktop")
return;
var curBounds:Rectangle;
if (!textLine.parent)
{
// Has to be in the container to get the bounds
/*addTextLine(textLine,0);
curBounds = textLine.getBounds(_container);
removeTextLine(textLine); */
tempLineHolder.addChildAt(textLine,0);
curBounds = textLine.getBounds(tempLineHolder);
tempLineHolder.removeChildAt(0);
CONFIG::debug { assert(textLine.parent == null,"Bad removeChild in getPlacedTextLineBounds"); }
}
else
{
// Note: Relative to its parent, which may not be _container
// but in all reasonable cases, should share its origin with _container -- really???
curBounds = textLine.getBounds(textLine.parent);
}
assert(Math.abs(newBounds.x-curBounds.x) <= .1 && Math.abs(newBounds.y-curBounds.y) <= .1 && Math.abs(newBounds.width-curBounds.width) <= .1 && Math.abs(newBounds.height-curBounds.height) <= .1,
"verifyPlacedTextLineBounds: Bounds are different: "+curBounds.toString()+" : "+newBounds.toString());
}
}
/** @private */
tlf_internal function addComposedLine(textLine:TextLine):void
{
_linesInView.push(textLine);
}
/** @private Return the array. Client code may add lines to the array. */
tlf_internal function get composedLines():Array
{
if (!_linesInView)
_linesInView = [];
return _linesInView;
}
/** @private Empty out the linesInView, starting from the supplied text index. */
tlf_internal function clearComposedLines(pos:int):void
{
var index:int = 0;
for each (var textLine:TextLine in _linesInView)
{
var tfl:TextFlowLine = textLine.userData as TextFlowLine;
if (tfl.absoluteStart >= pos)
break;
index++;
}
_linesInView.length = index;
_updateStart = Math.min(_updateStart, pos);
}
/** @private */
tlf_internal function get numFloats():int
{
return _composedFloats ? _composedFloats.length : 0;
}
/** @private */
tlf_internal function getFloatAt(index:int):FloatCompositionData
{
return _composedFloats[index];
}
/** @private */
tlf_internal function getFloatAtPosition(absolutePosition:int):FloatCompositionData
{
if (!_composedFloats)
return null;
var i:int = findFloatIndexAtOrAfter(absolutePosition);
return (i < _composedFloats.length) ? _composedFloats[i] : null;
}
/** Add new float info (called by composition when a float is composed into the container
* @private
*/
tlf_internal function addFloatAt(absolutePosition:int, float:DisplayObject, floatType:String, x:Number, y:Number, alpha:Number, matrix:Matrix, depth:Number, knockOutWidth:Number, columnIndex:int, parent:DisplayObjectContainer):void
{
// trace("addFloatAt", absolutePosition, "for container", flowComposer.getControllerIndex(this));
if (!_composedFloats)
_composedFloats = [];
var floatInfo:FloatCompositionData = new FloatCompositionData(absolutePosition, float, floatType, x, y, alpha, matrix, depth, knockOutWidth, columnIndex, parent);
if (_composedFloats.length > 0 && _composedFloats[_composedFloats.length - 1] < absolutePosition)
_composedFloats.push(floatInfo);
else
{
var index:int = findFloatIndexAtOrAfter(absolutePosition);
_composedFloats.splice(index, 0, floatInfo);
}
CONFIG::debug { verifyComposedFloats(); }
}
/** Remove float info for all floats after the given text position
* @private
*/
tlf_internal function clearFloatsAt(absolutePosition:int):void
{
// trace("clearFloatsAt", absolutePosition, "for container", flowComposer.getControllerIndex(this));
if (_composedFloats)
if (absolutePosition == this.absoluteStart)
_composedFloats.length = 0;
else
_composedFloats.length = findFloatIndexAtOrAfter(absolutePosition);
}
/** @private */
CONFIG::debug private function verifyComposedFloats():void
{
var previousPosition:int = -1;
for ( var i:int = 0; i < _composedFloats.length; ++i)
{
var floatInfo:FloatCompositionData = _composedFloats[i];
// Don't allow duplicate entries
for ( var j:int = i + 1; j < _composedFloats.length && _composedFloats[j].absolutePosition != floatInfo.absolutePosition; ++j) {/* do nothing */}
assert(j == _composedFloats.length, "Found duplicate entry in ContainerController _composedFloats list");
// Entries should be ordered
assert(floatInfo.absolutePosition > previousPosition, "Found out of order float in ContainerController _composedFloats list");
previousPosition = floatInfo.absolutePosition;
assert (floatInfo.floatType != Float.START && floatInfo.floatType != Float.END, "Unexpected float type in composed floats array");
}
}
/**
* @private
* Returns the index in the array of a knockOut at the specified location, or the first knockOut index past that location.
*/
tlf_internal function findFloatIndexAfter(absolutePosition:int):int
{
for (var i:int = 0; i < _composedFloats.length && _composedFloats[i].absolutePosition <= absolutePosition; ++i)
{
// do nothing
}
return i;
}
/**
* @private
* Returns the index in the array of a knockOut at the specified location, or the first knockOut index past that location.
*/
tlf_internal function findFloatIndexAtOrAfter(absolutePosition:int):int
{
for (var i:int = 0; i < _composedFloats.length && _composedFloats[i].absolutePosition < absolutePosition; ++i)
{
// do nothing
}
return i;
}
/** @private */
tlf_internal function getInteractionHandler():IInteractionEventHandler
{ return this; }
}
}
import flash.events.MouseEvent;
import flash.display.InteractiveObject;
class PsuedoMouseEvent extends MouseEvent
{
public function PsuedoMouseEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, localX:Number = NaN, localY:Number = NaN, relatedObject:InteractiveObject = null, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, buttonDown:Boolean = false)
{
super(type,bubbles,cancelable,localX,localY,relatedObject,ctrlKey,altKey,shiftKey,buttonDown);
}
public override function get currentTarget():Object
{ return relatedObject; }
public override function get target():Object
{ return relatedObject; }
}