//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package flashx.textLayout.elements { import flash.display.DisplayObjectContainer; import flash.text.engine.ContentElement; import flash.text.engine.GroupElement; import flash.utils.getQualifiedClassName; import flashx.textLayout.compose.FlowDamageType; import flashx.textLayout.container.ContainerController; import flashx.textLayout.debug.assert; import flashx.textLayout.events.ModelChange; import flashx.textLayout.tlf_internal; use namespace tlf_internal; [DefaultProperty("mxmlChildren")] /** * The FlowGroupElement class is the base class for FlowElement objects that can have an array of children. These classes include * TextFlow, ParagraphElement, DivElement, and LinkElement. * *

You cannot create a FlowGroupElement object directly. Invoking new FlowGroupElement() throws an error * exception.

* * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 * * @see DivElement * @see LinkElement * @see ParagraphElement * @see TextFlow */ public class FlowGroupElement extends FlowElement { /** children of the FlowGroupElement. They must all be FlowElements. Depending on _numChildren either store a single child in _singleChild or multiple children in the array. */ private var _childArray:Array; private var _singleChild:FlowElement; private var _numChildren:int; /** Base class - invoking new FlowGroupElement() throws an error exception. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function FlowGroupElement() { _numChildren = 0; } /** @private */ public override function deepCopy(startPos:int = 0, endPos:int = -1):FlowElement { if (endPos == -1) endPos = textLength; var retFlow:FlowGroupElement = shallowCopy(startPos, endPos) as FlowGroupElement; var newFlowElement:FlowElement; for (var idx:int = 0; idx < _numChildren; idx++) { var child:FlowElement = getChildAt(idx); if (((startPos - child.parentRelativeStart) < child.textLength) && ((endPos - child.parentRelativeStart) > 0)) { //child is in Selected area newFlowElement = child.deepCopy(startPos - child.parentRelativeStart, endPos - child.parentRelativeStart); retFlow.replaceChildren(retFlow.numChildren,retFlow.numChildren,newFlowElement); if (retFlow.numChildren > 1) { var possiblyEmptyFlowElement:FlowElement = retFlow.getChildAt(retFlow.numChildren - 2); if (possiblyEmptyFlowElement.textLength == 0) { retFlow.replaceChildren(retFlow.numChildren - 2, retFlow.numChildren - 1); } } } } return retFlow; } /* @private */ public override function getText(relativeStart:int=0, relativeEnd:int=-1, paragraphSeparator:String="\n"):String { var text:String = super.getText(); if (relativeEnd == -1) relativeEnd = textLength; var pos:int = relativeStart; for (var idx:int = findChildIndexAtPosition(relativeStart); idx < _numChildren && pos < relativeEnd; idx++) { var child:FlowElement = getChildAt(idx); var copyStart:int = pos - child.parentRelativeStart; var copyEnd:int = Math.min(relativeEnd - child.parentRelativeStart, child.textLength); text += child.getText(copyStart, copyEnd, paragraphSeparator); pos += copyEnd - copyStart; if (paragraphSeparator && child is ParagraphFormattedElement && pos < relativeEnd) text += paragraphSeparator; } return text; } // **************************************** // Begin TextLayoutFormat Related code // **************************************** /** @private */ tlf_internal override function formatChanged(notifyModelChanged:Boolean = true):void { super.formatChanged(notifyModelChanged); for (var idx:int = 0; idx < _numChildren; idx++) { var child:FlowElement = getChildAt(idx); child.formatChanged(false); } } // **************************************** // End TLFFormat Related code // **************************************** // **************************************** // Begin import helper code // **************************************** [RichTextContent] /** * Appends an array of children to this object. Uses the replaceChildren() method to append each * element in the array. Intended for use during an mxml compiled import. * * @throws TypeError if array element is not a FlowElement or String * @param array - array of children to attach. Each element of the array must be a FlowElement object or a String. * @see FlowGroupElement#replaceChildren() * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function get mxmlChildren():Array { return _numChildren == 0 ? null : (_numChildren == 1 ? [ this._singleChild ] : _childArray.slice() ); } public function set mxmlChildren(array:Array):void { /* NOTE: all FlowElement implementers and overrides of mxmlChildren must specify [RichTextContent] metadata */ // remove all existing children this.replaceChildren(0,_numChildren); // In the text model, non-ParagraphFormattedElements (i.e. spans, images, links, TCY) cannot be children of a ContainerFormattedElement (TextFlow, DivElement etc.) // They can only be children of paragraphs or subparagraph blocks. // In XML, however,

elements can be implied (for example, a may appear as a direct child of ). // So, while parsing the XML, if we enounter a non-ParagraphFormattedElement child of a ContainerFormattedElement // 1. an explicitly created paragraph is used as the parent instead // 2. such explicitly created paragraphs are shared by adjacent flow elements provided there isn't an intervening ParagraphFormattedElement var effectiveParent:FlowGroupElement = this; // append them on the end for each (var child:Object in array) { if (child is FlowElement) { if (child is ParagraphFormattedElement) { // Reset due to possibly intervening FlowParagrpahElement; See note 2. above effectiveParent = this; } else if (effectiveParent is ContainerFormattedElement) { // See note 1. above effectiveParent = new ParagraphElement(); effectiveParent.impliedElement = true; replaceChildren(_numChildren, _numChildren, effectiveParent); } if ( (child is SpanElement) || (child is SubParagraphGroupElement)) child.bindableElement = true; effectiveParent.replaceChildren(effectiveParent.numChildren, effectiveParent.numChildren, FlowElement(child) ); } else if (child is String) { var s:SpanElement = new SpanElement(); s.text = String(child); s.bindableElement = true; s.impliedElement = true; if (effectiveParent is ContainerFormattedElement) { // See note 1. above effectiveParent = new ParagraphElement(); replaceChildren(_numChildren, _numChildren, effectiveParent); effectiveParent.impliedElement = true; } effectiveParent.replaceChildren(effectiveParent.numChildren, effectiveParent.numChildren, s); } else if (child != null) throw new TypeError(GlobalSettings.resourceStringFunction("badMXMLChildrenArgument",[ getQualifiedClassName(child) ])); } } /** @private */ override tlf_internal function createGeometry(parentToBe:DisplayObjectContainer):void { for (var idx:int = 0; idx < _numChildren; idx++) { var child:FlowElement = getChildAt(idx); child.createGeometry(parentToBe); } } // **************************************** // End import helper code // **************************************** // **************************************** // Begin tree navigation code // **************************************** /** * Returns the number of FlowElement children that this FlowGroupElement object has. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function get numChildren(): int { return _numChildren; } /** * Searches in children for the specified FlowElement object and returns its index position. * * @param child The FlowElement object item to locate among the children. * @return The index position of the specified chilc. If child is not found, returns -1. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function getChildIndex(child:FlowElement):int { var hi:int = _numChildren-1; // one hole here - if child is null and this has no children then we'll return 0 if (hi <= 0) return _singleChild == child ? 0 : -1; var lo:int = 0; while (lo <= hi) { var mid:int = (lo+hi)/2; var p:FlowElement = _childArray[mid]; if (p.parentRelativeStart == child.parentRelativeStart) { // during intermediate caluclations there are zero length elements lurking about if (p == child) { CONFIG::debug { assert(_childArray.indexOf(child) == mid,"Bad getChildIndex"); } return mid; } var testmid:int; if (p.textLength == 0) { // look forward for a match for (testmid = mid; testmid < _numChildren; testmid++) { p = _childArray[testmid]; if (p == child) { CONFIG::debug { assert(_childArray.indexOf(child) == testmid,"Bad getChildIndex"); } return testmid; } if (p.textLength != 0) break; } } // look backwards while (mid > 0) { mid--; p = _childArray[mid]; if (p == child) { CONFIG::debug { assert(_childArray.indexOf(child) == mid,"Bad getChildIndex"); } return mid; } if (p.textLength != 0) break; } CONFIG::debug { assert(_childArray.indexOf(child) == -1,"Bad getChildIndex"); } return -1; } if (p.parentRelativeStart < child.parentRelativeStart) lo = mid+1; else hi = mid-1; } CONFIG::debug { assert(_childArray.indexOf(child) == -1,"Bad getChildIndex"); } return -1; } /** * Returns the FlowElement child at the specified index. * * @param index the position at which to find the FlowElement object. * * @return the child FlowElement object at the specified position. * @includeExample examples\FlowGroupElement_getChildAtExample.as -noswf * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function getChildAt(index:int):FlowElement { if (_numChildren > 1) return _childArray[index]; return index == 0 ? _singleChild : null; } /** @private */ tlf_internal function getNextLeafHelper(limitElement:FlowGroupElement,child:FlowElement):FlowLeafElement { var idx:int = getChildIndex(child); if (idx == -1) return null; // bug? if (idx == _numChildren-1) { if (limitElement == this || !parent) return null; return parent.getNextLeafHelper(limitElement,this); } var child:FlowElement = getChildAt(idx+1); return (child is FlowLeafElement) ? FlowLeafElement(child) : FlowGroupElement(child).getFirstLeaf(); } /** @private */ tlf_internal function getPreviousLeafHelper(limitElement:FlowGroupElement,child:FlowElement):FlowLeafElement { var idx:int = getChildIndex(child); if (idx == -1) return null; // bug? if (idx == 0) { if (limitElement == this || !parent) return null; return parent.getPreviousLeafHelper(limitElement,this); } var child:FlowElement = getChildAt(idx-1); return (child is FlowLeafElement) ? FlowLeafElement(child) : FlowGroupElement(child).getLastLeaf(); } /** * Given a relative text position, find the leaf element that contains the position. * *

Looks down the flow element hierarchy to find the FlowLeafElement that * contains the specified position. The specified position * is relative to this FlowElement object.

* * @param relativePosition relative text index to look up. * @return the leaf element containing the relative position. * * @includeExample examples\FlowGroupElement_findLeafExample.as -noswf * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function findLeaf(relativePosition:int):FlowLeafElement { var found:FlowLeafElement = null; var childIdx:int = findChildIndexAtPosition(relativePosition); if (childIdx != -1) { // childIdx is index of the first child containing pos. Many of its following siblings // may also contain pos if their respective previous siblings are zero-length. // Check them all until a leaf containing pos is found. do { var child:FlowElement = this.getChildAt(childIdx++); if (!child) break; var childRelativePos: int = relativePosition - child.parentRelativeStart; if (child is FlowGroupElement) found = FlowGroupElement(child).findLeaf(childRelativePos); else { // if its not a FlowGroupElement than it must be a FlowLeafElement CONFIG::debug { assert(child is FlowLeafElement,"Invalid child in FlowGroupElement.findLeaf"); } if (childRelativePos >= 0 && childRelativePos < child.textLength || (child.textLength == 0 && _numChildren == 1)) found = FlowLeafElement(child); } } while (!found && !child.textLength); } return found; } /** * Given a relative text position, find the index of the first child FlowElement that contains the relative position. * More than one child can contain relative position because of zero length FlowElements. * *

Examine the children to find the FlowElement that contains the relative position. The supplied relative position * is relative to this FlowElement.

* * @param relativePosition the position relative to this element * @return index of first child element containing relativePosition * * @includeExample examples\FlowGroupElement_findChildIndexAtPositionExample.as -noswf * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function findChildIndexAtPosition(relativePosition:int):int { var lo:int = 0; var hi:int = _numChildren-1; while (lo <= hi) { var mid:int = (lo+hi)/2; var child:FlowElement = getChildAt(mid); if (child.parentRelativeStart <= relativePosition) { // always return the first zero length element in the list if (child.parentRelativeStart == relativePosition) { while (mid != 0) { child = getChildAt(mid-1); if (child.textLength != 0) break; mid--; } return mid; } if (child.parentRelativeStart + child.textLength > relativePosition) return mid; lo = mid+1; } else hi = mid-1; } return -1; } /** * Returns the first FlowLeafElement descendant of this group. * * @return the first FlowLeafElement object. * * @includeExample examples\FlowGroupElement_getFirstLeafExample.as -noswf * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function getFirstLeaf(): FlowLeafElement { if (_numChildren > 1) { for (var idx:int = 0; idx < _numChildren; idx++) { var child:FlowElement = _childArray[idx]; var leaf:FlowLeafElement = (child is FlowGroupElement) ? FlowGroupElement(child).getFirstLeaf() : FlowLeafElement(child); if (leaf) return leaf; } return null; } return _numChildren == 0 ? null : ((_singleChild is FlowGroupElement) ? FlowGroupElement(_singleChild).getFirstLeaf() : FlowLeafElement(_singleChild)); } /** * Returns the last FlowLeafElement descendent of this group. * * @return the last FlowLeafElement object. * * @includeExample examples\FlowGroupElement_getLastLeafExample.as -noswf * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function getLastLeaf(): FlowLeafElement { if (_numChildren > 1) { for (var idx:int = _numChildren; idx != 0; idx--) { var child:FlowElement = _childArray[idx-1]; var leaf:FlowLeafElement = (child is FlowGroupElement) ? FlowGroupElement(child).getLastLeaf() : FlowLeafElement(child) ; if (leaf) return leaf; } return null; } return _numChildren == 0 ? null : ((_singleChild is FlowGroupElement) ? FlowGroupElement(_singleChild).getLastLeaf() : FlowLeafElement(_singleChild)); } /** @private */ public override function getCharAtPosition(relativePosition:int):String { var leaf:FlowLeafElement = findLeaf(relativePosition); return leaf ? leaf.getCharAtPosition(relativePosition-leaf.getElementRelativeStart(this)) : ""; } /** @private return an element or matching idName. */ tlf_internal override function getElementByIDHelper(idName:String):FlowElement { var rslt:FlowElement = super.getElementByIDHelper(idName); if (rslt == null) { for (var idx:int = 0; idx < _numChildren; idx++) { var child:FlowElement = getChildAt(idx); rslt = child.getElementByIDHelper(idName); if (rslt != null) break; } } return rslt; } /** @private return adding elements with matching styleName to an array. */ tlf_internal override function getElementsByStyleNameHelper(a:Array,styleName:String):void { super.getElementsByStyleNameHelper(a,styleName); for (var idx:int = 0; idx < _numChildren; idx++) { var child:FlowElement = getChildAt(idx); child.getElementsByStyleNameHelper(a,styleName); } } // **************************************** // End tree navigation code // **************************************** // **************************************** // Begin tree modification support code // **************************************** /** @private */ tlf_internal function removeBlockElement(child:FlowElement, block:ContentElement):void { // when Image's are moved into ParagraphElement's this assertion should always fire CONFIG::debug { assert(child is InlineGraphicElement,"invalid call to removeBlockElement"); } } /** @private */ tlf_internal function insertBlockElement(child:FlowElement, block:ContentElement):void { // when Image's are moved into ParagraphElement's this assertion should always fire CONFIG::debug { assert(child is InlineGraphicElement,"invalid call to insertBlockElement"); } } /** @private * True if there is a corresponding FTE data structure currently instantiated. */ tlf_internal function hasBlockElement():Boolean { CONFIG::debug { assert(false,"invalid call to hasBlockElement"); } return false; } /** @private */ tlf_internal function createContentAsGroup():GroupElement { CONFIG::debug { assert(false,"invalid call to createContentAsGroup"); } return null; } /** @private This is only called from SpanElement.splitAtPosition */ tlf_internal function addChildAfterInternal(child:FlowElement, newChild:FlowElement):void { //this function was kept for efficiency purposes. It is used by splitForChange //which in turn is used by applyCharacterFormat, when changing the //attributes applied to characters. In the end, the length of the document //will be the same. So, without this fnction, we would be creating a new //span, updating the lengths, and then removing a part of the span and updating //the lengths again (getting the same exact lengths we had before). This can be //inefficient. So, this function does everything addChildAfter does, without //updating the lengths. This is an internal function since the user really has //to know what they're doing and will not be exposed as a public API CONFIG::debug { assert(_numChildren != 0, "addChildAfter must have children"); } CONFIG::debug { assert(getChildIndex(child) != -1, "addChildAfter: before child must be in array"); } if (_numChildren > 1) { // TODO: binary search for indexOf child CONFIG::debug { assert(_childArray.indexOf(child) != -1,"Bad call to addChildAfterInternal"); } _childArray.splice(_childArray.indexOf(child)+1,0,newChild); } else { // not found returns above returns -1 so behave the same CONFIG::debug { assert(_singleChild == child,"Bad call to addChildAfterInternal"); } _childArray = [ _singleChild, newChild ]; _singleChild = null; } _numChildren++; newChild.setParentAndRelativeStartOnly(this,child.parentRelativeEnd); } /** * Helper for replaceChildren. Determines if elem can legally be a child of this. * @return true --> ok, false--> not a legal child * @private */ tlf_internal function canOwnFlowElement(elem:FlowElement):Boolean { return !(elem is TextFlow) && !(elem is FlowLeafElement) && !(elem is SubParagraphGroupElement); } /** @private */ private static function getNestedArgCount(obj:Object):uint { return (obj is Array) ? obj.length : 1; } /** @private */ private static function getNestedArg(obj:Object, index:uint):FlowElement { CONFIG::debug { assert(index < getNestedArgCount(obj),"bad index to getNestedArg"); }; return ((obj is Array) ? obj[index] : obj) as FlowElement; } /** * Replaces child elements in the group with the specified new elements. Use the beginChildIndex and * endChildIndex parameters to govern the operation as follows: *

*

Otherwise, this method replaces the specified elements, starting with the element at beginChildIndex * and up to but not including endChildIndex.

* * @param beginChildIndex The index value for the start position of the replacement range in the children array. * @param endChildIndex The index value following the end position of the replacement range in the children array. * @param rest The elements to replace the specified range of elements. Can be a sequence containing flow elements or * arrays or vectors thereof. * * @throws RangeError The beginChildIndex or endChildIndex specified is out of range. * * @includeExample examples\FlowGroupElement_replaceChildrenExample.as -noswf * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function replaceChildren(beginChildIndex:int, endChildIndex:int, ...rest):void { if (beginChildIndex > _numChildren || endChildIndex > _numChildren) throw RangeError(GlobalSettings.resourceStringFunction("badReplaceChildrenIndex")); var thisAbsStart:int = getAbsoluteStart(); var absStartIdx:int = thisAbsStart + (beginChildIndex == _numChildren ? textLength : getChildAt(beginChildIndex).parentRelativeStart); var relStartIdx:int = beginChildIndex == _numChildren ? textLength : getChildAt(beginChildIndex).parentRelativeStart; // deletion phase if (beginChildIndex < endChildIndex) { var child:FlowElement; // scratch variable var len:int = 0; while (beginChildIndex < endChildIndex) { child = this.getChildAt(beginChildIndex); child.modelChanged(ModelChange.ELEMENT_REMOVAL, 0, child.textLength); len += child.textLength; child.setParentAndRelativeStart(null,0); if (_numChildren == 1) { _singleChild = null; _numChildren = 0; } else { _childArray.splice(beginChildIndex,1); _numChildren--; if (_numChildren == 1) { _singleChild = _childArray[0]; _childArray = null; } } endChildIndex--; } if (len) { // TODO: this code should move into updateLengths. updateLengths needs a rewrite // as it assumes that any element that is removed has its length set to zero and updateLengths // is called on that element first. replaceChildren doesn't do that - it just removes the element // until rewrite reuse endChildIndex and update start of all following elements while (endChildIndex < _numChildren) { child = getChildAt(endChildIndex); child.setParentRelativeStart(child.parentRelativeStart-len); endChildIndex++; } // update lengths updateLengths(absStartIdx,-len,true); deleteContainerText(relStartIdx + len,len); } } CONFIG::debug { assert(thisAbsStart == getAbsoluteStart(),"replaceChildren: Bad thisAbsStart"); } var childrenToAdd:int = 0; // number of children to add var flatNewChildList:Array; // stores number of children when > 1 var newChildToAdd:FlowElement; // stores a single child to add - avoids creating an Array for the 99% case var newChild:FlowElement; // scratch var idx:int; // scratch for each (var obj:Object in rest) { if (!obj) continue; var numNestedArgs:int = getNestedArgCount(obj); for (idx = 0; idx 1) _childArray.splice(beginChildIndex,0,newChild); else { _childArray = beginChildIndex == 0 ? [ newChild, _singleChild ] : [ _singleChild, newChild ]; _singleChild = null; } _numChildren++; newChild.setParentAndRelativeStart(this,relStartIdx+addedTextLength); addedTextLength += newChild.textLength; beginChildIndex++; // points to the next slot } CONFIG::debug { assert(thisAbsStart == getAbsoluteStart(),"replaceChildren: Bad thisAbsStart"); } if (addedTextLength) { // update following elements - see comment above. // it would be best if this loop only ran once while (beginChildIndex < _numChildren) { child = getChildAt(beginChildIndex++); child.setParentRelativeStart(child.parentRelativeStart+addedTextLength); } updateLengths(absStartIdx,addedTextLength,true); var enclosingContainer:ContainerController = getEnclosingController(relStartIdx); if (enclosingContainer) ContainerController(enclosingContainer).setTextLength(enclosingContainer.textLength + addedTextLength); } for (idx = 0; idx < childrenToAdd; idx++) { newChild = childrenToAdd == 1 ? newChildToAdd : flatNewChildList[idx]; newChild.modelChanged(ModelChange.ELEMENT_ADDED, 0, newChild.textLength); } } else { var tFlow:TextFlow = getTextFlow(); if (tFlow != null) { // beginChildIndex points to the next element // use scratch idx as "damageStart" if (beginChildIndex < _numChildren) { // first, look for the next element and damage the beginning. idx = thisAbsStart + getChildAt(beginChildIndex).parentRelativeStart; } else if (beginChildIndex > 1) { // damage the end of the previous element newChild = getChildAt(beginChildIndex-1); idx = thisAbsStart + newChild.parentRelativeStart + newChild.textLength - 1; } else { // damage the very end of the textFlow idx = thisAbsStart; if (idx >= tFlow.textLength) idx--; } tFlow.damage(idx, 1, FlowDamageType.GEOMETRY, false); } } } /** * Appends a child FlowElement object. The new child is added to the end of the children list. * * @param child The child element to append. * * @return the added child FlowElement * * @includeExample examples\FlowGroupElement_addChildExample.as -noswf * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function addChild(child:FlowElement):FlowElement { replaceChildren(_numChildren, _numChildren, child); return child; } /** * Adds a child FlowElement object at the specified index position. * * @param The index of the position at which to add the child element, with the first position being 0. * @param child The child element to add. * @throws RangeError The index is out of range. * * @return the added child FlowElement * * @includeExample examples\FlowGroupElement_addChildAtExample.as -noswf * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 * */ public function addChildAt(index:uint, child:FlowElement):FlowElement { replaceChildren(index, index, child); return child; } /** * Removes the specified child FlowElement object from the group. * * @param child The child element to remove. * @throws ArgumentError The child is not found. * * @return the removed child FlowElement object * * @includeExample examples\FlowGroupElement_removeChildExample.as -noswf * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 * */ public function removeChild(child:FlowElement):FlowElement { var index:int = getChildIndex(child); if (index == -1) throw ArgumentError(GlobalSettings.resourceStringFunction("badRemoveChild")); removeChildAt(index); return child; } /** * Removes the child FlowElement object at the specified index position. * * @param index position at which to remove the child element. * @throws RangeError The index is out of range. * * @return the child FlowElement object removed from the specified position. * * @includeExample examples\FlowGroupElement_removeChildAtExample.as -noswf * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function removeChildAt(index:uint):FlowElement { var childToReplace:FlowElement = getChildAt(index); replaceChildren(index, index+1); return childToReplace; } /** * Splits this object at the position specified by the childIndex parameter. If this group element has * a parent, creates a shallow copy of this object and replaces its children with the elements up to the index. Moves * elements following childIndex into the copy. * * @return the new FlowGroupElement object. * @throws RangeError if childIndex is greater than the length of the children. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function splitAtIndex(childIndex:int):FlowGroupElement { if (childIndex > _numChildren) throw RangeError(GlobalSettings.resourceStringFunction("invalidSplitAtIndex")); var newSibling:FlowGroupElement = shallowCopy() as FlowGroupElement; var numChildrenToMove:int = _numChildren-childIndex; if (numChildrenToMove == 1) newSibling.addChild(removeChildAt(childIndex)); else if (numChildrenToMove != 0) { var childArray:Array = _childArray.slice(childIndex); this.replaceChildren(childIndex,_numChildren-1); newSibling.replaceChildren(0, 0, childArray); } if (parent) { var myidx:int = parent.getChildIndex(this); parent.replaceChildren(myidx+1,myidx+1,newSibling); } return newSibling; } /** * Splits this object at the position specified by the relativePosition parameter, where * the relative position is a relative text position in this element. * * @throws RangeError if relativePosition is greater than textLength, or less than 0. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 * * @private */ public override function splitAtPosition(relativePosition:int):FlowElement { // Creates a shallowCopy of this and adds it to parent after this. // Moves elements from characterIndex forward into the copy // returns the new shallowCopy if ((relativePosition < 0) || (relativePosition > textLength)) throw RangeError(GlobalSettings.resourceStringFunction("invalidSplitAtPosition")); var curElementIdx:int; if (relativePosition == textLength) curElementIdx = _numChildren; else { curElementIdx = findChildIndexAtPosition(relativePosition); var curFlowElement:FlowElement = getChildAt(curElementIdx); if (curFlowElement.parentRelativeStart != relativePosition) { if (curFlowElement is FlowGroupElement) { FlowGroupElement(curFlowElement).splitAtPosition(relativePosition - curFlowElement.parentRelativeStart); } else { //I would imagine that it has to be a span. That's the only non-FlowGroupElement //type that can take up more than a textLength of 1. CONFIG::debug { assert(curFlowElement is SpanElement, "SpanElements are the only leaf elements that can currently have > 1 textLength"); } SpanElement(curFlowElement).splitAtPosition(relativePosition - curFlowElement.parentRelativeStart); } //increase by one. It's the new element that we want to move over. curElementIdx++; } } //increase by one. It's the new element that we want to move over. return splitAtIndex(curElementIdx); } /** @private */ tlf_internal override function normalizeRange(normalizeStart:uint,normalizeEnd:uint):void { var idx:int = findChildIndexAtPosition(normalizeStart); if (idx != -1 && idx < _numChildren) { // backup over zero length children var child:FlowElement = getChildAt(idx); normalizeStart = normalizeStart-child.parentRelativeStart; CONFIG::debug { assert(normalizeStart >= 0, "bad normalizeStart in normalizeRange"); } for (;;) { // watch out for changes in the length of the child var origChildEnd:int = child.parentRelativeStart+child.textLength; child.normalizeRange(normalizeStart,normalizeEnd-child.parentRelativeStart); var newChildEnd:int = child.parentRelativeStart+child.textLength; normalizeEnd += newChildEnd-origChildEnd; // adjust // no zero length children if (child.textLength == 0 && !child.bindableElement) replaceChildren(idx,idx+1); else idx++; if (idx == _numChildren) break; // next child child = getChildAt(idx); if (child.parentRelativeStart > normalizeEnd) break; normalizeStart = 0; // for the next child } } } /** @private */ tlf_internal override function applyWhiteSpaceCollapse():void { for (var idx:int = 0; idx < _numChildren;) { var child:FlowElement = getChildAt(idx); child.applyWhiteSpaceCollapse(); if (child.parent == this) // check to see if child was removed (could have been ++idx; } // If the element was added automatically, it may now have no content and needs to be removed // This can happen with whitespace between paragraphs that is added by set mxmlChildren if (textLength == 0 && impliedElement && parent != null) parent.removeChild(this); super.applyWhiteSpaceCollapse(); } /** @private */ tlf_internal override function appendElementsForDelayedUpdate(tf:TextFlow):void { for (var idx:int = 0; idx < _numChildren; idx++) { var child:FlowElement = getChildAt(idx); child.appendElementsForDelayedUpdate(tf); } } // **************************************** // End tree modification support code // **************************************** // **************************************** // Begin debug support code // **************************************** /** @private */ CONFIG::debug public override function debugCheckFlowElement(depth:int = 0, extraData:String = ""):int { var rslt:int = super.debugCheckFlowElement(depth,extraData); // debugging function that asserts if the flow element is in an invalid state var totalChildLength:int = 0; if (_numChildren) { for (var childIndex:int = 0; childIndex < _numChildren; ++childIndex) { var child:FlowElement = getChildAt(childIndex); rslt += assert(child.parent == this, "child doesn't point to parent"); // totalChildLength is relative offset to child rslt += assert(child.parentRelativeStart == totalChildLength, "child start offset wrong"); rslt += child.debugCheckFlowElement(depth+1); totalChildLength += child.textLength; } } else { // only spans may own text rslt += assert(this is SpanElement || textLength == 0, "only spans may have text"); totalChildLength = textLength; } assert(totalChildLength == textLength, "child total textLength wrong"); return rslt; } // **************************************** // End debug support code // **************************************** } }