blob: 5fc5414a315bc875c9bd7050bcd27eb43a700bb9 [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.Point;
import flash.geom.Rectangle;
import flash.text.engine.TextLine;
import flash.text.engine.TextLineValidity;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuClipboardItems;
import flash.utils.Timer;
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.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.ParagraphElement;
import flashx.textLayout.elements.TextFlow;
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.formats.TextLayoutFormatValueHolder;
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 flashx.textLayout.container.TextContainerController
*/
public class ContainerController implements IInteractionEventHandler, ITextLayoutFormat, ISandboxSupport
{
private var _textFlowCache:TextFlow;
private var _rootElement:ContainerFormattedElement;
private var _absoluteStart:int;
private var _textLength:int;
private var _container:Sprite;
// note must be protected - subclass sets or gets this variable but can't be public
/** computed container attributes. @private */
protected var _computedFormat:TextLayoutFormatValueHolder;
// 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 _composeCompleteRatio:Number; // 1 if composition was complete when contentHeight, etc registered, greater than one otherwise
// 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;
/** @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
*
* <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 _formatValueHolder: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;
_composeCompleteRatio = 1;
// 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.
if (_container is InteractiveObject)
InteractiveObject(_container).doubleClickEnabled = true;
_horizontalScrollPolicy = _verticalScrollPolicy = String(ScrollPolicy.scrollPolicyPropertyDefinition.defaultValue);
_hasScrollRect = false;
CONFIG::debug { id = contCount.toString(); ++contCount; }
_shapeChildren = [ ];
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.
*
* @param w The width in pixels that's available for text in the container.
* @param h The height in pixels that's available for text in the container.
*
* @includeExample examples\ContainerController_setCompositionSizeExample.as -noswf
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function setCompositionSize(w:Number,h:Number):void
{
// 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();
invalidateContents();
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)
{
clearCompositionResults();
detachContainer();
_rootElement = value;
_textFlowCache = null;
_textLength = 0;
_absoluteStart = -1;
attachContainer();
if (_rootElement)
formatChanged();
}
}
/**
* @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;
}
//--------------------------------------------------------------------------
//
// 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;
// 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;
}
}
}
}
}
/** @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
_composeCompleteRatio = 1;
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"); }
_composeCompleteRatio = (textFlow.textLength-containerAbsoluteStart) / numChars;
// _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;
}
}
setTextLengthOnly(numChars);
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!");
}
}
/** Updates the text within the container.
* Called after an editing change or composition to keep absoluteStart and textLength up to date.
* @private
*/
tlf_internal function updateLength(pos:int, lengthToAdd:int):void
{
CONFIG::debug { assert(_textLength+lengthToAdd >= 0,"bad set textLength"); }
setTextLengthOnly(_textLength + lengthToAdd);
}
/**
* 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();
}
/**
* Removes inlines that should no longer be on the display list and
* adds inlines that are new to the display list.
* @private
*/
tlf_internal function updateInlineChildren():void
{
}
/** determines the shapechildren in the container and applies VJ. @private */
protected function fillShapeChildren(sc:Array,tempSprite:Sprite):void
{
if (_textLength == 0)
return; // none
var flowComposer:IFlowComposer = this.flowComposer;
var wmode:String = effectiveBlockProgression;
var width:Number = _measureWidth ? _contentWidth : _compositionWidth;
var height:Number = _measureHeight ? _contentHeight : _compositionHeight;
var scrollAdjustXTW:int;
var scrollAdjustYTW:int;
var scrollAdjustWidthTW:int;
var scrollAdjustHeightTW:int;
var adjustX:Number = (wmode == BlockProgression.RL) ? _xScroll - width : _xScroll;
var adjustY:Number = _yScroll;
scrollAdjustXTW = Twips.round(adjustX);
scrollAdjustYTW = Twips.round(adjustY);
scrollAdjustWidthTW = Twips.to(width);
scrollAdjustHeightTW = Twips.to(height);
// 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);
// 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);
// Build the list in reverse order and than reverse it at the end.
// This fixes bug 2509360 ARGO: Unselectable lines appear while scrolling
// new bug filed because this is a small performance hit on the reverse - but this is the safest thing to do late in dev cycle
for (var lineIndex:int = firstLine; lineIndex <= lastLine; lineIndex++)
{
var curLine:TextFlowLine = flowComposer.getLineAt(lineIndex);
if (curLine == null || curLine.controller != this)
continue;
var textLine:TextLine = lineIsVisible(wmode, scrollAdjustXTW, scrollAdjustYTW, scrollAdjustWidthTW, scrollAdjustHeightTW, curLine);
if (!textLine)
continue;
CONFIG::debug { assert(textLine == curLine.peekTextLine(),"Bad textLine in fillShapeChildren "+Debugging.getIdentity(textLine)+" "+Debugging.getIdentity(curLine)); }
if (adjustLines)
{
textLine.x -= adjustX;
textLine.y -= adjustY;
}
sc.push(textLine);
if (textLine.parent == null)
tempSprite.addChild(textLine);
}
if (adjustLines)
{
_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
}
}
/** 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();
}
}
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 && _composeCompleteRatio != 1 && 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();
}
}
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 && _composeCompleteRatio != 1 && 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
*
* 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
{
return (effectiveBlockProgression == BlockProgression.TB) ? _contentHeight * _composeCompleteRatio : _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
{
return (effectiveBlockProgression == BlockProgression.RL) ? _contentWidth * _composeCompleteRatio : _contentWidth;
}
/** @private */
tlf_internal function setContentBounds(contentLeft:Number, contentTop:Number, contentWidth:Number, contentHeight:Number):void
{
_contentWidth = contentWidth;
_contentHeight = contentHeight;
_contentLeft = contentLeft;
_contentTop = contentTop;
}
private function updateForScroll():void
{
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 TextLayoutEvent(TextLayoutEvent.SCROLL));
// trace("contentHeight", contentHeight, "contentWidth", contentWidth);
// trace("contentHeight", contentHeight, "contentWidth", contentWidth);
}
CONFIG::debug tlf_internal function validateLines():void
{
if (!Debugging.containerLineValidation)
return;
var flowComposer:IFlowComposer = textFlow.flowComposer;
var textLine:TextLine;
/*for (var ii:int = 0; ii < flowComposer.numLines; ii++)
{
textLine = flowComposer.getLineAt(ii).peekTextLine()
trace(" // flowComposer",ii,textLine ? Debugging.getIdentity(textLine) : "null");
}*/
// find first textline
var numContainerChildren:int = _container.numChildren;
for (var containerIndex:int = 0; containerIndex < numContainerChildren; containerIndex++)
{
textLine = _container.getChildAt(containerIndex) as TextLine;
if (textLine)
break;
}
if (textLine == null)
return; // no lines in this container
// Given the TextLine, find the index of the corresponding TextFlowLine.
var textFlowLine:TextFlowLine = textLine.userData as TextFlowLine;
var flowComposerIndex:int = flowComposer.findLineIndexAtPosition(textFlowLine.absoluteStart);
assert(flowComposerIndex != flowComposer.numLines,"BAD FIRST LINE IN CONTAINER");
var columnIndex:int = -1; // force the TextFlowLine position to reset
while (containerIndex < numContainerChildren)
{
textLine = _container.getChildAt(containerIndex) as TextLine;
if (textLine == null)
{
// the very last thing can be the selection sprite
assert(containerIndex == numContainerChildren-1,"Wrong location for selectionsprite");
assert(_container.getChildAt(containerIndex) == getSelectionSprite(false),"expected selectionsprite but not found");
break;
}
textFlowLine = flowComposer.getLineAt(flowComposerIndex);
if (textFlowLine.columnIndex != columnIndex)
{
// find the index of the first visible line in the column
// Scrolling in multiple columns may mean that the line after the last visible text line in column 1 is not the first visible line in column 2.
// Skip over lines that are "scrolled out" on the last column
while (textLine != textFlowLine.peekTextLine())
{
++flowComposerIndex;
textFlowLine = flowComposer.getLineAt(flowComposerIndex);
}
columnIndex = textFlowLine.columnIndex;
}
var peekLine:TextLine = textFlowLine.peekTextLine();
assert(textLine == peekLine,"BAD TEXTLINE IN TEXTFLOWLINE");
containerIndex++;
flowComposerIndex++;
}
}
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 prevLine:TextFlowLine = begLineIndex == 0 ? null : flowComposer.getLineAt(begLineIndex-1);
var currLine:TextFlowLine = flowComposer.getLineAt(begLineIndex);
var accumulatedIntersection:int = 0;
var scrollRectLeft:Number = containerScrollRectLeft;
var scrollRectTop:Number = containerScrollRectTop;
var scrollRectRight:Number = containerScrollRectRight;
var scrollRectBottom:Number = containerScrollRectBottom;
var scrollRect:Rectangle = new Rectangle(scrollRectLeft, scrollRectTop, scrollRectRight-scrollRectLeft, scrollRectBottom-scrollRectTop);
for (var lineIndex:int = begLineIndex; lineIndex <= endLineIndex; lineIndex++)
{
var nextLine:TextFlowLine = lineIndex+1 == flowComposer.numLines ? null : flowComposer.getLineAt(lineIndex+1);
var lineEnd:int = currLine.absoluteStart+currLine.textLength;
if (currLine.controller == this)
{
accumulatedIntersection += currLine.selectionWillIntersectScrollRect(scrollRect,begPos,Math.min(lineEnd,endPos),prevLine,nextLine);
if (accumulatedIntersection >= 2)
return; // dont scroll
}
if (lineIndex == endLineIndex)
break;
prevLine = currLine;
currLine = nextLine;
begPos = lineEnd;
}
var rect:Rectangle = posToRectangle(activePosition);
if (!rect)
{
flowComposer.composeToPosition(activePosition);
rect = posToRectangle(activePosition);
}
if (rect)
{
var lastVisibleLine:TextFlowLine;
// vertical scroll
if (rect.top < scrollRectTop)
verticalScrollPosition = rect.top;
if (verticalText) {
// horizontal scroll
if (rect.left < scrollRectLeft)
horizontalScrollPosition = rect.left + _compositionWidth;
if (rect.right > scrollRectRight)
horizontalScrollPosition = rect.right;
// set the rect to the previous character for the test on the bottom of the scrollRect.
// Note, when dealing with the "bottommost" character (t-to-b), we actually need to position
// pos at pos-1 because pos is looking at the character following the insertion point.
// However, we can only look at the previous character if we're not on the first character in
// a line.
// This tests for pos being the first char on a line. If not, reset rect.
if (flowComposer.findLineAtPosition(activePosition).absoluteStart != activePosition)
rect = posToRectangle(activePosition-1);
// 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 (activePosition == anchorPosition)
rect.bottom += 2;
// vertical scroll
if (rect && rect.bottom > scrollRectBottom)
verticalScrollPosition = rect.bottom - _compositionHeight;
// now, we need to determine if the scrollRect is full or only partially full.
// A partially full scrollRect can happen when you're deleting text at the end of the
// container. When that happens, the bottomExtreme line in the scrollRect has space between
// it and the left edge of the scrollRect. So, if it's partially full, then we need to scroll
// to bring more of the Flow into view.
lastVisibleLine = getLastVisibleLine();
if (lastVisibleLine && lastVisibleLine.x - lastVisibleLine.descent - lastVisibleLine.spaceAfter > scrollRectLeft)
horizontalScrollPosition = lastVisibleLine.x - lastVisibleLine.descent + _compositionWidth;
}
else
{
// vertical scroll
if (rect.bottom > scrollRectBottom)
verticalScrollPosition = rect.bottom - _compositionHeight;
// horizontal scroll
if (rect.left < scrollRectLeft)
horizontalScrollPosition = rect.left;
// set the rect to the previous character for the test on the right side of the scrollRect.
// Note, when dealing with the "rightmost" character (l-to-r), we actually need to position
// pos at pos-1 because pos is looking at the character following the insertion point.
// However, we can only look at the previous character if we're not on the first character in
// a line.
// This tests for pos being the first char on a line. If not, reset rect.
if (flowComposer.findLineAtPosition(activePosition).absoluteStart != activePosition)
rect = posToRectangle(activePosition-1);
// 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;
if (rect && rect.right > scrollRectRight)
horizontalScrollPosition = rect.right - _compositionWidth;
// now, we need to determine if the scrollRect is full or only partially full.
// A partially full scrollRect can happen when you're deleting text at the end of the
// container. When that happens, the bottomExtreme line in the scrollRect has space between
// it and the bottom edge of the scrollRect. So, if it's partially full, then we need to scroll
// to bring more of the Flow into view.
lastVisibleLine = getLastVisibleLine();
if (rect.top > scrollRectTop && lastVisibleLine && lastVisibleLine.y + lastVisibleLine.height + lastVisibleLine.spaceAfter < scrollRectBottom)
verticalScrollPosition = lastVisibleLine.y + lastVisibleLine.height;
}
}
}
private function posToRectangle(pos:int):Rectangle
{
var line:TextFlowLine = flowComposer.findLineAtPosition(pos);
// should the textLine ever be null? It is after some operations -- dunno why (rlw)
if (!line.textLineExists || line.isDamaged())
return null;
var textLine:TextLine = line.getTextLine(true);
var atomBounds:Rectangle;
var atomIdx:int = textLine.getAtomIndexAtCharIndex(pos-line.paragraph.getAbsoluteStart());
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 (effectiveBlockProgression == BlockProgression.RL)
{
var leafElement:FlowLeafElement = _rootElement.getTextFlow().findLeaf(pos);
if (leafElement.getParentByType(flashx.textLayout.elements.TCYElement) != null)
return new Rectangle(line.x+atomBounds.x+line.y+atomBounds.y+atomBounds.width,atomBounds.height);
}
return effectiveBlockProgression == BlockProgression.RL ?
new Rectangle(line.x, line.y + atomBounds.y, line.height, atomBounds.height) :
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 && _textLength)
textFlow.damage(absoluteStart, _textLength, 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 && attachTransparentBackground)
{
var s:Sprite = _container as Sprite;
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)
attachContainer();
else
detachContainer();
}
//--------------------------------------------------------------------------
// 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);
_container.addEventListener(MouseEvent.MOUSE_WHEEL, receiver.mouseWheelHandler);
_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);
_container.removeEventListener(MouseEvent.MOUSE_WHEEL, receiver.mouseWheelHandler);
_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 */
tlf_internal function detachContainer():void
{
if (_minListenersAttached)
{
if (_container)
{
_container.removeEventListener(FocusEvent.FOCUS_IN, requiredFocusInHandler);
_container.removeEventListener(MouseEvent.MOUSE_OVER, requiredMouseOverHandler);
if(_allListenersAttached)
{
removeInteractionHandlers();
_container.contextMenu = null;
attachTransparentBackgroundForHit(true);
_allListenersAttached = false;
}
}
_minListenersAttached = false;
}
}
private function attachAllListeners():void
{
if (!_allListenersAttached && textFlow && textFlow.interactionManager)
{
CONFIG::debug { assert(_minListenersAttached,"Bad call to attachAllListeners - won't detach"); }
_allListenersAttached = true;
if (_container)
{
_container.contextMenu = createContextMenu();
attachInteractionHandlers();
}
}
}
/** @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
{
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();
var lastVisibleLine:TextFlowLine = getLastVisibleLine();
// 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.
if (lastVisibleLine)
{
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)
flowComposer.composeToPosition(flowComposer.damageAbsoluteStart + 1000);
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 (firstVisibleLine)
{
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)
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)
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
{
// 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)
{
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)
{
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)
{
// 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)
{
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);
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);
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)
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)
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)
interactionManager.textInputHandler(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
{
var tf:DisplayObjectContainer = _container as DisplayObjectContainer;
if (interactionManager)
{
interactionManager.menuSelectHandler(event);
}
else
{
var cbItems:ContextMenuClipboardItems = tf.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)
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();
if (interactionManager.activePosition == interactionManager.anchorPosition)
selObj.graphics.beginFill(selFormat.pointColor)
else
selObj.graphics.beginFill(selFormat.rangeColor);
// 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;
}
selObj.graphics.drawRect(int(x),int(y),w,h);
selObj.graphics.endFill();
// make it blink
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, DisplayObject(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, DisplayObject(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 get effectivePaddingLeft():Number
{ return computedFormat.paddingLeft + (_rootElement ? _rootElement.computedFormat.paddingLeft : 0); }
/**
* @private
*/
tlf_internal function get effectivePaddingRight():Number
{ return computedFormat.paddingRight + (_rootElement ? _rootElement.computedFormat.paddingRight : 0); }
/**
* @private
*/
tlf_internal function get effectivePaddingTop():Number
{ return computedFormat.paddingTop + (_rootElement ? _rootElement.computedFormat.paddingTop : 0); }
/**
* @private
*/
tlf_internal function get effectivePaddingBottom():Number
{ return computedFormat.paddingBottom + (_rootElement ? _rootElement.computedFormat.paddingBottom : 0); }
private var _selectionSprite:Sprite;
/** @private */
tlf_internal function getSelectionSprite(createIfNull:Boolean):DisplayObjectContainer
{
if (_selectionSprite == null && createIfNull)
{
_selectionSprite = new Sprite();
_selectionSprite.mouseEnabled = false;
_selectionSprite.mouseChildren = false;
}
return _selectionSprite;
}
static private function createContainerControllerInitialFormat():ITextLayoutFormat
{
var ccif:TextLayoutFormatValueHolder = new TextLayoutFormatValueHolder();
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 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;
}
/** The TextLines being added to the array in fillShapeChildren are added to tempSprite because they are about to be displayed
* and the TextFlowLine code needs to know that as it keeps all displayed lines in the TextFlowLine textLineCache. This tells them that.
*/
static private var tempLineHolder:Sprite = new Sprite();
/** 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 scrolled:Boolean = false; // true if scroll values were changed - we need to notify in this case
var tmp:Number = _yScroll;
if (verticalScrollPolicy != ScrollPolicy.OFF && !_measureHeight)
_yScroll = computeVerticalScrollPosition(_yScroll,false);
scrolled = (tmp != _yScroll);
tmp = _xScroll;
if (horizontalScrollPolicy != ScrollPolicy.OFF && !_measureWidth)
_xScroll = computeHorizontalScrollPosition(_xScroll,false);
scrolled = scrolled || (tmp != _xScroll);
// 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.
var newShapeChildren:Array = [ ];
fillShapeChildren(newShapeChildren,tempLineHolder);
var childIdx:int = getFirstTextLineChildIndex(); // index where the first text line must appear at in its container
var oldIdx:int = 0; // offset into shapeChildren
var newIdx:int = 0; // offset into newShapeChildren
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
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);
_shapeChildren = newShapeChildren;
shapesInvalid = false;
// TODO: support for inline children (tables)
// synchronize the inline shapes beginning at childIdx
updateInlineChildren();
// _textFrame.updateVisibleRectangle(this._visibleRect);
updateVisibleRectangle();
// 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);
var tf:TextFlow = this.textFlow;
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))
{
tf.dispatchEvent(new TextLayoutEvent(TextLayoutEvent.SCROLL));
}
if (tf.hasEventListener(UpdateCompleteEvent.UPDATE_COMPLETE))
{
tf.dispatchEvent(new UpdateCompleteEvent(UpdateCompleteEvent.UPDATE_COMPLETE,false,false,tf, this));
}
CONFIG::debug { assert(tempLineHolder.numChildren == 0,"Uh oh"); }
CONFIG::debug { validateLines(); }
// prevent leaks here - this code should't be needed
while (tempLineHolder.numChildren)
tempLineHolder.removeChildAt(0);
}
private function removeAndRecycleTextLines (beginIndex:int, endIndex:int):void
{
var backgroundManager:BackgroundManager = textFlow.backgroundManager;
var child:TextLine;
while (beginIndex < endIndex)
{
child = _shapeChildren[beginIndex++];
removeTextLine(child);
CONFIG::debug { Debugging.traceFTECall(null,_container,"removeTextLine",child); }
// Recycle if its not displayed and not connected to the textblock
if (TextLineRecycler.textLineRecyclerEnabled && !child.parent)
{
if (child.userData == null)
{
TextLineRecycler.addLineForReuse(child);
if (backgroundManager)
backgroundManager.removeLineFromCache(child);
}
else if (child.validity == TextLineValidity.INVALID)
{
if (child.nextLine == null && child.previousLine == null && (!child.textBlock || child.textBlock.firstLine != child))
{
child.userData.releaseTextLine();
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
{
_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
{
_container.addChildAt(_backgroundShape, getFirstTextLineChildIndex());
}
/**
* 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);
}
/**
* @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;
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";
/**
* The <code>userStyles</code> object for a ContainerController instance. The getter makes a copy of the
* <code>userStyles</code> object, which is an array of <em>stylename-value</em> pairs.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get userStyles():Object
{
var styles:Object = _formatValueHolder == null ? null : _formatValueHolder.userStyles;
return styles ? Property.shallowCopy(styles) : null;
}
public function set userStyles(styles:Object):void
{
var newStyles:Object = new Object();
for (var val:Object in styles)
newStyles[val] = styles[val];
writableTextLayoutFormatValueHolder().userStyles = newStyles;
formatChanged(); // modelChanged(ModelChange.USER_STYLE_CHANGED,0,this.textLength,true);
}
/** Returns the <code>coreStyles</code> on this ContainerController. Note that the getter makes a copy of the core
* styles dictionary. The coreStyles object encapsulates those formats that are defined by TextLayoutFormat. The
* <code>coreStyles</code> object consists of an array of <em>stylename-value</em> pairs.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get coreStyles():Object
{
var styles:Object = _formatValueHolder == null ? null : _formatValueHolder.coreStyles;
return styles ? Property.shallowCopy(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 _formatValueHolder; }
public function set format(value:ITextLayoutFormat):void
{
formatInternal = value;
formatChanged();
}
private function writableTextLayoutFormatValueHolder():FlowValueHolder
{
if (_formatValueHolder == null)
_formatValueHolder = new FlowValueHolder();
return _formatValueHolder;
}
/** Sets the _format data member. No side effects.
* @private
*/
tlf_internal function set formatInternal(value:ITextLayoutFormat):void
{
if (value == null)
{
if (_formatValueHolder == null || _formatValueHolder.coreStyles == null)
return; // no change
_formatValueHolder.coreStyles = null;
}
else
writableTextLayoutFormatValueHolder().format = value;
}
/** 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[styleProp];
return getUserStyleWorker(styleProp);
}
/**
* 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] !== undefined)
this[styleProp] = newValue;
else
{
_formatValueHolder.setUserStyle(styleProp,newValue);
formatChanged(); // modelChanged(ModelChange.USER_STYLE_CHANGED,0,this.textLength,true);
}
}
/** 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); }
/** @private worker function - any styleProp */
tlf_internal function getUserStyleWorker(styleProp:String):*
{
CONFIG::debug { assert(TextLayoutFormat.description[styleProp] === undefined,"bad call to getUserStyleWorker"); }
var userStyle:* = _formatValueHolder.getUserStyle(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;
}
/**
* 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:TextLayoutFormatValueHolder = _rootElement ? TextLayoutFormatValueHolder(_rootElement.computedFormat): null;
_computedFormat = FlowElement.createTextLayoutFormatPrototype(formatForCascade,parentPrototype);
resetColumnState();
}
return _computedFormat;
}
/** @private */
tlf_internal function get formatForCascade():TextLayoutFormatValueHolder
{
if (_rootElement)
{
var tf:TextFlow = _rootElement.getTextFlow();
if (tf)
{
var elemStyle:TextLayoutFormatValueHolder = tf.getTextLayoutFormatStyle(this);
if (elemStyle)
{
var localFormat:ITextLayoutFormat = _formatValueHolder;
if (localFormat == null)
return elemStyle;
var rslt:TextLayoutFormatValueHolder = new TextLayoutFormatValueHolder(elemStyle);
rslt.apply(localFormat);
return rslt;
}
}
}
return _formatValueHolder;
}
/** @private */
tlf_internal function lineIsVisible(wmode:String, scrollXTW:int, scrollYTW:int, scrollWidthTW:int, scrollHeightTW:int, textFlowLine:TextFlowLine):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.
var textLine:TextLine;
if (!textFlowLine.hasLineBounds())
{
textLine = textFlowLine.createShape(wmode);
if (textLine.numChildren == 0)
{
// Get it the new way
if (wmode == BlockProgression.TB)
textFlowLine.setLineBounds(Twips.to(textLine.x), Twips.to(textLine.y - textLine.ascent), Twips.to(textLine.textWidth), Twips.to(textLine.textHeight));
else
textFlowLine.setLineBounds(Twips.to(textLine.x - textLine.descent), Twips.to(textLine.y), Twips.to(textLine.textHeight), Twips.to(textLine.textWidth));
}
else // Phase this out after composition is updated to handle inline case correctly
{
var lineBounds:Rectangle = getPlacedTextLineBounds(textLine);
textFlowLine.setLineBounds(Twips.to(lineBounds.x), Twips.to(lineBounds.y), Twips.to(lineBounds.width), Twips.to(lineBounds.height));
}
}
var isVisible:Boolean = textFlowLine.isLineVisible(wmode, scrollXTW, scrollYTW, scrollWidthTW, scrollHeightTW);
if (!isVisible)
return null;
if (!textLine)
textLine = textFlowLine.createShape(wmode);
return textLine;
}
/* CONFIG::debug private function oldLineIsVisible(wmode:String, scrollAdjustRect:Rectangle, textLine:TextLine):Boolean
{
var curBounds:Rectangle = getPlacedTextLineBounds(textLine);
// trace("fillShapeChildren:",lineIndex.toString(),curBounds.toString(),textLine.x.toString(),textLine.y.toString(),scrollRect.toString());
return ((wmode == BlockProgression.RL) ? curBounds.x + curBounds.width >= scrollAdjustRect.left && curBounds.x < scrollAdjustRect.x + scrollAdjustRect.width :
curBounds.y + curBounds.height >= scrollAdjustRect.top && curBounds.y < scrollAdjustRect.y + scrollAdjustRect.height)
} */
/** @private */
tlf_internal function getPlacedTextLineBounds(textLine:TextLine):Rectangle
{
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);
}
return curBounds;
}
/** @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; }
}