blob: 8a4cd2f54f5b773ec35c09999f926f1cdcfdc6d6 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package flashx.textLayout.edit
{
import flashx.textLayout.debug.assert;
import flashx.textLayout.elements.ContainerFormattedElement;
import flashx.textLayout.elements.DivElement;
import flashx.textLayout.elements.FlowElement;
import flashx.textLayout.elements.FlowGroupElement;
import flashx.textLayout.elements.FlowLeafElement;
import flashx.textLayout.elements.LinkElement;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.SpanElement;
import flashx.textLayout.elements.SubParagraphGroupElement;
import flashx.textLayout.elements.TCYElement;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.tlf_internal;
use namespace tlf_internal;
[ExcludeClass]
/**
* Encapsulates all methods necessary for dynamic editing of a text. The methods are all static member functions of this class.
* @private - because we can't make it tlf_internal. Used by the operations package
*/
public class TextFlowEdit
{
private static function deleteRange(theFlow:FlowGroupElement, startPos:int, endPos:int):int
{
var curFlowElementIdx:int = 0;
var curFlowElement:FlowElement;
var needToMergeWhenDone:Boolean = false;
var relStart:int = 0;
var relEnd:int = 0;
var s:SpanElement;
var processedAnElement:Boolean = false;
var totalItemsDeleted:int = 0;
var curNumDeleted:int = 0;
var tempFlowElement:FlowElement;
var numItems:int;
// do the middle all in one big block
var beginBlockDeleteIdx:int = -1;
var blockDeleteNumElements:int;
var blockDeleteLength:int;
while (curFlowElementIdx < theFlow.numChildren)
{
curFlowElement = theFlow.getChildAt(curFlowElementIdx);
relStart = startPos - curFlowElement.parentRelativeStart;
if (relStart < 0) relStart = 0;
relEnd = endPos - curFlowElement.parentRelativeStart;
if ((relStart < curFlowElement.textLength) && (relEnd > 0))
{
//at least partially selected
if ((relStart <= 0) && ((relEnd > curFlowElement.textLength) || ((relEnd >= curFlowElement.textLength) && (curFlowElement is ParagraphElement))))
{
//completely selected
// If the last character of a paragraph is part of the span, it won't get deleted. We will skip over it for now, and may delete it later as
// part of a paragraph merge.
curNumDeleted = curFlowElement.textLength;
var skippingTerminator:Boolean = curFlowElementIdx == theFlow.numChildren - 1 && (curFlowElement is SpanElement) && (theFlow is ParagraphElement);
if (skippingTerminator/* || !(theFlow is TextFlow)*/)
{
if (beginBlockDeleteIdx != -1)
{
theFlow.replaceChildren(beginBlockDeleteIdx,beginBlockDeleteIdx+blockDeleteNumElements);
curFlowElementIdx -= blockDeleteNumElements;
totalItemsDeleted += blockDeleteLength;
endPos -= blockDeleteLength;
beginBlockDeleteIdx = -1;
}
theFlow.replaceChildren(curFlowElementIdx, curFlowElementIdx + 1, null);
// if (skippingTerminator)
++curFlowElementIdx;
totalItemsDeleted += curNumDeleted;
endPos -= curNumDeleted;
}
else
{
// setup for a block delete
if (beginBlockDeleteIdx == -1)
{
beginBlockDeleteIdx = curFlowElementIdx;
blockDeleteNumElements = 0;
blockDeleteLength = 0;
}
blockDeleteNumElements++;
blockDeleteLength += curNumDeleted;
++curFlowElementIdx;
}
}
else
{ //not completely selected
if (beginBlockDeleteIdx != -1)
{
theFlow.replaceChildren(beginBlockDeleteIdx,beginBlockDeleteIdx+blockDeleteNumElements);
curFlowElementIdx -= blockDeleteNumElements;
totalItemsDeleted += blockDeleteLength;
endPos -= blockDeleteLength;
beginBlockDeleteIdx = -1;
}
if (curFlowElement is SpanElement)
{
s = curFlowElement as SpanElement;
if(relEnd > s.textLength)
relEnd = s.textLength;
s.replaceText(relStart, relEnd, "");
curNumDeleted = (relEnd - relStart);
totalItemsDeleted += curNumDeleted;
endPos -= curNumDeleted;
} else if (!(curFlowElement is FlowGroupElement))
{
curNumDeleted = curFlowElement.textLength;
totalItemsDeleted += curFlowElement.textLength;
endPos -= curNumDeleted;
theFlow.replaceChildren(curFlowElementIdx, curFlowElementIdx + 1, null);
} else { //it must be a FlowGroupElement of some kind
if ((!processedAnElement) && (relEnd >= curFlowElement.textLength))
{
if (curFlowElement is ParagraphElement)
needToMergeWhenDone = true;
else if (curFlowElement is FlowGroupElement)
{
numItems = (curFlowElement as FlowGroupElement).numChildren;
if (numItems > 0)
{
tempFlowElement = (curFlowElement as FlowGroupElement).getChildAt(numItems - 1);
if (tempFlowElement is ParagraphElement)
{
needToMergeWhenDone = true;
}
}
}
}
curNumDeleted = TextFlowEdit.deleteRange(curFlowElement as FlowGroupElement, relStart, relEnd);
totalItemsDeleted += curNumDeleted;
endPos -= curNumDeleted;
if (needToMergeWhenDone == true)
endPos++;
if (!(curFlowElement is ParagraphElement))
needToMergeWhenDone = false;
}
if (processedAnElement)
{
break;
}
curFlowElementIdx++;
}
processedAnElement = true;
} else if (processedAnElement)
{
break;
} else {
curFlowElementIdx++;
}
}
if (beginBlockDeleteIdx != -1)
{
theFlow.replaceChildren(beginBlockDeleteIdx,beginBlockDeleteIdx+blockDeleteNumElements);
curFlowElementIdx -= blockDeleteNumElements;
totalItemsDeleted += blockDeleteLength;
endPos -= blockDeleteLength;
}
if (needToMergeWhenDone)
{
joinNextParagraph(ParagraphElement(curFlowElement.getPreviousSibling()));
}
return totalItemsDeleted;
}
private static function isFlowElementInArray(arr:Array, fl:FlowElement):Boolean
{
if (arr != null)
{
var arrLen:int = arr.length;
var currPos:int = 0;
while (currPos < arrLen)
{
if (arr[currPos] == fl)
{
return true;
}
currPos++;
}
}
return false;
}
private static function getContainer(flEl:FlowElement):ContainerFormattedElement
{
while (!(flEl.parent is ContainerFormattedElement))
{
flEl = flEl.parent;
}
return flEl.parent as ContainerFormattedElement;
}
private static function isInsertableItem(flItem:FlowElement, missingBeginElements:Array, missingEndElements:Array):Boolean
{
return ((flItem is ParagraphElement) ||
(!TextFlowEdit.isFlowElementInArray(missingBeginElements, flItem) &&
!TextFlowEdit.isFlowElementInArray(missingEndElements, flItem)));
}
private static function putDivAtEndOfContainer(container:ContainerFormattedElement):DivElement
{
var tempDiv:DivElement = new DivElement();
var tempPar:ParagraphElement = new ParagraphElement();
tempPar.replaceChildren(0, 0, new SpanElement());
tempDiv.replaceChildren(0, 0, tempPar);
container.replaceChildren(container.numChildren, container.numChildren, tempDiv);
return tempDiv;
}
private static function putDivAtEndOfContainerAndInsertTextFlow(theFlow:TextFlow, pos:int, insertedTextFlow:FlowGroupElement, missingBeginElements:Array, missingEndElements:Array, separatorArray:Array):int
{
var nextInsertionPosition:int = pos;
var insertContainer:ContainerFormattedElement = TextFlowEdit.getContainer(theFlow.findAbsoluteParagraph(nextInsertionPosition));
var tempDiv:DivElement = TextFlowEdit.putDivAtEndOfContainer(insertContainer);
separatorArray.push(tempDiv);
var childArray:Array = insertedTextFlow.mxmlChildren;
insertedTextFlow.replaceChildren(0, insertedTextFlow.numChildren); // removing them from the old parent in a block is much faster
for each (var tempFlChild:FlowElement in childArray)
nextInsertionPosition = TextFlowEdit.insertTextFlow(theFlow, nextInsertionPosition, tempFlChild as FlowGroupElement, missingBeginElements, missingEndElements, separatorArray);
var elementIdx:int = tempDiv.parent.getChildIndex(tempDiv);
tempDiv.parent.replaceChildren(elementIdx, elementIdx + 1, null);
separatorArray.pop();
return nextInsertionPosition;
}
private static function isContainerSeparator(fl:FlowElement, separatorArray:Array):Boolean
{
var i:int = 0;
var numItemsInArray:int = separatorArray.length;
while (i < numItemsInArray)
{
if (separatorArray[i] == fl)
{
return true;
}
i++;
}
return false;
}
private static var processedFirstFlowElement:Boolean = false;
private static function insertTextFlow(theFlow:TextFlow, pos:int, insertedTextFlow:FlowGroupElement, missingBeginElementsInFlow:Array = null, missingEndElementsInFlow:Array = null, separatorArray:Array = null):int
{
var nextInsertionPosition:int = pos;
if (!TextFlowEdit.isInsertableItem(insertedTextFlow, missingBeginElementsInFlow, missingEndElementsInFlow) ||
(insertedTextFlow is TextFlow))
{
if (insertedTextFlow is TextFlow)
{
processedFirstFlowElement = false;
var tempDiv:DivElement = TextFlowEdit.putDivAtEndOfContainer(theFlow as ContainerFormattedElement);
separatorArray = new Array();
separatorArray.push(tempDiv);
}
var tempFlChild:FlowElement = insertedTextFlow.getChildAt(0);
if (TextFlowEdit.isInsertableItem(tempFlChild, missingBeginElementsInFlow, missingEndElementsInFlow))
{
nextInsertionPosition = TextFlowEdit.putDivAtEndOfContainerAndInsertTextFlow(theFlow, nextInsertionPosition, insertedTextFlow, missingBeginElementsInFlow, missingEndElementsInFlow, separatorArray);
} else {
while (insertedTextFlow.numChildren > 0)
{
tempFlChild = insertedTextFlow.getChildAt(0);
insertedTextFlow.replaceChildren(0, 1, null);
nextInsertionPosition = TextFlowEdit.insertTextFlow(theFlow, nextInsertionPosition, tempFlChild as FlowGroupElement, missingBeginElementsInFlow, missingEndElementsInFlow, separatorArray);
}
}
if (insertedTextFlow is TextFlow)
{
theFlow.replaceChildren(theFlow.numChildren - 1, theFlow.numChildren, null);
if (nextInsertionPosition >= theFlow.textLength)
{
nextInsertionPosition = theFlow.textLength - 1;
}
separatorArray.pop();
}
} else {
//if you are inserting at the very end of a paragraph, bump up the position
//by one. Otherwise, if you are not at the end of the paragraph, split at
//the position, and then move up by 1.
var leafEl:FlowLeafElement = null;
if (pos > 0) leafEl = theFlow.findLeaf(pos - 1);
var para:ParagraphElement = theFlow.findAbsoluteParagraph(pos);
var paraSplitIndex:int = pos - para.getAbsoluteStart();
var flowElIndex:int = para.parent.getChildIndex(para);
var okToMergeWithAfter:Boolean = true;
if (paraSplitIndex > 0)
{
if (paraSplitIndex < (para.textLength - 1))
{
para.splitAtPosition(paraSplitIndex);
} else if ((insertedTextFlow.textLength == 1) && !processedFirstFlowElement) {
if (TextFlowEdit.isFlowElementInArray(missingEndElementsInFlow, insertedTextFlow) ||
TextFlowEdit.isFlowElementInArray(missingBeginElementsInFlow, insertedTextFlow))
{
processedFirstFlowElement = true;
return nextInsertionPosition;
} else {
para.splitAtPosition(paraSplitIndex);
}
} else {
okToMergeWithAfter = false;
}
pos++;
} else { //no split done. So we want to insert after the previous paragraph.
flowElIndex = flowElIndex - 1;
}
//insert the insertedTextFlow after the paragraph at paragraphIndex
var paragraphContainer:FlowGroupElement = para.parent;
if (TextFlowEdit.isContainerSeparator(paragraphContainer, separatorArray))
{
flowElIndex = paragraphContainer.parent.getChildIndex(paragraphContainer);
paragraphContainer = paragraphContainer.parent;
flowElIndex--;
}
paragraphContainer.replaceChildren(flowElIndex + 1, flowElIndex + 1, insertedTextFlow);
nextInsertionPosition = pos + insertedTextFlow.textLength;
if (insertedTextFlow is ParagraphElement)
{
var missingEnd:Boolean = TextFlowEdit.isFlowElementInArray(missingEndElementsInFlow, insertedTextFlow);
if (okToMergeWithAfter && missingEnd)
{
// Merge the paragraph with what comes next. If the inserted paragraph is inserted to the middle or end of the paragraph,
// then merge the next paragraph into the inserted paragraph. If we're inserting to the start of the paragraph, merge
// the inserted paragraph into the next paragraph, so that the original host paragraph maintains its format settings.
if (paraSplitIndex == 0)
{
if (joinToNextParagraph(ParagraphElement(insertedTextFlow)))
nextInsertionPosition--;
}
else if (joinNextParagraph(ParagraphElement(insertedTextFlow)))
nextInsertionPosition--;
}
if (!processedFirstFlowElement)
{
if (paraSplitIndex > 0)
{
var prevSibling:ParagraphElement = insertedTextFlow.getPreviousSibling() as ParagraphElement;
if (prevSibling && joinNextParagraph(prevSibling))
nextInsertionPosition--;
}
}
if (missingEnd)
{
var absolutePar:ParagraphElement = paragraphContainer.getTextFlow().findAbsoluteParagraph(nextInsertionPosition);
var absoluteParIndex:int = absolutePar.getAbsoluteStart();
if ((nextInsertionPosition - absolutePar.getAbsoluteStart()) == 0)
{
nextInsertionPosition--;
}
}
}
processedFirstFlowElement = true;
}
return nextInsertionPosition;
}
/**
* Replaces the range of text positions that the <code>startPos</code> and
* <code>endPos</code> parameters specify with the <code>newTextFlow</code> parameter in
* <code>theFlow</code>.
* <p>To delete elements, pass <code>null</code> for <code>newTextFlow</code>.</p>
* <p>To insert an element, pass the same value for <code>startPos</code> and <code>endPos</code>.
* <p>To insert a newline after the <code>newTextFlow</code> is inserted, pass in
* <code>true</code> for <code>insertParAfter</code></p>
* <p>The new element will be inserted before the specified index.</p>
* <p>To append the TextFlow, pass <code>theFlow.length</code> for <code>startPos</code> and <code>endPos</code>.</p>
*
* @param theFlow The TextFlow that is being inserted into.
* @param startPos The index value of the first position of the replacement range in the TextFlow.
* @param endPos The index value following the end position of the replacement range in the TextFlow.
* @param newTextFlow The TextFlow to be merged into theFlow.
* @param missingBeginElementsInFlow Array indicating all the elements within the TextFlow that have their beginning parts chopped off.
* @param missingEndElementsInFlow Array indicating all the elements within the TextFlow that have their ending parts chopped off.
*/
public static function replaceRange(theFlow:TextFlow, startPos:int, endPos:int, textScrap:TextScrap = null):int
{
var nextInsertPosition:int = startPos;
if (endPos > startPos)
{
deleteRange(theFlow, startPos, endPos);
}
if (textScrap != null)
{
textScrap = textScrap.clone(); // make a copy so the original isn't mutated
nextInsertPosition = insertTextFlow(theFlow, startPos, textScrap.textFlow, textScrap.beginMissingArray, textScrap.endMissingArray);
}
return nextInsertPosition;
}
/**
* Creates a copy of the TextFlow in between two positions and returns the TextFlow
* within a TextScrap object. See TextScrap for more information.
* @param theFlow The TextFlow that is being copied from.
* @param startPos The index value of the first position of the TextFlow being copied from.
* @param endPos The index value following the end position of the TextFlow being copied from.
*/
public static function createTextScrap(theFlow:TextFlow, startPos:int, endPos:int):TextScrap
{
if (!theFlow || startPos >= endPos)
return null;
var newTextFlow:TextFlow = theFlow.deepCopy(startPos, endPos) as TextFlow;
newTextFlow.normalize();
var retTextScrap:TextScrap = new TextScrap(newTextFlow);
if (newTextFlow.textLength > 0)
{
var fl:FlowElement = newTextFlow.getLastLeaf();
var srcElem:FlowElement = theFlow.findLeaf(startPos);
var copyElem:FlowElement = newTextFlow.getFirstLeaf();
while (copyElem && srcElem)
{
if ((startPos - srcElem.getAbsoluteStart()) > 0)
{
retTextScrap.addToBeginMissing(copyElem);
}
copyElem = copyElem.parent;
srcElem = srcElem.parent;
}
srcElem = theFlow.findLeaf(endPos - 1);
copyElem = newTextFlow.getLastLeaf();
if ((copyElem is SpanElement) && (!(srcElem is SpanElement)))
{
copyElem = newTextFlow.findLeaf(newTextFlow.textLength - 2);
}
while (copyElem && srcElem)
{
if (endPos < (srcElem.getAbsoluteStart() + srcElem.textLength))
{
retTextScrap.addToEndMissing(copyElem);
}
copyElem = copyElem.parent;
srcElem = srcElem.parent;
}
return retTextScrap;
}
return null;
}
/**
* Creates a TCY run out of the selected positions.
* @param theFlow The TextFlow of interest.
* @param startPos The index value of the first position of the TextFlow to be turned into a TCY run.
* @param endPos The index value following the end position of the TextFlow to be turned into a TCY run.
*/
public static function makeTCY(theFlow:TextFlow, startPos:int, endPos:int):Boolean
{
var madeTCY:Boolean = true;
var curPara:ParagraphElement = theFlow.findAbsoluteParagraph(startPos);
if(!curPara)
return false;
while(curPara)
{
var paraEnd:int = curPara.getAbsoluteStart() + curPara.textLength;
var curEndPos:int = Math.min(paraEnd, endPos);
//we have an entire para selected and the para only contains a kParaTerminator char, which cannot be
//made into TCY.
if(canInsertSPBlock(theFlow, startPos, curEndPos, TCYElement) && curPara.textLength > 1)
{
var new_tcyElem:TCYElement = new TCYElement();
//don't hide an error!
if(madeTCY)
madeTCY = insertNewSPBlock(theFlow, startPos, curEndPos, new_tcyElem, TCYElement);
else
insertNewSPBlock(theFlow, startPos, curEndPos, new_tcyElem, TCYElement);
}
else
madeTCY = false;
if(paraEnd < endPos)
{
curPara = theFlow.findAbsoluteParagraph(curEndPos);
startPos = curEndPos;
}
else
curPara = null;
}
return madeTCY;
}
/**
* Creates one or more LinkElements out of the selected positions. It will go through
* every paragraph within the selected position and make links.
* @param theFlow The TextFlow of interest.
* @param startPos The index value of the first position of the TextFlow to be turned into a link.
* @param endPos The index value following the end position of the TextFlow to be turned into a link.
* @param urlString The url string to be associated with the link.
*/
public static function makeLink(theFlow:TextFlow, startPos:int, endPos:int, urlString:String, target:String):Boolean
{
var madeLink:Boolean = true;
var curPara:ParagraphElement = theFlow.findAbsoluteParagraph(startPos);
if(!curPara)
return false;
while(curPara)
{
var paraEnd:int = curPara.getAbsoluteStart() + curPara.textLength;
var curEndPos:int = Math.min(paraEnd, endPos);
var linkEndPos:int = (curEndPos == paraEnd) ? (curEndPos - 1) : curEndPos;
if (linkEndPos > startPos)
{
//if the end of the paragraph is < endPos, we are going across bounds
if(!canInsertSPBlock(theFlow, startPos, linkEndPos, LinkElement))
{
return false;
}
var newLinkElement:LinkElement = new LinkElement();
newLinkElement.href = urlString;
newLinkElement.target = target;
//don't hide an error!
if(madeLink)
madeLink = insertNewSPBlock(theFlow, startPos, linkEndPos, newLinkElement, LinkElement);
else
insertNewSPBlock(theFlow, startPos, linkEndPos, newLinkElement, LinkElement);
}
if(paraEnd < endPos)
{
curPara = theFlow.findAbsoluteParagraph(curEndPos);
startPos = curEndPos;
}
else
curPara = null;
}
return madeLink;
}
/**
* Removes the TCY block at the selected positions.
* @param theFlow The TextFlow of interest.
* @param startPos The index value of the first position of the TextFlow.
* @param endPos The index value following the end position of the TextFlow.
*/
public static function removeTCY(theFlow:TextFlow, startPos:int, endPos:int):Boolean
{
if (endPos <= startPos)
{
return false;
}
return findAndRemoveFlowGroupElement(theFlow, startPos, endPos, TCYElement);;
}
/**
* Removes all LinkElements under the selected positions. It will go through
* every paragraph within the selected position and remove any link.
* @param theFlow The TextFlow of interest.
* @param startPos The index value of the first position of the TextFlow.
* @param endPos The index value following the end position of the TextFlow.
*/
public static function removeLink(theFlow:TextFlow, startPos:int, endPos:int):Boolean
{
if (endPos <= startPos)
{
return false;
}
return findAndRemoveFlowGroupElement(theFlow, startPos, endPos, LinkElement);
}
/**
* @private
* insertNewSPBlock - add a SubParagraphGroupElement (spg) to <code>theFlow</code> at the indicies specified by <code>startPos</code> and
* <code>endPos</code>. The <code>newSPB</code> will take ownership of any FlowElements within the range and will split them
* as needed. If the parent of the FlowGroupElement indicated by <code>startPos</code> is the same as <code>spgClass</code> then
* the method fails and returns false because a spg cannot own children of the same class as itself. Any spg of type <code>spgClass</code>
* found within the indicies, however, is subsumed into <code>newSPB</code>, effectively replacing it.
*
* @param theFlow:TextFlow - The TextFlow that is the destination for the newSPB
* @param startPos:int - The absolute index value of the first position of the range in the TextFlow to perform the insertion.
* @param endPos:int - The index value following the end position of the range in the TextFlow to perform the insertion.
* @param newSPB:SubParagraphGroupElement - The new SubParagraphElement which is to be added into theFlow.
* @param spgClass:Class - the class of the fbe we intend to add.
*
* Examples: Simple and complex where insertion is of <code>spgClass</code> b. Selection is l~o
* 1) <a><span>ghijklmnop</span></a>
* 2) <a><span>ghij</span><b><span>klm</span></b><span>nop</span></a>
* 3) <a><span>ghijk</span><c><span>lmn</span></c><span>op</span></a>
*
*/
tlf_internal static function insertNewSPBlock(theFlow:TextFlow, startPos:int, endPos:int, newSPB:SubParagraphGroupElement, spgClass:Class):Boolean
{
var curPos:int = startPos;
var curFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(curPos);
var elementIdx:int = 0;
CONFIG::debug{ assert(curFBE != null, "No FBE at location curPos(" + curPos + ")!");}
//if we are at the last "real" glyph of the paragraph, include the terminator.
var paraEl:ParagraphElement = curFBE.getParagraph();
if(endPos == (paraEl.getAbsoluteStart() + paraEl.textLength - 1))
++endPos;
//before processing this any further, we need to make sure that we are not
//splitting a spg which is contained within the same type of spg as the curFBE's parent.
//for example, if we had a tcyElement inside a linkElement, then we cannot allow a link element
//to be made within the tcyElement as the link would not function. As a rule, a SubParagraphElement
//cannot own a child of the same class.
//
//However, if the curFBE is parented by a spg and the objects have the same start and end, then we are doing
//a replace and we're not splitting the parent. Check if the bounds are the same and if so, don't skip it...
var parentStart:int = curFBE.parent.getAbsoluteStart();
var curFBEStart:int = curFBE.getAbsoluteStart();
if(curFBE.parent && curFBE.parent is spgClass &&
!(parentStart == curFBEStart && parentStart + curFBE.parent.textLength == curFBEStart + curFBE.textLength))
{
return false;
}
//entire FBE is selected and is not a paragraph, get its parent.
if(!(curFBE is ParagraphElement) && curPos == curFBEStart && curPos + curFBE.textLength <= endPos)
{
elementIdx = curFBE.parent.getChildIndex(curFBE);
curFBE = curFBE.parent;
}
//first, if the curFBE is of the same class as the newSPB, then we need to split it to allow for insertion
//of the new one IF the start position is > the start of the curFBE
//
//running example after this block:
// 1) <a><span>ghijk</span><span>lmnop</span></a>
// 2) <a><span>ghij</span><b><span>k</span></b><b><span>lm</span></b><span>nop</span></a>
// 3) <a><span>ghijk</span><c><span>lmn</span></c><span>op</span></a> - no change
if(curPos >= curFBEStart)
{
if(!(curFBE is spgClass))
elementIdx = findAndSplitElement(curFBE, elementIdx, curPos, true);
else
{
elementIdx = findAndSplitElement(curFBE.parent, curFBE.parent.getChildIndex(curFBE), curPos, false);
curFBE = curFBE.parent;
}
}
//now that the curFBE has been split, we want to insert the newSPB into the flow and then start absorbing the
//contents...
//running example after this block:
// 1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
// 2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a>
// 3) <a><span>ghijk</span><b></b><c><span>lmn</span></c><span>op</span></a> - no change
//
// we need another use case here where selection is entire sbp and selection runs from the head of a spg to
// part way through it - so that a) does into parent and b) goes into spg
// if this is case 2, then the new element must go into the parent...
if(curFBE is spgClass)
{
curFBEStart = curFBE.getAbsoluteStart();
elementIdx = curFBE.parent.getChildIndex(curFBE);
if(curPos > curFBEStart)//we're splitting the element, not replacing it...
elementIdx += 1;
//if the spg, curFBE is entirely selected then we want to use the parent, not the item itself.
while(endPos >= curFBEStart + curFBE.textLength)
{
//we need access to the parent, which contains both the start and end not the FBE we just split
curFBE = curFBE.parent;
}
curFBE.replaceChildren(elementIdx, elementIdx, newSPB);
}
else
{
//we're inserting into the curFBE
curFBE.replaceChildren(elementIdx, elementIdx, newSPB);
}
//see subsumeElementsToSPBlock to see effects on running example
subsumeElementsToSPBlock(curFBE, elementIdx + 1, curPos, endPos, newSPB, spgClass);
return true;
}
/**
* @private
* splitElement - split <code>elem</code> at the relative index of <code>splitPos</code>. If <code>splitSubBlockContents</code>
* is true, split the contents of <code>elem</code> if it is a SubParagraphGroupElement, otherwise just split <code>elem</code>
*
* @param elem:FlowElement - the FlowElement to split
* @param splitPos:int - The elem relative index indicating where to split
* @param splitSubBlockContents:Boolean - boolean indicating whether a SubParagraphGroupElement is to be split OR that it's contents
* should be split. For example, are we splitting a link or are we splitting the child of the link
*
* <spg><span>ABCDEF</span></spg>
*
* if <code>splitPos</code> indicated index between C and D, then if <code>splitSubBlockContents</code> equals true,
* result is:
*
* <spg><span>ABC</span><span>DEF</span></spg>
*
* if <code>splitSubBlockContents</code> equals false, result is:
*
* <spg><span>ABC</span></spg><spg><span>DEF</span></spg>
*/
tlf_internal static function splitElement(elem:FlowElement, splitPos:int, splitSubBlockContents:Boolean):void
{
CONFIG::debug{ assert(splitPos < elem.textLength, "trying to splic FlowElement at illegal index!"); }
if (elem is SpanElement)
{
SpanElement(elem).splitAtPosition(splitPos);
}
else if(elem is SubParagraphGroupElement && splitSubBlockContents)
{
var subBlock:SubParagraphGroupElement = SubParagraphGroupElement(elem);
// Split the SpanElement of the block at splitPos. If the item at the splitPos is not a SpanElement, no action occurs.
var tempElem:SpanElement = subBlock.findLeaf(splitPos) as SpanElement;
if (tempElem)
tempElem.splitAtPosition(splitPos - tempElem.getElementRelativeStart(subBlock));
}
else if (elem is FlowGroupElement)
{
FlowGroupElement(elem).splitAtPosition(splitPos);
}
else
{
CONFIG::debug { assert(false, "Trying to split on an illegal FlowElement"); }
}
}
/**
* @private
* findAndSplitElement - starting at the child <code>elementIdx</code> of <code>fbe</code>, iterate
* through the elements untill we find the one located at the aboslute index of <code>startIdx</code>. Upon
* locating the child, split either the element itself OR its children based on the value of <code>splitSubBlockContents</code>
*
* @param fbe:FlowGroupElement - the FBE into which the newSPB is being inserted.
* @param elementIdx:int - The index into the <code>fbe's</code> child list to start
* @param startIdx:int - The absolute index value into the TextFlow.
* @param splitSubBlockContents:Boolean - boolean indicating whether a subElement is to be split OR that it's contents
* should be split. For example, are we splitting a link or are we splitting the child of the link
*
* <p>ZYX<link>ABCDEF</link>123</p>
*
* if we are inserting a TCY into the link, splitSubBlockContents should be false. We want to split the span ABCDEF such that result is:
* <p>ZYX<link>AB<tcy>CD</tcy>EF</link>123</p>
*
* if we are creating a new link from X to B, then we want the link to split and splitSubBlockContents should be false:
*
* <p>ZY<link>XAB</link><link>CDEF</link>123</p>
*
* @return int - the index of the last child of <code>fbe</code> processed.
*/
tlf_internal static function findAndSplitElement(fbe:FlowGroupElement, elementIdx:int, startIdx:int, splitSubBlockContents:Boolean):int
{
var curFlowEl:FlowElement = null;
var curIndexInPar:int = startIdx - fbe.getAbsoluteStart();
while(elementIdx < fbe.numChildren)
{
curFlowEl = fbe.getChildAt(elementIdx);
if (curIndexInPar == curFlowEl.parentRelativeStart)
return elementIdx;
if ((curIndexInPar > curFlowEl.parentRelativeStart) && (curIndexInPar < curFlowEl.parentRelativeEnd))
{
splitElement(curFlowEl, curIndexInPar - curFlowEl.parentRelativeStart, splitSubBlockContents);
}
++elementIdx;
}
return elementIdx;
}
/**
* @private
* subsumeElementsToSPBlock - incorporates all elements of <code>parentFBE</code> into
* the <code>newSPB</code> between the <code>curPos</code> and <code>endPos</code>. If a child of
* <code>parentFBE</code> is of type <code>spgClass</code> then the child's contents are removed from the child,
* added to the <code>newSPB</code>, the child is then removed from the <code>parentFBE</code>
*
* @param parentFBE:FlowGroupElement - the FBE into which the newSPB is being inserted.
* @param startPos:int - The index value of the first position of the replacement range in the TextFlow.
* @param endPos:int - The index value following the end position of the replacement range in the TextFlow.
* @param newSPB:SubParagraphGroupElement - the new SubParagraphGroupElement we intend to insert.
* @param spgClass:Class - the class of the fbe we intend to insert.
*
* @return int - the aboslute index in the text flow after insertion.
*
* Examples: Simple and complex where insertion is of <code>spgClass</class> b. Selection is l~o
* 1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
* 2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a>
*
* parentFBE = <a>
* elementIdx = 1) 2, 2) 3
* curPos = 5
* endPos = 9
* newSPB is of type <b>
*/
tlf_internal static function subsumeElementsToSPBlock(parentFBE:FlowGroupElement, elementIdx:int, curPos:int, endPos:int, newSPB:SubParagraphGroupElement, spgClass:Class):int
{
var curFlowEl:FlowElement = null;
//if we have an invalid index, then skip out. elementIdx will always point one
//element beyond the one we are inserting....
if(elementIdx >= parentFBE.numChildren)
return curPos;
while (curPos < endPos)
{
//running example: curFlowEl is the element immediately after the inserted newSPB:
// 1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
// points to span-lmnop
// 2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a>
// points to b-lm
curFlowEl = parentFBE.getChildAt(elementIdx);
//if the curFlowEl is of the Class we are adding (spgClass), and the entire thing is selected,
//then we are adding the entire block, but not spliting it - perform the split on the next block
//I think this can be safely removed from here as ownership of contents is handled below.
//leaving in commented out code in case we need to revert - gak 05.01.08
/* if(curFlowEl is spgClass && curPos == curFlowEl.getAbsoluteStart() && curFlowEl.getAbsoluteStart() + curFlowEl.textLength <= endPos)
{
curPos = parentFBE.getAbsoluteStart() + parentFBE.textLength;
continue;
}*/
//if the endPos is less than the length of the curFlowEl, then we need to split it.
//if the curFlowEl is NOT of class type spgClass, then we need to break it
//
//Use case: splitting a link in two (or three as will be the result with head and tail sharing
//attributes...
//running example 1 hits this, but 2 does not. Using variation of 2:
//
// example: 1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
// 2a) <a><span>fo</span><b></b><b><span>obar</span></b></a> - selection: from o~a
//
// after this code:
// 1) <a><span>ghijk</span><b></b><span>lmno</span><span>p</span></a>
// 2a) <a><span>fo</span><b></b><b><span>oba</span></b><b><span>or</span></b></a>
if ((curPos + curFlowEl.textLength) > endPos)
{
splitElement(curFlowEl, endPos - curFlowEl.getAbsoluteStart(), !(curFlowEl is spgClass)); //changed to curFlowEl from newSPB as newSPB should be of type spgClass
}
//add the length before replacing the elements
curPos += curFlowEl.textLength;
//running example: after parentFBE.replaceChildren
//
// 1) curFlowEl = <span>lmno</span>
// <a><span>ghijk</span><b></b>{curFlowEl}<span>p</span></a>
//
// 2) curFlowEl = <b><span>lm</span></b>
// <a><span>ghij</span><b><span>k</span></b><b></b>{curFlowEl}<span>nop</span></a>
parentFBE.replaceChildren(elementIdx, elementIdx + 1, null);
//if the curFlowEl is of type spgClass, then we need to take its children and
//add them to the newSPB because a spg cannot contain a child of the same class
//as itself
//
// exmaple: 2) curFlowEl = <b><span>lm</span></b>
if (curFlowEl is spgClass)
{
var subBlock:SubParagraphGroupElement = curFlowEl as SubParagraphGroupElement;
//elementCount == 1 - <span>lm</span>
while (subBlock.numChildren > 0)
{
//fe[0] = <span>lm</span>
var fe:FlowElement = subBlock.getChildAt(0);
//<span></span>
subBlock.replaceChildren(0, 1, null);
//<b><span>lm</span></b>
newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, fe);
}
//when compelete, example 2 is:
//2) <a><span>ghij</span><b><span>k</span></b><b><span>lm</span></b><span>nop</span></a>
}
else
{
//example 1, curFlowEl is <span>lmno</span>, so this is not hit
//
// extending element <a> from foo~other
// <a>foo</a><b>bar<a>other</a><b>
// curFlowEl = <b>bar<a>other</a><b>
//
// since <b> is a spg, we need to walk it's contents and remove any <a> elements
if(curFlowEl is SubParagraphGroupElement)
{
//we need to dive into this spgClass and remove any fbes of type spgClass
//pass in the curFlowEl as the newSPB, remove any spgs of type spgClass, then
//perform the replace on the newSPB passed in here
//
//ignore the return value of the recursive call as the length has already been
//accounted for above
flushSPBlock(curFlowEl as SubParagraphGroupElement, spgClass);
}
newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, curFlowEl);
if(newSPB.numChildren == 1 && curFlowEl is SubParagraphGroupElement)
{
var childSPGE:SubParagraphGroupElement = curFlowEl as SubParagraphGroupElement;
//running example:
//a.precedence = 800, tcy.precedence = kMinSPGEPrecedence
//this = <tcy><a><span>fooBar</span></a><tcy>
//childSPGE = <a><span>fooBar</span></a>
if(childSPGE.textLength == newSPB.textLength && (curPos >= endPos))
{
CONFIG::debug { assert(childSPGE.precedence != newSPB.precedence, "normalizeRange found two equal SPGEs"); }
//if the child's precedence is higher than mine, I need to swap
if(childSPGE.precedence > newSPB.precedence)
{
//first, remove the child
//this = <tcy></tcy>
newSPB.replaceChildren(0,1,null);
//we need to flop this object for the child
while(childSPGE.numChildren > 0)
{
//tempFE = <span>fooBar</span>
var tempFE:FlowElement = childSPGE.getChildAt(0);
//child = <a></a>
childSPGE.replaceChildren(0,1,null);
//this = <tcy><span>fooBar</span></tcy>
newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, tempFE);
}
var myIdx:int = newSPB.parent.getChildIndex(newSPB);
CONFIG::debug{ assert(myIdx >= 0, "Invalid index! How can a SubParagraphGroupElement normalizing not have a parent!"); }
//add childSPGE in my place
newSPB.parent.replaceChildren(myIdx, myIdx + 1, childSPGE)
//childSPGE = <tcy><a><span>fooBar</span></a></tcy>
childSPGE.replaceChildren(0,0,newSPB);
}
}
}
}
}
return curPos;
}
/**
* @private
* findAndRemoveFlowGroupElement
*
* @param theFlow The TextFlow that is containing the elements to remove.
* @param startPos The index value of the first position of the range in the TextFlow where we want to perform removal.
* @param endPos The index value following the end position of the range in the TextFlow where we want to perform removal.
* @param fbeClass Class the class of the fbe we intend to remove.
*
* Walks through the elements of <code>theFlow</code> looking for any FlowGroupElement of type <code>fbeClass</class>
* On finding one, it removes the FBE's contents and adds them back into the FBE's parent. If the class of object is
* embedded within another spg and this removal would break the parent spg, then the method does nothing.
*
* Example:
* <link>ABC<tcy>DEF</tcy>GHI</link>
* Selection is on E and removal of link is attempted.
* Because E is a child of a spg (tcy), and removing the link from E would split the parent spg (link),
* the action is disallowed.
*
* Running example:
* 1) <link><tcy><span>foo</span></tcy><span>bar</span></link>
* @return Boolean - true if items are removed or none are found. false if operation is illegal.
*/
tlf_internal static function findAndRemoveFlowGroupElement(theFlow:TextFlow, startPos:int, endPos:int, fbeClass:Class):Boolean
{
var curPos:int = startPos;
var curEl:FlowElement;
//walk through the elements
while (curPos < endPos)
{
var containerFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(curPos);
//if the start of the parent is the same as the start of the current containerFBE, then
//we potentially have the wrong object. We need to walk up the parents until we get to
//the one which starts at our start AND is the topmost object at that index.
//example: <a><b>foo</b> bar</a> - getting the object at "f" will yield the <b> element, not <a>
while(containerFBE.parent && containerFBE.parent.getAbsoluteStart() == containerFBE.getAbsoluteStart() &&
!(containerFBE.parent is ParagraphElement)) //don't go beyond paragraph
{
containerFBE = containerFBE.parent;
}
//if the absoluteFBE is the item we are trying to remove, we need to work with its parent, so
//reassign containerFBE. For example, if an entire link were selected, we'd need to get it's parent to
//perform the removal
if(containerFBE is fbeClass)
containerFBE = containerFBE.parent;
//before processing this any further, we need to make sure that we are not
//splitting a spg which is contained within the same type of spg as the curFBE's parent.
//for example, if we had a tcyElement inside a linkElement, then we cannot allow a link element
//to be broken within the tcyElement as the link would have to split the TCY.
var ancestorOfFBE:FlowGroupElement = containerFBE.parent;
while(ancestorOfFBE != null && !(ancestorOfFBE is fbeClass))
{
if(ancestorOfFBE.parent is fbeClass)
{
return false;
}
ancestorOfFBE = ancestorOfFBE.parent;
}
//if this is a sbe block contained in another sbe, and it is entire within the
//selection bounds, we need to use the parent sbe's container. If it is splitting
//the child sbe, we don't allow this and it is handled later...
var containerFBEStart:int = containerFBE.getAbsoluteStart();
if(ancestorOfFBE is fbeClass && (containerFBEStart >= curPos && containerFBEStart + containerFBE.textLength <= endPos))
containerFBE = ancestorOfFBE.parent;
var childIdx:int = containerFBE.findChildIndexAtPosition(curPos - containerFBEStart);
curEl = containerFBE.getChildAt(childIdx);
if(curEl is fbeClass)
{
CONFIG::debug{ assert(curEl is SubParagraphGroupElement, "Wrong FBE type! Trying to remove illeage FBE!") };
var curFBE:FlowGroupElement = curEl as FlowGroupElement;
//get it's parent and the index of the curFBE
var parentBlock:FlowGroupElement = curFBE.parent;
var idxInParent:int = parentBlock.getChildIndex(curFBE);
//if the curPos is not at the head of the SPB, then we need to split it here
//curFBE will point to the FBE starting at curPos
if(curPos > curFBE.getAbsoluteStart())
{
splitElement(curFBE, curPos - curFBE.getAbsoluteStart(), false);
curPos = curFBE.getAbsoluteStart() + curFBE.textLength;
continue;
}
//if curFBE goes beyond the endPos, then we need to split off the tail.
if (curFBE.getAbsoluteStart() + curFBE.textLength > endPos)
{
splitElement(curFBE, endPos - curFBE.getAbsoluteStart(), false);
}
//apply the length of the curFBE to the curPos tracker. Do this before
//removing the contents or it will be 0!
curPos = curFBE.getAbsoluteStart() + curFBE.textLength;
//walk all the contents of the FBE into it's parent container
while (curFBE.numChildren > 0)
{
var childFE:FlowElement = curFBE.getChildAt(0);
curFBE.replaceChildren(0, 1, null);
parentBlock.replaceChildren(idxInParent, idxInParent, childFE);
idxInParent++;
}
//remove the curFBE
parentBlock.replaceChildren(idxInParent, idxInParent + 1, null);
}
else if(curEl is SubParagraphGroupElement) //check all the parents...
{
var curSPB:SubParagraphGroupElement = SubParagraphGroupElement(curEl);
if(curSPB.numChildren == 1)
curPos = curSPB.getAbsoluteStart() + curSPB.textLength;
else
{
curEl = curSPB.getChildAt(curSPB.findChildIndexAtPosition(curPos - curSPB.getAbsoluteStart()));
curPos = curEl.getAbsoluteStart() + curEl.textLength;
}
}
else
{
//the current block isn't the type we're looking for, so just go to the end of the
//FlowElement and continue
curPos = curEl.getAbsoluteStart() + curEl.textLength;
}
}
return true;
}
/**
* @private
* canInsertSPBlock
*
* validate that we a valid selection to allow for insertion of a subBlock. The rules are as
* follows:
* endPos > start
* the new block will not span multiple paragraphs
* if the block is going into a SubParagraphGroupElement, it must not split the block:
* example: Text - ABCDEFG with a link on CDE
* legal new Block - D, CD, CDE, [n-chars]CDE[n1-chars]
* illegal new Block - [1 + n-chars]C[D], [D]E[1 + n-chars]
* exception - if the newBlock is the same class as the one we are trying to split
* then we can truncate the original and add its contents to the new one, or extend it
* as appropriate
*
* @param theFlow The TextFlow that is containing the elements to validate.
* @param startPos The index value of the first position of the range in the TextFlow to test.
* @param endPos The index value following the end position of the range in the TextFlow to test.
* @param blockClass Class the class of the fbe we intend to insert.
*/
tlf_internal static function canInsertSPBlock(theFlow:TextFlow, startPos:int, endPos:int, blockClass:Class):Boolean
{
if(endPos <= startPos)
return false;
var anchorFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(startPos);
if(anchorFBE.getParentByType(blockClass))
anchorFBE = anchorFBE.getParentByType(blockClass) as FlowGroupElement;
var tailFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(endPos - 1);
if(tailFBE.getParentByType(blockClass))
tailFBE = tailFBE.getParentByType(blockClass) as FlowGroupElement;
//if these are the same FBEs then we are safe to insert a SubParagraphGroupElement
if(anchorFBE == tailFBE)
return true;
//make sure that the two FBEs belong to the same paragraph!
else if(anchorFBE.getParagraph() != tailFBE.getParagraph())
return false;
else if(anchorFBE is blockClass && tailFBE is blockClass)//they're the same class, OK to merge, split, etc...
return true;
else if(anchorFBE is SubParagraphGroupElement && !(anchorFBE is blockClass))
{
var anchorStart:int = anchorFBE.getAbsoluteStart();
if(startPos > anchorStart && endPos > anchorStart + anchorFBE.textLength)
return false;
}
else if((anchorFBE.parent is SubParagraphGroupElement || tailFBE.parent is SubParagraphGroupElement)
&& anchorFBE.parent != tailFBE.parent)
{
//if either FBE parent is a SPGE and they are not the same, prevent the split.
return false;
}
//if we got here, then the anchorFBE is OK, check the tail. If endPos is pointing to the
//0th character of a FlowGroupElement, we don't need to worry about the tail.
if(tailFBE is SubParagraphGroupElement && !(tailFBE is blockClass) && endPos > tailFBE.getAbsoluteStart())
{
var tailStart:int = tailFBE.getAbsoluteStart();
if(startPos < tailStart && endPos < tailStart + tailFBE.textLength)
return false;
}
return true;
}
/**
* @private flushSPBlock recursively walk a spg looking for elements of type spgClass. On finding one,
* remove it's children and then remove the object itself. Since spg's cannot hold children of the same type
* as themselves, recursion is only needed for spg's of a class other than that of spgClass.
*
* example: subPB = <b>bar<a>other</a><b> extending an <a> element to include all of "other"
*/
tlf_internal static function flushSPBlock(subPB:SubParagraphGroupElement, spgClass:Class):void
{
var subParaIter:int = 0;
//example, subPB has 2 elements, <span>bar</span> and <a><span>other</span></a>
while(subParaIter < subPB.numChildren)
{
//subParaIter == 0, subFE = <span>bar</span> skip the FE and move to next
//subParaIter == 1, subFE = <a><span>other</span></a> - is a spgClass
var subFE:FlowElement = subPB.getChildAt(subParaIter);
if(subFE is spgClass)
{
//subParaIter == 1, subFE = <a><span>other</span></a>
var subChildFBE:FlowGroupElement = subFE as FlowGroupElement;
while(subChildFBE.numChildren > 0)
{
//subFEChild = <span>other</span>
var subFEChild:FlowElement = subChildFBE.getChildAt(0);
//subFEChild = <a></a>
subChildFBE.replaceChildren(0, 1, null);
//subPB = <b>barother<a></a><b>
subPB.replaceChildren(subParaIter, subParaIter, subFEChild);
}
//increment so that subParaIter points to the element we just emptied
++subParaIter;
//remove the empty child
//subPB = <b>barother<b>
subPB.replaceChildren(subParaIter, subParaIter + 1, null);
}
else if(subFE is SubParagraphGroupElement)
{
flushSPBlock(subFE as SubParagraphGroupElement, spgClass);
++subParaIter;
}
else
++subParaIter;//go to next child
}
}
/** Joins this paragraph's next sibling to this if it is a paragraph */
static public function joinNextParagraph(para:ParagraphElement):Boolean
{
if (para && para.parent)
{
var myidx:int = para.parent.getChildIndex(para);
if (myidx != para.parent.numChildren-1)
{
// right now, you can only merge with other paragraphs
var sibParagraph:ParagraphElement = para.parent.getChildAt(myidx+1) as ParagraphElement;
if (sibParagraph)
{
while (sibParagraph.numChildren > 0)
{
var curFlowElement:FlowElement = sibParagraph.getChildAt(0);
sibParagraph.replaceChildren(0, 1, null);
para.replaceChildren(para.numChildren, para.numChildren, curFlowElement);
}
para.parent.replaceChildren(myidx+1, myidx+2, null);
return true;
}
}
}
return false;
}
/** Joins this paragraph's next sibling to this if it is a paragraph */
static public function joinToNextParagraph(para:ParagraphElement):Boolean
{
if (para && para.parent)
{
var myidx:int = para.parent.getChildIndex(para);
if (myidx != para.parent.numChildren-1)
{
// right now, you can only merge with other paragraphs
var sibParagraph:ParagraphElement = para.parent.getChildAt(myidx+1) as ParagraphElement;
if (sibParagraph)
{
// Add the first paragraph's children to the front of the next paragraph's child list
var addAtIndex:int = 0;
while (para.numChildren > 0)
{
var curFlowElement:FlowElement = para.getChildAt(0);
para.replaceChildren(0, 1, null);
sibParagraph.replaceChildren(addAtIndex, addAtIndex, curFlowElement);
++addAtIndex;
}
para.parent.replaceChildren(myidx, myidx+1, null);
return true;
}
}
}
return false;
}
}
}