//////////////////////////////////////////////////////////////////////////////// // // 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; 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. * *
* * @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:ITextLayoutFormat; // 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 * *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.
* * @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 defaultcompositionWidth
* and compositionHeight
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 IFlowComposer.compose()
or IFlowComposer.updateAllControllers()
.
*
* @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 textFlow
and
* rootElement
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 compose()
or
* updateAllControllers()
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 compose()
* or updateAllControllers()
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 wmode:String = effectiveBlockProgression;
var width:Number = _measureWidth ? _contentWidth : _compositionWidth;
var height:Number = _measureHeight ? _contentHeight : _compositionHeight;
var scrollAdjustRect:Rectangle;
if (wmode == BlockProgression.RL)
scrollAdjustRect = new Rectangle(_xScroll - width, _yScroll, width, height);
else
scrollAdjustRect = new Rectangle(_xScroll, _yScroll, width, 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 = curLine.createShape(wmode);
if (!textLine)
continue;
CONFIG::debug { assert(textLine == curLine.peekTextLine(),"Bad textLine in fillShapeChildren "+Debugging.getIdentity(textLine)+" "+Debugging.getIdentity(curLine)); }
var curBounds:Rectangle = getPlacedTextLineBounds(textLine);
// trace("fillShapeChildren:",lineIndex.toString(),curBounds.toString(),textLine.x.toString(),textLine.y.toString(),scrollRect.toString());
if ((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)
{
if (adjustLines)
{
textLine.x -= scrollAdjustRect.x;
textLine.y -= scrollAdjustRect.y;
}
sc.push(textLine);
if (textLine.parent == null)
tempSprite.addChild(textLine);
}
}
if (adjustLines)
{
_contentLeft -= scrollAdjustRect.x;
_contentTop -= scrollAdjustRect.y;
}
}
//--------------------------------------------------------------------------
//
// 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.
textFlow.dispatchEvent(new TextLayoutEvent(TextLayoutEvent.SCROLL));
// trace("contentHeight", contentHeight, "contentWidth", contentWidth);
// trace("contentHeight", contentHeight, "contentWidth", contentWidth);
}
/** @private */
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
var textFlowLine:TextFlowLine;
// find the index of the first line
for (var flowComposerIndex:int = 0; flowComposerIndex < flowComposer.numLines; flowComposerIndex++)
{
textFlowLine = flowComposer.getLineAt(flowComposerIndex);
if (textFlowLine.peekTextLine() == textLine)
break;
}
assert(flowComposerIndex != flowComposer.numLines,"BAD FIRST LINE IN CONTAINER");
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);
assert(textLine == textFlowLine.peekTextLine(),"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 firstVisibleLine:TextFlowLine;
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.
var a:Array = findFirstAndLastVisibleLine();
firstVisibleLine = a[0];
lastVisibleLine = a[1];
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.
var b:Array = findFirstAndLastVisibleLine();
firstVisibleLine = b[0];
lastVisibleLine = b[1];
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.
* You can override this method to define a custom context menu.
* * @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 findFirstAndLastVisibleLine():Array { var firstLine:int = flowComposer.findLineIndexAtPosition(absoluteStart); var lastLine:int = flowComposer.findLineIndexAtPosition(absoluteStart + _textLength - 1); var lastColumn:int = _columnState.columnCount - 1; var firstVisibleLine:TextFlowLine; var lastVisibleLine:TextFlowLine; // no visible lines? if (firstLine == flowComposer.numLines) return [null,null]; for (var lineIndex:int = firstLine; lineIndex <= lastLine; lineIndex++) { var curLine:TextFlowLine = flowComposer.getLineAt(lineIndex); if (curLine.controller != this) continue; // skip until we find the lines in the last column if (curLine.columnIndex != lastColumn) continue; if (curLine.textLineExists) { var curTextLine:TextLine = curLine.getTextLine(); if (curTextLine && curTextLine.parent) { if (!firstVisibleLine) firstVisibleLine = curLine; lastVisibleLine = curLine; } } } return [firstVisibleLine, lastVisibleLine]; } /** * 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. * *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.
* * @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 a:Array = findFirstAndLastVisibleLine(); var firstVisibleLine:TextFlowLine = a[0]; var lastVisibleLine:TextFlowLine = a[1]; // 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 theMouseEvent.MOUSE_OVER
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 MouseEvent.MOUSE_OUT
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 MouseEvent.MOUSE_WHEEL
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 MouseEvent.MOUSE_DOWN
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 MouseEvent.MOUSE_UP
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 MouseEvent.MOUSE_MOVE
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 MouseEvent.DOUBLE_CLICK
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 function getContainerController(container:DisplayObject):ContainerController
{
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;
}
return null;
}
/**
* Processes the FocusEvent.KEY_FOCUS_CHANGE
and FocusEvent.MOUSE_FOCUS_CHANGE
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 FocusEvent.FOCUS_IN
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 FocusEvent.FOCUS_OUT
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 Event.ACTIVATE
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 Event.DEACTIVATE
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 KeyboardEvent.KEY_DOWN
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 Keyboard.KEY_UP
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 FocusEvent.KEY_FOCUS_CHANGE
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 TextEvent.TEXT_INPUT
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 IMEEvent.IME_START_COMPOSITION
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 ContextMenuEvent.MENU_SELECT
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 flashx.textLayout.elements.TextFlow TextFlow
*/
static public function get containerControllerInitialFormat():ITextLayoutFormat
{ return _containerControllerInitialFormat; }
static public function set containerControllerInitialFormat(val:ITextLayoutFormat):void
{ _containerControllerInitialFormat = val; }
/** @private */
protected function get attachTransparentBackground():Boolean
{ return true; }
/** @private */
tlf_internal function clearCompositionResults():void
{
setTextLength(0);
for each (var textLine:TextLine in _shapeChildren)
{
removeTextLine(textLine);
CONFIG::debug { Debugging.traceFTECall(null,_container,"removeTextLine",textLine); }
}
_shapeChildren.length = 0;
}
/** 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 flash.text.engine.TextLine
child of container
* if one exists, and that of the last child of container
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 flash.text.engine.TextLine
object as a descendant of container
.
* The default implementation of this method, which may be overriden, adds the object
* as a direct child of container
at the specified index.
*
* @param textLine the flash.text.engine.TextLine
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 flash.text.engine.TextLine
object from its parent.
* The default implementation of this method, which may be overriden, removes the object
* from container
if it is a direct child of the latter.
*
* This method may be called even if the object is not a descendant of container
.
* Any implementation of this method must ensure that no action is taken in this case.
*
* @param textLine the flash.text.engine.TextLine
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 flash.display.Shape
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 container
* just before the first flash.text.engine.TextLine
child, if one exists, and after the last exisiting
* child otherwise.
*
* @param shape flash.display.Shape
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 flash.display.DisplayObjectContainer
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 flash.text.engine.TextLine
child of container
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 container
.
*
* @param selectionContainer flash.display.DisplayObjectContainer
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 flash.display.DisplayObjectContainer
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 flash.display.DisplayObjectContainer
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 userStyles
object for a ContainerController instance. The getter makes a copy of the
* userStyles
object, which is an array of stylename-value 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 coreStyles
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
* coreStyles
object consists of an array of stylename-value 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 styleProp
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 styleProp
parameter to the value
* specified by the newValue
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 styleProp
from this FlowElement. Sets the value to
* undefined
.
*
* @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)
{
FlowElement._scratchTextLayoutFormat.format = formatForCascade;
var element:FlowElement = _rootElement;
if (element)
{
while (1)
{
var attrs:ITextLayoutFormat = ContainerFormattedElement(element).formatForCascade;
if (attrs)
FlowElement._scratchTextLayoutFormat.concatInheritOnly(attrs);
if (element.parent == null)
break;
element = element.parent;
}
}
var defaultFormat:ITextLayoutFormat;
var defaultFormatHash:uint;
var tf:TextFlow = element as TextFlow;
if (tf)
{
defaultFormat = tf.getDefaultFormat();
defaultFormatHash = tf.getDefaultFormatHash();
}
_computedFormat= TextFlow.getCanonical(FlowElement._scratchTextLayoutFormat,defaultFormat,defaultFormatHash);
resetColumnState();
}
return _computedFormat;
}
/** @private */
tlf_internal function get formatForCascade():ITextLayoutFormat
{
if (_rootElement)
{
var tf:TextFlow = _rootElement.getTextFlow();
if (tf)
{
var elemStyle:ITextLayoutFormat = tf.getTextLayoutFormatStyle(this);
if (elemStyle)
{
var localFormat:ITextLayoutFormat = format;
if (localFormat == null)
return elemStyle;
var rslt:TextLayoutFormat = new TextLayoutFormat(elemStyle);
rslt.apply(localFormat);
return rslt;
}
}
}
return format;
}
/** @private */
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);
}
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; }
}