| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 flash.utils.getQualifiedClassName; |
| |
| import flashx.textLayout.conversion.ConverterBase; |
| 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.ListItemElement; |
| import flashx.textLayout.elements.ParagraphElement; |
| import flashx.textLayout.elements.SpanElement; |
| import flashx.textLayout.elements.SubParagraphGroupElementBase; |
| import flashx.textLayout.elements.TCYElement; |
| import flashx.textLayout.elements.TextFlow; |
| import flashx.textLayout.formats.ITextLayoutFormat; |
| import flashx.textLayout.formats.TextLayoutFormat; |
| 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 |
| { |
| tlf_internal static function deleteRange(textFlow:TextFlow, startPos:int, endPos:int):ParagraphElement |
| { |
| var mergePara:ParagraphElement; |
| |
| // If the range to be deleted contains the paragraph end, we may have to merge up the paragraphs when we're done. |
| if (endPos > startPos) |
| { |
| var firstLeafInRange:FlowLeafElement = textFlow.findLeaf(startPos); |
| var lastLeafInRange:FlowLeafElement = textFlow.findLeaf(endPos - 1); |
| var firstParagraphInRange:ParagraphElement = firstLeafInRange.getParagraph(); |
| var lastParagraphInRange:ParagraphElement = lastLeafInRange.getParagraph(); |
| var firstParaStart:int = firstParagraphInRange.getAbsoluteStart(); |
| var lastParaEnd:int = lastParagraphInRange.getAbsoluteStart() + lastParagraphInRange.textLength; |
| // If the selection is inside a single paragraph, merge only if the terminator is included and the start of the paragraph is not. |
| // If the two paragraphs are different, merge unless the start and end match exactly |
| // Don't merge if the paragraph is an empty paragraph in a list item that has other content (it will just come back again in normalize) |
| var doMerge:Boolean = false; |
| if (firstParagraphInRange == lastParagraphInRange) |
| doMerge = (endPos == lastParaEnd && startPos != firstParaStart); |
| else |
| doMerge = (startPos != firstParaStart); |
| if (doMerge) |
| { |
| var followingLeaf:FlowLeafElement = textFlow.findLeaf(endPos); |
| if (followingLeaf) |
| { |
| mergePara = followingLeaf.getParagraph(); |
| if (mergePara.textLength == 1 && mergePara.parent is ListItemElement && mergePara.parent.numChildren > 1) |
| mergePara = null; |
| } |
| } |
| } |
| |
| deleteRangeInternal(textFlow, startPos, endPos - startPos); |
| |
| if (mergePara) |
| { |
| var previousLeaf:FlowLeafElement = mergePara.getFirstLeaf().getPreviousLeaf(); |
| mergePara = previousLeaf ? previousLeaf.getParagraph() : null; |
| } |
| |
| return mergePara; |
| } |
| |
| private static function deleteRangeInternal(element:FlowGroupElement, relativeStart:int, numToDelete:int):void |
| { |
| var pendingDeleteStart:int = -1; |
| var pendingDeleteCount:int = 0; |
| |
| var childIndex:int = element.findChildIndexAtPosition(relativeStart); |
| while (numToDelete > 0 && childIndex < element.numChildren) |
| { |
| var child:FlowElement = element.getChildAt(childIndex); |
| if (relativeStart <= child.parentRelativeStart && numToDelete >= child.textLength) // remove the entire child |
| { |
| if (pendingDeleteStart < 0) |
| pendingDeleteStart = childIndex; |
| pendingDeleteCount++; |
| numToDelete -= child.textLength; |
| } |
| else // deleting part of the child |
| { |
| if (pendingDeleteStart >= 0) |
| { |
| element.replaceChildren(pendingDeleteStart, pendingDeleteStart + pendingDeleteCount); |
| childIndex -= pendingDeleteCount; |
| pendingDeleteStart = -1; |
| pendingDeleteCount = 0; |
| } |
| var childStart:int = child.parentRelativeStart; |
| var childRelativeStart:int = Math.max(relativeStart - childStart, 0); |
| var childNumToDelete:int = Math.min(child.textLength - childRelativeStart, numToDelete); |
| if (child is SpanElement) |
| { |
| var span:SpanElement = child as SpanElement; |
| span.replaceText(childRelativeStart, childRelativeStart + childNumToDelete, ""); |
| numToDelete -= childNumToDelete; |
| } |
| else |
| { |
| CONFIG::debug { assert (child is FlowGroupElement, "Expected FlowGroupElement"); } |
| deleteRangeInternal(child as FlowGroupElement, childRelativeStart, childNumToDelete); |
| numToDelete -= childNumToDelete; |
| } |
| } |
| childIndex++ |
| } |
| if (pendingDeleteStart >= 0) |
| element.replaceChildren(pendingDeleteStart, pendingDeleteStart + pendingDeleteCount); |
| } |
| |
| // Find the lowest possible FlowElement ancestor of element that can accept prospectiveChild as a child element. |
| private static function findLowestPossibleParent(element:FlowGroupElement, prospectiveChild:FlowElement):FlowGroupElement |
| { |
| while (element && !element.canOwnFlowElement(prospectiveChild)) |
| element = element.parent; |
| return element; |
| } |
| |
| private static function removePasteAttributes(element:FlowElement):void |
| { |
| if (!element) |
| return; |
| |
| if (element is FlowGroupElement && element.format) |
| { |
| var flowGroupElement:FlowGroupElement = FlowGroupElement(element); |
| if (element.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) !== undefined) |
| removePasteAttributes(flowGroupElement.getChildAt(flowGroupElement.numChildren - 1)); |
| } |
| element.setStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE, undefined); |
| } |
| |
| // Apply the formatting attributes from the (soon to be) previous element to the insertThis element(s). Used when we're about to |
| // insert the element(s) and we want it to adopt the formatting of its context. |
| private static function applyFormatToElement(destinationElement:FlowGroupElement, childIndex:int, insertThis:Object):void |
| { |
| var formatSourceSibling:FlowElement; |
| |
| // find the previous sibling and use its formats for the new siblings |
| if (childIndex > 0) |
| formatSourceSibling = destinationElement.getChildAt(childIndex - 1); |
| else |
| formatSourceSibling = destinationElement.getChildAt(0); |
| if (formatSourceSibling) |
| { |
| var spanFormat:ITextLayoutFormat; |
| if (formatSourceSibling is FlowGroupElement) // take all levels from the sibling down to the root into account |
| { |
| var element:FlowElement = FlowGroupElement(formatSourceSibling).getLastLeaf(); |
| var concatFormat:TextLayoutFormat; |
| while (element != formatSourceSibling.parent) |
| { |
| if (element.format) |
| { |
| if (!concatFormat) |
| concatFormat = new TextLayoutFormat(element.format); |
| else |
| concatFormat.concatInheritOnly(element.format); |
| } |
| element = element.parent; |
| } |
| spanFormat = concatFormat; |
| } |
| else |
| spanFormat = formatSourceSibling.format; |
| |
| if (insertThis is Array) |
| { |
| for each (var scrapElement:FlowElement in insertThis) |
| if (scrapElement is FlowLeafElement) |
| scrapElement.format = spanFormat; |
| else |
| scrapElement.format = formatSourceSibling.format; |
| } |
| else if (insertThis is FlowLeafElement) |
| insertThis.format = spanFormat; |
| else |
| insertThis.format = formatSourceSibling.format; |
| } |
| } |
| /** |
| * Replaces the range of text positions that the <code>startPos</code> and |
| * <code>endPos</code> parameters specify with the <code>textScrap</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>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 textFlow The TextFlow that is being inserted into. |
| * @param absoluteStart The index value of the first position of the replacement range in the TextFlow. |
| * @param textScrap The TextScrap to be pasted into theFlow. |
| */ |
| public static function insertTextScrap(textFlow:TextFlow, absoluteStart:int, textScrap:TextScrap, applyFormat:Boolean):int |
| { |
| if (!textScrap) |
| return absoluteStart; |
| |
| var scrapFlow:TextFlow = textScrap.textFlow.deepCopy() as TextFlow; |
| var scrapLeaf:FlowLeafElement = scrapFlow.getFirstLeaf(); |
| |
| var destinationLeaf:FlowLeafElement = textFlow.findLeaf(absoluteStart); |
| var insertPosition:int = absoluteStart; |
| |
| var firstParagraph:Boolean = true; |
| var doSplit:Boolean = false; |
| |
| while (scrapLeaf) |
| { |
| removePasteAttributes(scrapLeaf); |
| var scrapElement:FlowElement = scrapLeaf; // highest level complete element in the scrap |
| var scrapParagraph:ParagraphElement = scrapLeaf.getParagraph(); |
| |
| // On the first paragraph, it always merges in to the destination paragraph if the destination paragraph has content |
| var destinationParagraph:ParagraphElement = destinationLeaf ? destinationLeaf.getParagraph() : destinationParagraph; |
| if (firstParagraph && (destinationParagraph.textLength > 1 || applyFormat)) |
| { |
| if (!scrapParagraph.format || scrapParagraph.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) === undefined) |
| doSplit = true; |
| scrapElement = scrapParagraph.getChildAt(0); |
| } |
| else |
| { |
| if (applyFormat && firstParagraph) |
| { |
| destinationElement = destinationLeaf ? findLowestPossibleParent(destinationLeaf.parent, scrapElement) : findLowestPossibleParent(destinationElement, scrapElement); |
| var currentIndex:int = destinationElement.findChildIndexAtPosition(insertPosition - destinationElement.getAbsoluteStart()); |
| applyFormatToElement(destinationElement, currentIndex, scrapElement); |
| } |
| // Normally the root element of the scrap is marked as partial, but if not, just assume that its partial (we never paste the TextFlow element) |
| while (scrapElement && scrapElement.parent && (!scrapElement.parent.format || scrapElement.parent.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) === undefined) && !(scrapElement.parent is TextFlow)) |
| scrapElement = scrapElement.parent; |
| } |
| |
| |
| // Find the lowest level parent in the TextFlow that can accept the scrapElement as a child. |
| // If necessary, copy higher up the scrapElement hierarchy to find a match. |
| var destinationElement:FlowGroupElement = destinationLeaf ? findLowestPossibleParent(destinationLeaf.parent, scrapElement) : findLowestPossibleParent(destinationElement, scrapElement); |
| while (!destinationElement) |
| { |
| // Nothing in the TextFlow element hierarchy can accept the incoming scrap element. |
| // Go up the scrapElement's hierarchy of partial nodes until we find one that can be inserted. |
| scrapElement = scrapElement.parent; |
| CONFIG::debug { assert(scrapElement != null, "Couldn't find scrapElement that could be pasted"); } |
| destinationElement = findLowestPossibleParent(destinationLeaf.parent, scrapElement); |
| } |
| CONFIG::debug { assert(destinationElement != null, "insertTextScrap failed to find a FlowElement that can take the scrap element"); } |
| |
| removePasteAttributes(scrapElement); |
| |
| var destinationStart:int = destinationElement.getAbsoluteStart(); |
| if (firstParagraph && doSplit) |
| { |
| // Split the paragraph, and merge the scrap paragraph to the end of the first paragraph of the destination |
| CONFIG::debug { assert(destinationElement is ParagraphElement, "We should be splitting a paragraph"); } |
| ModelEdit.splitElement(textFlow, destinationElement, insertPosition - destinationStart); |
| var scrapParent:FlowGroupElement = scrapElement.parent; |
| var scrapChildren:Array = scrapParent.mxmlChildren; |
| scrapParent.replaceChildren(0, scrapParent.numChildren); |
| if (scrapParent.parent) |
| scrapParent.parent.removeChild(scrapParent); |
| if (applyFormat) |
| applyFormatToElement(destinationElement, destinationElement.numChildren, scrapChildren); |
| destinationElement.replaceChildren(destinationElement.numChildren, destinationElement.numChildren, scrapChildren); |
| scrapElement = destinationElement.getChildAt(destinationElement.numChildren - 1); // last span pasted, so we'll paste next after this |
| firstParagraph = false; |
| } |
| else |
| { |
| // We're going to add scrapElement as a child of destinationElement at the insertPosition. |
| // Split the children of destinationElement if necessary. |
| var childIndex:int = destinationElement.findChildIndexAtPosition(insertPosition - destinationElement.getAbsoluteStart()); |
| var child:FlowElement = destinationElement.getChildAt(childIndex); |
| var childStart:int = child.getAbsoluteStart(); |
| if (insertPosition == childStart + child.textLength) |
| ++childIndex; |
| else if (insertPosition > childStart) |
| { |
| if (child is FlowLeafElement) |
| child.splitAtPosition(insertPosition - childStart); |
| else |
| ModelEdit.splitElement(textFlow, child as FlowGroupElement, insertPosition - childStart); |
| ++childIndex; |
| } |
| if (applyFormat) |
| applyFormatToElement(destinationElement, childIndex, scrapElement); |
| destinationElement.replaceChildren(childIndex, childIndex, scrapElement); |
| } |
| |
| |
| // Advance to the next destination leaf |
| destinationLeaf = (scrapElement is FlowLeafElement) ? FlowLeafElement(scrapElement).getNextLeaf() : FlowGroupElement(scrapElement).getLastLeaf().getNextLeaf(); |
| insertPosition = destinationLeaf ? destinationLeaf.getAbsoluteStart() : textFlow.textLength - 1; |
| |
| scrapLeaf = scrapFlow.getFirstLeaf(); |
| // check to make sure we're inserting into the right paragraph |
| if(destinationLeaf && scrapLeaf && scrapLeaf.getParagraph() == scrapParagraph) |
| { |
| if(destinationLeaf.getParagraph() != destinationParagraph) |
| { |
| insertPosition--; |
| destinationLeaf = null; |
| } |
| } |
| } |
| // keep the cursor in the same paragraph unless a whole paragraph was inserted |
| if(scrapParagraph.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) == "true" && insertPosition == destinationParagraph.getAbsoluteStart() + destinationParagraph.textLength) |
| insertPosition--; |
| |
| return insertPosition; |
| } |
| |
| /** |
| * 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! |
| madeTCY = madeTCY && 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! |
| madeLink = madeLink && 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 SubParagraphGroupElementBase (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:SubParagraphGroupElementBase - 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:SubParagraphGroupElementBase, 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 SubParagraphGroupElementBase, 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 SubParagraphGroupElementBase 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 SubParagraphGroupElementBase && splitSubBlockContents) |
| { |
| var subBlock:SubParagraphGroupElementBase = SubParagraphGroupElementBase(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:SubParagraphGroupElementBase - the new SubParagraphGroupElementBase 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:SubParagraphGroupElementBase, 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); |
| |
| //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:SubParagraphGroupElementBase = curFlowEl as SubParagraphGroupElementBase; |
| //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); |
| //<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 SubParagraphGroupElementBase) |
| { |
| //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 SubParagraphGroupElementBase, spgClass); |
| } |
| newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, curFlowEl); |
| |
| if(newSPB.numChildren == 1 && curFlowEl is SubParagraphGroupElementBase) |
| { |
| var childSPGE:SubParagraphGroupElementBase = curFlowEl as SubParagraphGroupElementBase; |
| //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); |
| |
| //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); |
| //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 SubParagraphGroupElementBase 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) && !(containerFBE 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 SubParagraphGroupElementBase, "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); |
| parentBlock.replaceChildren(idxInParent, idxInParent, childFE); |
| idxInParent++; |
| } |
| |
| //remove the curFBE |
| parentBlock.replaceChildren(idxInParent, idxInParent + 1); |
| } |
| else if(curEl is SubParagraphGroupElementBase) //check all the parents... |
| { |
| var curSPB:SubParagraphGroupElementBase = SubParagraphGroupElementBase(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 SubParagraphGroupElementBase, 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 SubParagraphGroupElementBase |
| 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 SubParagraphGroupElementBase && !(anchorFBE is blockClass)) |
| { |
| var anchorStart:int = anchorFBE.getAbsoluteStart(); |
| if(startPos > anchorStart && endPos > anchorStart + anchorFBE.textLength) |
| return false; |
| } |
| else if((anchorFBE.parent is SubParagraphGroupElementBase || tailFBE.parent is SubParagraphGroupElementBase) |
| && 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 SubParagraphGroupElementBase && !(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:SubParagraphGroupElementBase, 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); |
| //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); |
| } |
| else if(subFE is SubParagraphGroupElementBase) |
| { |
| flushSPBlock(subFE as SubParagraphGroupElementBase, spgClass); |
| ++subParaIter; |
| } |
| else |
| ++subParaIter;//go to next child |
| } |
| } |
| |
| /** returns next paragraph in reading order after para. Used for merging paragraphs after delete. */ |
| tlf_internal static function findNextParagraph(para:ParagraphElement):ParagraphElement |
| { |
| if (para) |
| { |
| var leaf:FlowLeafElement = para.getLastLeaf(); |
| leaf = leaf.getNextLeaf(); |
| if (leaf) |
| return leaf.getParagraph(); |
| } |
| return null; |
| /* var sibParagraph:ParagraphElement; |
| if (para && para.parent) |
| { |
| var child:FlowGroupElement = para; |
| var parent:FlowGroupElement = para.parent; |
| |
| var myidx:int = parent.getChildIndex(child); |
| |
| // go up the chain till not on last child |
| while(myidx == parent.numChildren-1) |
| { |
| child = parent; |
| parent = parent.parent; |
| myidx = parent.getChildIndex(child); |
| } |
| if (myidx != parent.numChildren-1) |
| { |
| // go down the first child descendents till reach a paragraph |
| var sibElement:FlowGroupElement = parent.getChildAt(myidx+1) as FlowGroupElement; |
| while(sibElement && !(sibElement is ParagraphElement)) |
| { |
| sibElement = sibElement.getChildAt(0) as FlowGroupElement; |
| } |
| sibParagraph = sibElement as ParagraphElement; |
| } |
| } |
| return sibParagraph; */ |
| } |
| |
| /** if parent is a singleton element, deletes it, then repeats deletion of singletons up the parent chain. Used after paragraph merge. */ |
| tlf_internal static function removeEmptyParentChain(parent:FlowGroupElement):IMemento |
| { |
| if(parent is ParagraphElement) |
| ParagraphElement(parent).removeEmptyTerminator(); |
| var mementoList:MementoList = new MementoList(parent.getTextFlow()); |
| while(parent && (parent.numChildren == 0)) |
| { |
| var grandParent:FlowGroupElement = parent.parent; |
| if(grandParent is ParagraphElement) |
| ParagraphElement(grandParent).removeEmptyTerminator(); |
| if(grandParent) |
| { |
| var parentIdx:int = grandParent.getChildIndex(parent); |
| mementoList.push(ModelEdit.removeElements(grandParent.getTextFlow(), grandParent, parentIdx, 1)); |
| //grandParent.replaceChildren(parentIdx, parentIdx+1); |
| } |
| parent = grandParent; |
| } |
| return mementoList; |
| } |
| |
| /** Joins this paragraph's next sibling to this if it is a paragraph */ |
| static public function joinNextParagraph(para:ParagraphElement, inSameParent:Boolean):IMemento |
| { |
| var nextPara:ParagraphElement = findNextParagraph(para); |
| if (nextPara && (!inSameParent || para.parent == nextPara.parent)) |
| return joinToElement(para, nextPara); |
| return null; |
| } |
| |
| /** Joins this paragraph's next sibling to this if it is a paragraph */ |
| static public function joinToNextParagraph(para:ParagraphElement, inSameParent:Boolean):MementoList |
| { |
| var sibParagraph:ParagraphElement = findNextParagraph(para); |
| if (sibParagraph && (!inSameParent || para.parent == sibParagraph.parent)) |
| return joinToNextElement(para, sibParagraph); |
| return null; |
| } |
| |
| /** Joins this element2 to element1 -- all children of element2 added to end of element1 */ |
| static public function joinToElement(element1:FlowGroupElement, element2:FlowGroupElement):IMemento |
| { |
| var list:MementoList; |
| |
| if (element1 && element2) |
| { |
| /* list = new MementoList(element1.getTextFlow()); |
| |
| var elementList:Array = element2.mxmlChildren; |
| |
| list.push(ModelEdit.removeElements(element2.getTextFlow(), element2, 0, element2.numChildren)); // remove children of the second element |
| |
| for(var i:int=0; i<elementList.length; ++i) // add them to the first element |
| { |
| list.push(ModelEdit.addElement(element1.getTextFlow(), elementList[i], element1, element1.numChildren)); |
| } |
| // remove (empty) element2 and chain of any empty parents |
| list.push(removeEmptyParentChain(element2)); |
| return list; |
| */ |
| return ModelEdit.joinElement(element2.getTextFlow(), element1, element2); |
| } |
| return list; |
| } |
| |
| /** Joins this element1 to element2 -- all children of element1 added to front of element2 */ |
| static public function joinToNextElement(element1:FlowGroupElement, element2:FlowGroupElement):MementoList |
| { |
| var list:MementoList; |
| |
| if (element1 && element2) |
| { |
| list = new MementoList(element1.getTextFlow()); |
| |
| var elementList:Array = element1.mxmlChildren; |
| list.push(ModelEdit.removeElements(element1.getTextFlow(), element1, 0, element1.numChildren)); // remove children of the first element |
| for(var i:int=elementList.length - 1; i>=0; --i) // add them to the second element |
| { |
| list.push(ModelEdit.addElement(element2.getTextFlow(), elementList[i], element2, 0)); |
| } |
| // remove (empty) element1 and chain of any empty parents |
| list.push(removeEmptyParentChain(element1)); |
| return list; |
| } |
| return list; |
| } |
| |
| |
| } |
| } |