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