1//////////////////////////////////////////////////////////////////////////////// 2// 3// ADOBE SYSTEMS INCORPORATED 4// Copyright 2007-2010 Adobe Systems Incorporated 5// All Rights Reserved. 6// 7// NOTICE: Adobe permits you to use, modify, and distribute this file 8// in accordance with the terms of the license agreement accompanying it. 9// 10//////////////////////////////////////////////////////////////////////////////// 11package flashx.textLayout.edit 12{ 13 import flash.utils.getQualifiedClassName; 14 15 import flashx.textLayout.conversion.ConverterBase; 16 import flashx.textLayout.debug.assert; 17 import flashx.textLayout.elements.ContainerFormattedElement; 18 import flashx.textLayout.elements.DivElement; 19 import flashx.textLayout.elements.FlowElement; 20 import flashx.textLayout.elements.FlowGroupElement; 21 import flashx.textLayout.elements.FlowLeafElement; 22 import flashx.textLayout.elements.LinkElement; 23 import flashx.textLayout.elements.ListItemElement; 24 import flashx.textLayout.elements.ParagraphElement; 25 import flashx.textLayout.elements.SpanElement; 26 import flashx.textLayout.elements.SubParagraphGroupElementBase; 27 import flashx.textLayout.elements.TCYElement; 28 import flashx.textLayout.elements.TextFlow; 29 import flashx.textLayout.formats.ITextLayoutFormat; 30 import flashx.textLayout.formats.TextLayoutFormat; 31 import flashx.textLayout.tlf_internal; 32 33 use namespace tlf_internal; 34 35 [ExcludeClass] 36 /** 37 * Encapsulates all methods necessary for dynamic editing of a text. The methods are all static member functions of this class. 38 * @private - because we can't make it tlf_internal. Used by the operations package 39 */ 40 public class TextFlowEdit 41 { 42 tlf_internal static function deleteRange(textFlow:TextFlow, startPos:int, endPos:int):ParagraphElement 43 { 44 var mergePara:ParagraphElement; 45 46 // If the range to be deleted contains the paragraph end, we may have to merge up the paragraphs when we're done. 47 if (endPos > startPos) 48 { 49 var firstLeafInRange:FlowLeafElement = textFlow.findLeaf(startPos); 50 var lastLeafInRange:FlowLeafElement = textFlow.findLeaf(endPos - 1); 51 var firstParagraphInRange:ParagraphElement = firstLeafInRange.getParagraph(); 52 var lastParagraphInRange:ParagraphElement = lastLeafInRange.getParagraph(); 53 var firstParaStart:int = firstParagraphInRange.getAbsoluteStart(); 54 var lastParaEnd:int = lastParagraphInRange.getAbsoluteStart() + lastParagraphInRange.textLength; 55 // If the selection is inside a single paragraph, merge only if the terminator is included and the start of the paragraph is not. 56 // If the two paragraphs are different, merge unless the start and end match exactly 57 // 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) 58 var doMerge:Boolean = false; 59 if (firstParagraphInRange == lastParagraphInRange) 60 doMerge = (endPos == lastParaEnd && startPos != firstParaStart); 61 else 62 doMerge = (startPos != firstParaStart); 63 if (doMerge) 64 { 65 var followingLeaf:FlowLeafElement = textFlow.findLeaf(endPos); 66 if (followingLeaf) 67 { 68 mergePara = followingLeaf.getParagraph(); 69 if (mergePara.textLength == 1 && mergePara.parent is ListItemElement && mergePara.parent.numChildren > 1) 70 mergePara = null; 71 } 72 } 73 } 74 75 deleteRangeInternal(textFlow, startPos, endPos - startPos); 76 77 if (mergePara) 78 { 79 var previousLeaf:FlowLeafElement = mergePara.getFirstLeaf().getPreviousLeaf(); 80 mergePara = previousLeaf ? previousLeaf.getParagraph() : null; 81 } 82 83 return mergePara; 84 } 85 86 private static function deleteRangeInternal(element:FlowGroupElement, relativeStart:int, numToDelete:int):void 87 { 88 var pendingDeleteStart:int = -1; 89 var pendingDeleteCount:int = 0; 90 91 var childIndex:int = element.findChildIndexAtPosition(relativeStart); 92 while (numToDelete > 0 && childIndex < element.numChildren) 93 { 94 var child:FlowElement = element.getChildAt(childIndex); 95 if (relativeStart <= child.parentRelativeStart && numToDelete >= child.textLength) // remove the entire child 96 { 97 if (pendingDeleteStart < 0) 98 pendingDeleteStart = childIndex; 99 pendingDeleteCount++; 100 numToDelete -= child.textLength; 101 } 102 else // deleting part of the child 103 { 104 if (pendingDeleteStart >= 0) 105 { 106 element.replaceChildren(pendingDeleteStart, pendingDeleteStart + pendingDeleteCount); 107 childIndex -= pendingDeleteCount; 108 pendingDeleteStart = -1; 109 pendingDeleteCount = 0; 110 } 111 var childStart:int = child.parentRelativeStart; 112 var childRelativeStart:int = Math.max(relativeStart - childStart, 0); 113 var childNumToDelete:int = Math.min(child.textLength - childRelativeStart, numToDelete); 114 if (child is SpanElement) 115 { 116 var span:SpanElement = child as SpanElement; 117 span.replaceText(childRelativeStart, childRelativeStart + childNumToDelete, ""); 118 numToDelete -= childNumToDelete; 119 } 120 else 121 { 122 CONFIG::debug { assert (child is FlowGroupElement, "Expected FlowGroupElement"); } 123 deleteRangeInternal(child as FlowGroupElement, childRelativeStart, childNumToDelete); 124 numToDelete -= childNumToDelete; 125 } 126 } 127 childIndex++ 128 } 129 if (pendingDeleteStart >= 0) 130 element.replaceChildren(pendingDeleteStart, pendingDeleteStart + pendingDeleteCount); 131 } 132 133 // Find the lowest possible FlowElement ancestor of element that can accept prospectiveChild as a child element. 134 private static function findLowestPossibleParent(element:FlowGroupElement, prospectiveChild:FlowElement):FlowGroupElement 135 { 136 while (element && !element.canOwnFlowElement(prospectiveChild)) 137 element = element.parent; 138 return element; 139 } 140 141 private static function removePasteAttributes(element:FlowElement):void 142 { 143 if (!element) 144 return; 145 146 if (element is FlowGroupElement && element.format) 147 { 148 var flowGroupElement:FlowGroupElement = FlowGroupElement(element); 149 if (element.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) !== undefined) 150 removePasteAttributes(flowGroupElement.getChildAt(flowGroupElement.numChildren - 1)); 151 } 152 element.setStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE, undefined); 153 } 154 155 // Apply the formatting attributes from the (soon to be) previous element to the insertThis element(s). Used when we're about to 156 // insert the element(s) and we want it to adopt the formatting of its context. 157 private static function applyFormatToElement(destinationElement:FlowGroupElement, childIndex:int, insertThis:Object):void 158 { 159 var formatSourceSibling:FlowElement; 160 161 // find the previous sibling and use its formats for the new siblings 162 if (childIndex > 0) 163 formatSourceSibling = destinationElement.getChildAt(childIndex - 1); 164 else 165 formatSourceSibling = destinationElement.getChildAt(0); 166 if (formatSourceSibling) 167 { 168 var spanFormat:ITextLayoutFormat; 169 if (formatSourceSibling is FlowGroupElement) // take all levels from the sibling down to the root into account 170 { 171 var element:FlowElement = FlowGroupElement(formatSourceSibling).getLastLeaf(); 172 var concatFormat:TextLayoutFormat; 173 while (element != formatSourceSibling.parent) 174 { 175 if (element.format) 176 { 177 if (!concatFormat) 178 concatFormat = new TextLayoutFormat(element.format); 179 else 180 concatFormat.concatInheritOnly(element.format); 181 } 182 element = element.parent; 183 } 184 spanFormat = concatFormat; 185 } 186 else 187 spanFormat = formatSourceSibling.format; 188 189 if (insertThis is Array) 190 { 191 for each (var scrapElement:FlowElement in insertThis) 192 if (scrapElement is FlowLeafElement) 193 scrapElement.format = spanFormat; 194 else 195 scrapElement.format = formatSourceSibling.format; 196 } 197 else if (insertThis is FlowLeafElement) 198 insertThis.format = spanFormat; 199 else 200 insertThis.format = formatSourceSibling.format; 201 } 202 } 203 /** 204 * Replaces the range of text positions that the <code>startPos</code> and 205 * <code>endPos</code> parameters specify with the <code>textScrap</code> parameter in 206 * <code>theFlow</code>. 207 * <p>To delete elements, pass <code>null</code> for <code>newTextFlow</code>.</p> 208 * <p>To insert an element, pass the same value for <code>startPos</code> and <code>endPos</code>. 209 * <p>The new element will be inserted before the specified index.</p> 210 * <p>To append the TextFlow, pass <code>theFlow.length</code> for <code>startPos</code> and <code>endPos</code>.</p> 211 * 212 * @param textFlow The TextFlow that is being inserted into. 213 * @param absoluteStart The index value of the first position of the replacement range in the TextFlow. 214 * @param textScrap The TextScrap to be pasted into theFlow. 215 */ 216 public static function insertTextScrap(textFlow:TextFlow, absoluteStart:int, textScrap:TextScrap, applyFormat:Boolean):int 217 { 218 if (!textScrap) 219 return absoluteStart; 220 221 var scrapFlow:TextFlow = textScrap.textFlow.deepCopy() as TextFlow; 222 var scrapLeaf:FlowLeafElement = scrapFlow.getFirstLeaf(); 223 224 var destinationLeaf:FlowLeafElement = textFlow.findLeaf(absoluteStart); 225 var insertPosition:int = absoluteStart; 226 227 var firstParagraph:Boolean = true; 228 var doSplit:Boolean = false; 229 230 while (scrapLeaf) 231 { 232 removePasteAttributes(scrapLeaf); 233 var scrapElement:FlowElement = scrapLeaf; // highest level complete element in the scrap 234 235 // On the first paragraph, it always merges in to the destination paragraph if the destination paragraph has content 236 var destinationParagraph:ParagraphElement = destinationLeaf.getParagraph(); 237 if (firstParagraph && (destinationParagraph.textLength > 1 || applyFormat)) 238 { 239 var scrapParagraph:ParagraphElement = scrapLeaf.getParagraph(); 240 if (!scrapParagraph.format || scrapParagraph.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) === undefined) 241 doSplit = true; 242 scrapElement = scrapParagraph.getChildAt(0); 243 } 244 else 245 { 246 if (applyFormat && firstParagraph) 247 { 248 destinationElement = findLowestPossibleParent(destinationLeaf.parent, scrapElement); 249 var currentIndex:int = destinationElement.findChildIndexAtPosition(insertPosition - destinationElement.getAbsoluteStart()); 250 applyFormatToElement(destinationElement, currentIndex, scrapElement); 251 } 252 // 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) 253 while (scrapElement && scrapElement.parent && (!scrapElement.parent.format || scrapElement.parent.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) === undefined) && !(scrapElement.parent is TextFlow)) 254 scrapElement = scrapElement.parent; 255 } 256 257 258 // Find the lowest level parent in the TextFlow that can accept the scrapElement as a child. 259 // If necessary, copy higher up the scrapElement hierarchy to find a match. 260 var destinationElement:FlowGroupElement = findLowestPossibleParent(destinationLeaf.parent, scrapElement); 261 while (!destinationElement) 262 { 263 // Nothing in the TextFlow element hierarchy can accept the incoming scrap element. 264 // Go up the scrapElement's hierarchy of partial nodes until we find one that can be inserted. 265 scrapElement = scrapElement.parent; 266 CONFIG::debug { assert(scrapElement != null, "Couldn't find scrapElement that could be pasted"); } 267 destinationElement = findLowestPossibleParent(destinationLeaf.parent, scrapElement); 268 } 269 CONFIG::debug { assert(destinationElement != null, "insertTextScrap failed to find a FlowElement that can take the scrap element"); } 270 271 removePasteAttributes(scrapElement); 272 273 var destinationStart:int = destinationElement.getAbsoluteStart(); 274 if (firstParagraph && doSplit) 275 { 276 // Split the paragraph, and merge the scrap paragraph to the end of the first paragraph of the destination 277 CONFIG::debug { assert(destinationElement is ParagraphElement, "We should be splitting a paragraph"); } 278 ModelEdit.splitElement(textFlow, destinationElement, insertPosition - destinationStart); 279 var scrapParent:FlowGroupElement = scrapElement.parent; 280 var scrapChildren:Array = scrapParent.mxmlChildren; 281 scrapParent.replaceChildren(0, scrapParent.numChildren); 282 if (scrapParent.parent) 283 scrapParent.parent.removeChild(scrapParent); 284 if (applyFormat) 285 applyFormatToElement(destinationElement, destinationElement.numChildren, scrapChildren); 286 destinationElement.replaceChildren(destinationElement.numChildren, destinationElement.numChildren, scrapChildren); 287 scrapElement = destinationElement.getChildAt(destinationElement.numChildren - 1); // last span pasted, so we'll paste next after this 288 firstParagraph = false; 289 } 290 else 291 { 292 // We're going to add scrapElement as a child of destinationElement at the insertPosition. 293 // Split the children of destinationElement if necessary. 294 var childIndex:int = destinationElement.findChildIndexAtPosition(insertPosition - destinationElement.getAbsoluteStart()); 295 var child:FlowElement = destinationElement.getChildAt(childIndex); 296 var childStart:int = child.getAbsoluteStart(); 297 if (insertPosition == childStart + child.textLength) 298 ++childIndex; 299 else if (insertPosition > childStart) 300 { 301 if (child is FlowLeafElement) 302 child.splitAtPosition(insertPosition - childStart); 303 else 304 ModelEdit.splitElement(textFlow, child as FlowGroupElement, insertPosition - childStart); 305 ++childIndex; 306 } 307 if (applyFormat) 308 applyFormatToElement(destinationElement, childIndex, scrapElement); 309 destinationElement.replaceChildren(childIndex, childIndex, scrapElement); 310 } 311 312 313 // Advance to the next destination leaf 314 destinationLeaf = (scrapElement is FlowLeafElement) ? FlowLeafElement(scrapElement).getNextLeaf() : FlowGroupElement(scrapElement).getLastLeaf().getNextLeaf(); 315 insertPosition = destinationLeaf ? destinationLeaf.getAbsoluteStart() : textFlow.textLength - 1; 316 317 scrapLeaf = scrapFlow.getFirstLeaf(); 318 } 319 320 return insertPosition; 321 } 322 323 /** 324 * Creates a TCY run out of the selected positions. 325 * @param theFlow The TextFlow of interest. 326 * @param startPos The index value of the first position of the TextFlow to be turned into a TCY run. 327 * @param endPos The index value following the end position of the TextFlow to be turned into a TCY run. 328 */ 329 public static function makeTCY(theFlow:TextFlow, startPos:int, endPos:int):Boolean 330 { 331 var madeTCY:Boolean = true; 332 var curPara:ParagraphElement = theFlow.findAbsoluteParagraph(startPos); 333 if(!curPara) 334 return false; 335 while(curPara) 336 { 337 var paraEnd:int = curPara.getAbsoluteStart() + curPara.textLength; 338 var curEndPos:int = Math.min(paraEnd, endPos); 339 340 //we have an entire para selected and the para only contains a kParaTerminator char, which cannot be 341 //made into TCY. 342 if(canInsertSPBlock(theFlow, startPos, curEndPos, TCYElement) && curPara.textLength > 1) 343 { 344 var new_tcyElem:TCYElement = new TCYElement(); 345 346 //don't hide an error! 347 madeTCY = madeTCY && insertNewSPBlock(theFlow, startPos, curEndPos, new_tcyElem, TCYElement); 348 } 349 else 350 madeTCY = false; 351 352 if(paraEnd < endPos) 353 { 354 curPara = theFlow.findAbsoluteParagraph(curEndPos); 355 startPos = curEndPos; 356 } 357 else 358 curPara = null; 359 } 360 361 return madeTCY; 362 } 363 364 /** 365 * Creates one or more LinkElements out of the selected positions. It will go through 366 * every paragraph within the selected position and make links. 367 * @param theFlow The TextFlow of interest. 368 * @param startPos The index value of the first position of the TextFlow to be turned into a link. 369 * @param endPos The index value following the end position of the TextFlow to be turned into a link. 370 * @param urlString The url string to be associated with the link. 371 */ 372 public static function makeLink(theFlow:TextFlow, startPos:int, endPos:int, urlString:String, target:String):Boolean 373 { 374 var madeLink:Boolean = true; 375 var curPara:ParagraphElement = theFlow.findAbsoluteParagraph(startPos); 376 if(!curPara) 377 return false; 378 379 while(curPara) 380 { 381 var paraEnd:int = curPara.getAbsoluteStart() + curPara.textLength; 382 var curEndPos:int = Math.min(paraEnd, endPos); 383 var linkEndPos:int = (curEndPos == paraEnd) ? (curEndPos - 1) : curEndPos; 384 if (linkEndPos > startPos) 385 { 386 //if the end of the paragraph is < endPos, we are going across bounds 387 if(!canInsertSPBlock(theFlow, startPos, linkEndPos, LinkElement)) 388 { 389 return false; 390 } 391 392 var newLinkElement:LinkElement = new LinkElement(); 393 newLinkElement.href = urlString; 394 newLinkElement.target = target; 395 396 //don't hide an error! 397 madeLink = madeLink && insertNewSPBlock(theFlow, startPos, linkEndPos, newLinkElement, LinkElement); 398 } 399 if(paraEnd < endPos) 400 { 401 curPara = theFlow.findAbsoluteParagraph(curEndPos); 402 startPos = curEndPos; 403 } 404 else 405 curPara = null; 406 } 407 408 return madeLink; 409 } 410 411 412 /** 413 * Removes the TCY block at the selected positions. 414 * @param theFlow The TextFlow of interest. 415 * @param startPos The index value of the first position of the TextFlow. 416 * @param endPos The index value following the end position of the TextFlow. 417 */ 418 public static function removeTCY(theFlow:TextFlow, startPos:int, endPos:int):Boolean 419 { 420 if (endPos <= startPos) 421 { 422 return false; 423 } 424 425 return findAndRemoveFlowGroupElement(theFlow, startPos, endPos, TCYElement); 426 } 427 428 /** 429 * Removes all LinkElements under the selected positions. It will go through 430 * every paragraph within the selected position and remove any link. 431 * @param theFlow The TextFlow of interest. 432 * @param startPos The index value of the first position of the TextFlow. 433 * @param endPos The index value following the end position of the TextFlow. 434 */ 435 public static function removeLink(theFlow:TextFlow, startPos:int, endPos:int):Boolean 436 { 437 if (endPos <= startPos) 438 { 439 return false; 440 } 441 442 return findAndRemoveFlowGroupElement(theFlow, startPos, endPos, LinkElement); 443 } 444 445 /** 446 * @private 447 * insertNewSPBlock - add a SubParagraphGroupElementBase (spg) to <code>theFlow</code> at the indicies specified by <code>startPos</code> and 448 * <code>endPos</code>. The <code>newSPB</code> will take ownership of any FlowElements within the range and will split them 449 * as needed. If the parent of the FlowGroupElement indicated by <code>startPos</code> is the same as <code>spgClass</code> then 450 * 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> 451 * found within the indicies, however, is subsumed into <code>newSPB</code>, effectively replacing it. 452 * 453 * @param theFlow:TextFlow - The TextFlow that is the destination for the newSPB 454 * @param startPos:int - The absolute index value of the first position of the range in the TextFlow to perform the insertion. 455 * @param endPos:int - The index value following the end position of the range in the TextFlow to perform the insertion. 456 * @param newSPB:SubParagraphGroupElementBase - The new SubParagraphElement which is to be added into theFlow. 457 * @param spgClass:Class - the class of the fbe we intend to add. 458 * 459 * Examples: Simple and complex where insertion is of <code>spgClass</code> b. Selection is l~o 460 * 1) <a><span>ghijklmnop</span></a> 461 * 2) <a><span>ghij</span><b><span>klm</span></b><span>nop</span></a> 462 * 3) <a><span>ghijk</span><c><span>lmn</span></c><span>op</span></a> 463 * 464 */ 465 tlf_internal static function insertNewSPBlock(theFlow:TextFlow, startPos:int, endPos:int, newSPB:SubParagraphGroupElementBase, spgClass:Class):Boolean 466 { 467 var curPos:int = startPos; 468 var curFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(curPos); 469 var elementIdx:int = 0; 470 471 CONFIG::debug{ assert(curFBE != null, "No FBE at location curPos(" + curPos + ")!");} 472 473 //if we are at the last "real" glyph of the paragraph, include the terminator. 474 var paraEl:ParagraphElement = curFBE.getParagraph(); 475 if(endPos == (paraEl.getAbsoluteStart() + paraEl.textLength - 1)) 476 ++endPos; 477 478 //before processing this any further, we need to make sure that we are not 479 //splitting a spg which is contained within the same type of spg as the curFBE's parent. 480 //for example, if we had a tcyElement inside a linkElement, then we cannot allow a link element 481 //to be made within the tcyElement as the link would not function. As a rule, a SubParagraphElement 482 //cannot own a child of the same class. 483 // 484 //However, if the curFBE is parented by a spg and the objects have the same start and end, then we are doing 485 //a replace and we're not splitting the parent. Check if the bounds are the same and if so, don't skip it... 486 var parentStart:int = curFBE.parent.getAbsoluteStart(); 487 var curFBEStart:int = curFBE.getAbsoluteStart(); 488 if(curFBE.parent && curFBE.parent is spgClass && 489 !(parentStart == curFBEStart && parentStart + curFBE.parent.textLength == curFBEStart + curFBE.textLength)) 490 { 491 return false; 492 } 493 494 //entire FBE is selected and is not a paragraph, get its parent. 495 if(!(curFBE is ParagraphElement) && curPos == curFBEStart && curPos + curFBE.textLength <= endPos) 496 { 497 elementIdx = curFBE.parent.getChildIndex(curFBE); 498 curFBE = curFBE.parent; 499 } 500 //first, if the curFBE is of the same class as the newSPB, then we need to split it to allow for insertion 501 //of the new one IF the start position is > the start of the curFBE 502 // 503 //running example after this block: 504 // 1) <a><span>ghijk</span><span>lmnop</span></a> 505 // 2) <a><span>ghij</span><b><span>k</span></b><b><span>lm</span></b><span>nop</span></a> 506 // 3) <a><span>ghijk</span><c><span>lmn</span></c><span>op</span></a> - no change 507 if(curPos >= curFBEStart) 508 { 509 if(!(curFBE is spgClass)) 510 elementIdx = findAndSplitElement(curFBE, elementIdx, curPos, true); 511 else 512 { 513 elementIdx = findAndSplitElement(curFBE.parent, curFBE.parent.getChildIndex(curFBE), curPos, false); 514 curFBE = curFBE.parent; 515 } 516 } 517 518 //now that the curFBE has been split, we want to insert the newSPB into the flow and then start absorbing the 519 //contents... 520 //running example after this block: 521 // 1) <a><span>ghijk</span><b></b><span>lmnop</span></a> 522 // 2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a> 523 // 3) <a><span>ghijk</span><b></b><c><span>lmn</span></c><span>op</span></a> - no change 524 // 525 // we need another use case here where selection is entire sbp and selection runs from the head of a spg to 526 // part way through it - so that a) does into parent and b) goes into spg 527 528 // if this is case 2, then the new element must go into the parent... 529 if(curFBE is spgClass) 530 { 531 curFBEStart = curFBE.getAbsoluteStart(); 532 elementIdx = curFBE.parent.getChildIndex(curFBE); 533 if(curPos > curFBEStart)//we're splitting the element, not replacing it... 534 elementIdx += 1; 535 536 //if the spg, curFBE is entirely selected then we want to use the parent, not the item itself. 537 while(endPos >= curFBEStart + curFBE.textLength) 538 { 539 //we need access to the parent, which contains both the start and end not the FBE we just split 540 curFBE = curFBE.parent; 541 } 542 curFBE.replaceChildren(elementIdx, elementIdx, newSPB); 543 } 544 else 545 { 546 //we're inserting into the curFBE 547 curFBE.replaceChildren(elementIdx, elementIdx, newSPB); 548 } 549 550 //see subsumeElementsToSPBlock to see effects on running example 551 subsumeElementsToSPBlock(curFBE, elementIdx + 1, curPos, endPos, newSPB, spgClass); 552 553 return true; 554 } 555 556 557 /** 558 * @private 559 * splitElement - split <code>elem</code> at the relative index of <code>splitPos</code>. If <code>splitSubBlockContents</code> 560 * is true, split the contents of <code>elem</code> if it is a SubParagraphGroupElementBase, otherwise just split <code>elem</code> 561 * 562 * @param elem:FlowElement - the FlowElement to split 563 * @param splitPos:int - The elem relative index indicating where to split 564 * @param splitSubBlockContents:Boolean - boolean indicating whether a SubParagraphGroupElementBase is to be split OR that it's contents 565 * should be split. For example, are we splitting a link or are we splitting the child of the link 566 * 567 * <spg><span>ABCDEF</span></spg> 568 * 569 * if <code>splitPos</code> indicated index between C and D, then if <code>splitSubBlockContents</code> equals true, 570 * result is: 571 * 572 * <spg><span>ABC</span><span>DEF</span></spg> 573 * 574 * if <code>splitSubBlockContents</code> equals false, result is: 575 * 576 * <spg><span>ABC</span></spg><spg><span>DEF</span></spg> 577 */ 578 tlf_internal static function splitElement(elem:FlowElement, splitPos:int, splitSubBlockContents:Boolean):void 579 { 580 CONFIG::debug{ assert(splitPos < elem.textLength, "trying to splic FlowElement at illegal index!"); } 581 if (elem is SpanElement) 582 { 583 SpanElement(elem).splitAtPosition(splitPos); 584 } 585 else if(elem is SubParagraphGroupElementBase && splitSubBlockContents) 586 { 587 var subBlock:SubParagraphGroupElementBase = SubParagraphGroupElementBase(elem); 588 // Split the SpanElement of the block at splitPos. If the item at the splitPos is not a SpanElement, no action occurs. 589 var tempElem:SpanElement = subBlock.findLeaf(splitPos) as SpanElement; 590 if (tempElem) 591 tempElem.splitAtPosition(splitPos - tempElem.getElementRelativeStart(subBlock)); 592 } 593 else if (elem is FlowGroupElement) 594 { 595 FlowGroupElement(elem).splitAtPosition(splitPos); 596 } 597 else 598 { 599 CONFIG::debug { assert(false, "Trying to split on an illegal FlowElement"); } 600 } 601 } 602 603 /** 604 * @private 605 * findAndSplitElement - starting at the child <code>elementIdx</code> of <code>fbe</code>, iterate 606 * through the elements untill we find the one located at the aboslute index of <code>startIdx</code>. Upon 607 * locating the child, split either the element itself OR its children based on the value of <code>splitSubBlockContents</code> 608 * 609 * @param fbe:FlowGroupElement - the FBE into which the newSPB is being inserted. 610 * @param elementIdx:int - The index into the <code>fbe's</code> child list to start 611 * @param startIdx:int - The absolute index value into the TextFlow. 612 * @param splitSubBlockContents:Boolean - boolean indicating whether a subElement is to be split OR that it's contents 613 * should be split. For example, are we splitting a link or are we splitting the child of the link 614 * 615 * <p>ZYX<link>ABCDEF</link>123</p> 616 * 617 * if we are inserting a TCY into the link, splitSubBlockContents should be false. We want to split the span ABCDEF such that result is: 618 * <p>ZYX<link>AB<tcy>CD</tcy>EF</link>123</p> 619 * 620 * if we are creating a new link from X to B, then we want the link to split and splitSubBlockContents should be false: 621 * 622 * <p>ZY<link>XAB</link><link>CDEF</link>123</p> 623 * 624 * @return int - the index of the last child of <code>fbe</code> processed. 625 */ 626 tlf_internal static function findAndSplitElement(fbe:FlowGroupElement, elementIdx:int, startIdx:int, splitSubBlockContents:Boolean):int 627 { 628 var curFlowEl:FlowElement = null; 629 var curIndexInPar:int = startIdx - fbe.getAbsoluteStart(); 630 631 while(elementIdx < fbe.numChildren) 632 { 633 curFlowEl = fbe.getChildAt(elementIdx); 634 if (curIndexInPar == curFlowEl.parentRelativeStart) 635 return elementIdx; 636 if ((curIndexInPar > curFlowEl.parentRelativeStart) && (curIndexInPar < curFlowEl.parentRelativeEnd)) 637 { 638 splitElement(curFlowEl, curIndexInPar - curFlowEl.parentRelativeStart, splitSubBlockContents); 639 } 640 ++elementIdx; 641 } 642 return elementIdx; 643 } 644 645 /** 646 * @private 647 * subsumeElementsToSPBlock - incorporates all elements of <code>parentFBE</code> into 648 * the <code>newSPB</code> between the <code>curPos</code> and <code>endPos</code>. If a child of 649 * <code>parentFBE</code> is of type <code>spgClass</code> then the child's contents are removed from the child, 650 * added to the <code>newSPB</code>, the child is then removed from the <code>parentFBE</code> 651 * 652 * @param parentFBE:FlowGroupElement - the FBE into which the newSPB is being inserted. 653 * @param startPos:int - The index value of the first position of the replacement range in the TextFlow. 654 * @param endPos:int - The index value following the end position of the replacement range in the TextFlow. 655 * @param newSPB:SubParagraphGroupElementBase - the new SubParagraphGroupElementBase we intend to insert. 656 * @param spgClass:Class - the class of the fbe we intend to insert. 657 * 658 * @return int - the aboslute index in the text flow after insertion. 659 * 660 * Examples: Simple and complex where insertion is of <code>spgClass</class> b. Selection is l~o 661 * 1) <a><span>ghijk</span><b></b><span>lmnop</span></a> 662 * 2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a> 663 * 664 * parentFBE = <a> 665 * elementIdx = 1) 2, 2) 3 666 * curPos = 5 667 * endPos = 9 668 * newSPB is of type <b> 669 */ 670 tlf_internal static function subsumeElementsToSPBlock(parentFBE:FlowGroupElement, elementIdx:int, curPos:int, endPos:int, newSPB:SubParagraphGroupElementBase, spgClass:Class):int 671 { 672 var curFlowEl:FlowElement = null; 673 674 //if we have an invalid index, then skip out. elementIdx will always point one 675 //element beyond the one we are inserting.... 676 if(elementIdx >= parentFBE.numChildren) 677 return curPos; 678 679 while (curPos < endPos) 680 { 681 //running example: curFlowEl is the element immediately after the inserted newSPB: 682 // 1) <a><span>ghijk</span><b></b><span>lmnop</span></a> 683 // points to span-lmnop 684 // 2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a> 685 // points to b-lm 686 curFlowEl = parentFBE.getChildAt(elementIdx); 687 688 //if the curFlowEl is of the Class we are adding (spgClass), and the entire thing is selected, 689 //then we are adding the entire block, but not spliting it - perform the split on the next block 690 691 //I think this can be safely removed from here as ownership of contents is handled below. 692 //leaving in commented out code in case we need to revert - gak 05.01.08 693 /* if(curFlowEl is spgClass && curPos == curFlowEl.getAbsoluteStart() && curFlowEl.getAbsoluteStart() + curFlowEl.textLength <= endPos) 694 { 695 curPos = parentFBE.getAbsoluteStart() + parentFBE.textLength; 696 continue; 697 }*/ 698 699 //if the endPos is less than the length of the curFlowEl, then we need to split it. 700 //if the curFlowEl is NOT of class type spgClass, then we need to break it 701 // 702 //Use case: splitting a link in two (or three as will be the result with head and tail sharing 703 //attributes... 704 //running example 1 hits this, but 2 does not. Using variation of 2: 705 // 706 // example: 1) <a><span>ghijk</span><b></b><span>lmnop</span></a> 707 // 2a) <a><span>fo</span><b></b><b><span>obar</span></b></a> - selection: from o~a 708 // 709 // after this code: 710 // 1) <a><span>ghijk</span><b></b><span>lmno</span><span>p</span></a> 711 // 2a) <a><span>fo</span><b></b><b><span>oba</span></b><b><span>or</span></b></a> 712 if ((curPos + curFlowEl.textLength) > endPos) 713 { 714 splitElement(curFlowEl, endPos - curFlowEl.getAbsoluteStart(), !(curFlowEl is spgClass)); //changed to curFlowEl from newSPB as newSPB should be of type spgClass 715 } 716 717 //add the length before replacing the elements 718 curPos += curFlowEl.textLength; 719 720 //running example: after parentFBE.replaceChildren 721 // 722 // 1) curFlowEl = <span>lmno</span> 723 // <a><span>ghijk</span><b></b>{curFlowEl}<span>p</span></a> 724 // 725 // 2) curFlowEl = <b><span>lm</span></b> 726 // <a><span>ghij</span><b><span>k</span></b><b></b>{curFlowEl}<span>nop</span></a> 727 parentFBE.replaceChildren(elementIdx, elementIdx + 1); 728 729 //if the curFlowEl is of type spgClass, then we need to take its children and 730 //add them to the newSPB because a spg cannot contain a child of the same class 731 //as itself 732 // 733 // exmaple: 2) curFlowEl = <b><span>lm</span></b> 734 if (curFlowEl is spgClass) 735 { 736 var subBlock:SubParagraphGroupElementBase = curFlowEl as SubParagraphGroupElementBase; 737 //elementCount == 1 - <span>lm</span> 738 while (subBlock.numChildren > 0) 739 { 740 //fe[0] = <span>lm</span> 741 var fe:FlowElement = subBlock.getChildAt(0); 742 //<span></span> 743 subBlock.replaceChildren(0, 1); 744 //<b><span>lm</span></b> 745 newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, fe); 746 } 747 //when compelete, example 2 is: 748 //2) <a><span>ghij</span><b><span>k</span></b><b><span>lm</span></b><span>nop</span></a> 749 } 750 else 751 { 752 //example 1, curFlowEl is <span>lmno</span>, so this is not hit 753 // 754 // extending element <a> from foo~other 755 // <a>foo</a><b>bar<a>other</a><b> 756 // curFlowEl = <b>bar<a>other</a><b> 757 // 758 // since <b> is a spg, we need to walk it's contents and remove any <a> elements 759 if(curFlowEl is SubParagraphGroupElementBase) 760 { 761 //we need to dive into this spgClass and remove any fbes of type spgClass 762 //pass in the curFlowEl as the newSPB, remove any spgs of type spgClass, then 763 //perform the replace on the newSPB passed in here 764 // 765 //ignore the return value of the recursive call as the length has already been 766 //accounted for above 767 flushSPBlock(curFlowEl as SubParagraphGroupElementBase, spgClass); 768 } 769 newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, curFlowEl); 770 771 if(newSPB.numChildren == 1 && curFlowEl is SubParagraphGroupElementBase) 772 { 773 var childSPGE:SubParagraphGroupElementBase = curFlowEl as SubParagraphGroupElementBase; 774 //running example: 775 //a.precedence = 800, tcy.precedence = kMinSPGEPrecedence 776 //this = <tcy><a><span>fooBar</span></a><tcy> 777 //childSPGE = <a><span>fooBar</span></a> 778 if(childSPGE.textLength == newSPB.textLength && (curPos >= endPos)) 779 { 780 CONFIG::debug { assert(childSPGE.precedence != newSPB.precedence, "normalizeRange found two equal SPGEs"); } 781 782 //if the child's precedence is higher than mine, I need to swap 783 if(childSPGE.precedence > newSPB.precedence) 784 { 785 //first, remove the child 786 //this = <tcy></tcy> 787 newSPB.replaceChildren(0,1); 788 789 //we need to flop this object for the child 790 while(childSPGE.numChildren > 0) 791 { 792 //tempFE = <span>fooBar</span> 793 var tempFE:FlowElement = childSPGE.getChildAt(0); 794 //child = <a></a> 795 childSPGE.replaceChildren(0,1); 796 //this = <tcy><span>fooBar</span></tcy> 797 newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, tempFE); 798 } 799 800 var myIdx:int = newSPB.parent.getChildIndex(newSPB); 801 CONFIG::debug{ assert(myIdx >= 0, "Invalid index! How can a SubParagraphGroupElementBase normalizing not have a parent!"); } 802 803 //add childSPGE in my place 804 newSPB.parent.replaceChildren(myIdx, myIdx + 1, childSPGE) 805 806 //childSPGE = <tcy><a><span>fooBar</span></a></tcy> 807 childSPGE.replaceChildren(0,0,newSPB); 808 } 809 } 810 } 811 } 812 813 } 814 815 return curPos; 816 } 817 818 /** 819 * @private 820 * findAndRemoveFlowGroupElement 821 * 822 * @param theFlow The TextFlow that is containing the elements to remove. 823 * @param startPos The index value of the first position of the range in the TextFlow where we want to perform removal. 824 * @param endPos The index value following the end position of the range in the TextFlow where we want to perform removal. 825 * @param fbeClass Class the class of the fbe we intend to remove. 826 * 827 * Walks through the elements of <code>theFlow</code> looking for any FlowGroupElement of type <code>fbeClass</class> 828 * On finding one, it removes the FBE's contents and adds them back into the FBE's parent. If the class of object is 829 * embedded within another spg and this removal would break the parent spg, then the method does nothing. 830 * 831 * Example: 832 * <link>ABC<tcy>DEF</tcy>GHI</link> 833 * Selection is on E and removal of link is attempted. 834 * Because E is a child of a spg (tcy), and removing the link from E would split the parent spg (link), 835 * the action is disallowed. 836 * 837 * Running example: 838 * 1) <link><tcy><span>foo</span></tcy><span>bar</span></link> 839 * @return Boolean - true if items are removed or none are found. false if operation is illegal. 840 */ 841 tlf_internal static function findAndRemoveFlowGroupElement(theFlow:TextFlow, startPos:int, endPos:int, fbeClass:Class):Boolean 842 { 843 var curPos:int = startPos; 844 var curEl:FlowElement; 845 846 //walk through the elements 847 while (curPos < endPos) 848 { 849 var containerFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(curPos); 850 851 //if the start of the parent is the same as the start of the current containerFBE, then 852 //we potentially have the wrong object. We need to walk up the parents until we get to 853 //the one which starts at our start AND is the topmost object at that index. 854 //example: <a><b>foo</b> bar</a> - getting the object at "f" will yield the <b> element, not <a> 855 while(containerFBE.parent && containerFBE.parent.getAbsoluteStart() == containerFBE.getAbsoluteStart() && 856 !(containerFBE.parent is ParagraphElement) && !(containerFBE is ParagraphElement)) //don't go beyond paragraph 857 { 858 containerFBE = containerFBE.parent; 859 } 860 861 //if the absoluteFBE is the item we are trying to remove, we need to work with its parent, so 862 //reassign containerFBE. For example, if an entire link were selected, we'd need to get it's parent to 863 //perform the removal 864 if(containerFBE is fbeClass) 865 containerFBE = containerFBE.parent; 866 867 //before processing this any further, we need to make sure that we are not 868 //splitting a spg which is contained within the same type of spg as the curFBE's parent. 869 //for example, if we had a tcyElement inside a linkElement, then we cannot allow a link element 870 //to be broken within the tcyElement as the link would have to split the TCY. 871 var ancestorOfFBE:FlowGroupElement = containerFBE.parent; 872 while(ancestorOfFBE != null && !(ancestorOfFBE is fbeClass)) 873 { 874 if(ancestorOfFBE.parent is fbeClass) 875 { 876 return false; 877 } 878 ancestorOfFBE = ancestorOfFBE.parent; 879 } 880 881 //if this is a sbe block contained in another sbe, and it is entire within the 882 //selection bounds, we need to use the parent sbe's container. If it is splitting 883 //the child sbe, we don't allow this and it is handled later... 884 var containerFBEStart:int = containerFBE.getAbsoluteStart(); 885 if(ancestorOfFBE is fbeClass && (containerFBEStart >= curPos && containerFBEStart + containerFBE.textLength <= endPos)) 886 containerFBE = ancestorOfFBE.parent; 887 888 var childIdx:int = containerFBE.findChildIndexAtPosition(curPos - containerFBEStart); 889 curEl = containerFBE.getChildAt(childIdx); 890 if(curEl is fbeClass) 891 { 892 CONFIG::debug{ assert(curEl is SubParagraphGroupElementBase, "Wrong FBE type! Trying to remove illeage FBE!"); } 893 var curFBE:FlowGroupElement = curEl as FlowGroupElement; 894 895 //get it's parent and the index of the curFBE 896 var parentBlock:FlowGroupElement = curFBE.parent; 897 var idxInParent:int = parentBlock.getChildIndex(curFBE); 898 899 //if the curPos is not at the head of the SPB, then we need to split it here 900 //curFBE will point to the FBE starting at curPos 901 if(curPos > curFBE.getAbsoluteStart()) 902 { 903 splitElement(curFBE, curPos - curFBE.getAbsoluteStart(), false); 904 curPos = curFBE.getAbsoluteStart() + curFBE.textLength; 905 continue; 906 } 907 908 //if curFBE goes beyond the endPos, then we need to split off the tail. 909 if (curFBE.getAbsoluteStart() + curFBE.textLength > endPos) 910 { 911 splitElement(curFBE, endPos - curFBE.getAbsoluteStart(), false); 912 } 913 914 //apply the length of the curFBE to the curPos tracker. Do this before 915 //removing the contents or it will be 0! 916 curPos = curFBE.getAbsoluteStart() + curFBE.textLength; 917 918 //walk all the contents of the FBE into it's parent container 919 while (curFBE.numChildren > 0) 920 { 921 var childFE:FlowElement = curFBE.getChildAt(0); 922 curFBE.replaceChildren(0, 1); 923 parentBlock.replaceChildren(idxInParent, idxInParent, childFE); 924 idxInParent++; 925 } 926 927 //remove the curFBE 928 parentBlock.replaceChildren(idxInParent, idxInParent + 1); 929 } 930 else if(curEl is SubParagraphGroupElementBase) //check all the parents... 931 { 932 var curSPB:SubParagraphGroupElementBase = SubParagraphGroupElementBase(curEl); 933 if(curSPB.numChildren == 1) 934 curPos = curSPB.getAbsoluteStart() + curSPB.textLength; 935 else 936 { 937 curEl = curSPB.getChildAt(curSPB.findChildIndexAtPosition(curPos - curSPB.getAbsoluteStart())); 938 curPos = curEl.getAbsoluteStart() + curEl.textLength; 939 } 940 } 941 else 942 { 943 //the current block isn't the type we're looking for, so just go to the end of the 944 //FlowElement and continue 945 curPos = curEl.getAbsoluteStart() + curEl.textLength; 946 } 947 948 } 949 950 return true; 951 } 952 953 /** 954 * @private 955 * canInsertSPBlock 956 * 957 * validate that we a valid selection to allow for insertion of a subBlock. The rules are as 958 * follows: 959 * endPos > start 960 * the new block will not span multiple paragraphs 961 * if the block is going into a SubParagraphGroupElementBase, it must not split the block: 962 * example: Text - ABCDEFG with a link on CDE 963 * legal new Block - D, CD, CDE, [n-chars]CDE[n1-chars] 964 * illegal new Block - [1 + n-chars]C[D], [D]E[1 + n-chars] 965 * exception - if the newBlock is the same class as the one we are trying to split 966 * then we can truncate the original and add its contents to the new one, or extend it 967 * as appropriate 968 * 969 * @param theFlow The TextFlow that is containing the elements to validate. 970 * @param startPos The index value of the first position of the range in the TextFlow to test. 971 * @param endPos The index value following the end position of the range in the TextFlow to test. 972 * @param blockClass Class the class of the fbe we intend to insert. 973 */ 974 tlf_internal static function canInsertSPBlock(theFlow:TextFlow, startPos:int, endPos:int, blockClass:Class):Boolean 975 { 976 if(endPos <= startPos) 977 return false; 978 979 var anchorFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(startPos); 980 if(anchorFBE.getParentByType(blockClass)) 981 anchorFBE = anchorFBE.getParentByType(blockClass) as FlowGroupElement; 982 983 var tailFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(endPos - 1); 984 if(tailFBE.getParentByType(blockClass)) 985 tailFBE = tailFBE.getParentByType(blockClass) as FlowGroupElement; 986 987 //if these are the same FBEs then we are safe to insert a SubParagraphGroupElementBase 988 if(anchorFBE == tailFBE) 989 return true; 990 //make sure that the two FBEs belong to the same paragraph! 991 else if(anchorFBE.getParagraph() != tailFBE.getParagraph()) 992 return false; 993 else if(anchorFBE is blockClass && tailFBE is blockClass)//they're the same class, OK to merge, split, etc... 994 return true; 995 else if(anchorFBE is SubParagraphGroupElementBase && !(anchorFBE is blockClass)) 996 { 997 var anchorStart:int = anchorFBE.getAbsoluteStart(); 998 if(startPos > anchorStart && endPos > anchorStart + anchorFBE.textLength) 999 return false; 1000 } 1001 else if((anchorFBE.parent is SubParagraphGroupElementBase || tailFBE.parent is SubParagraphGroupElementBase) 1002 && anchorFBE.parent != tailFBE.parent) 1003 { 1004 //if either FBE parent is a SPGE and they are not the same, prevent the split. 1005 return false; 1006 } 1007 1008 //if we got here, then the anchorFBE is OK, check the tail. If endPos is pointing to the 1009 //0th character of a FlowGroupElement, we don't need to worry about the tail. 1010 if(tailFBE is SubParagraphGroupElementBase && !(tailFBE is blockClass) && endPos > tailFBE.getAbsoluteStart()) 1011 { 1012 var tailStart:int = tailFBE.getAbsoluteStart(); 1013 if(startPos < tailStart && endPos < tailStart + tailFBE.textLength) 1014 return false; 1015 } 1016 return true; 1017 } 1018 1019 /** 1020 * @private flushSPBlock recursively walk a spg looking for elements of type spgClass. On finding one, 1021 * remove it's children and then remove the object itself. Since spg's cannot hold children of the same type 1022 * as themselves, recursion is only needed for spg's of a class other than that of spgClass. 1023 * 1024 * example: subPB = <b>bar<a>other</a><b> extending an <a> element to include all of "other" 1025 */ 1026 tlf_internal static function flushSPBlock(subPB:SubParagraphGroupElementBase, spgClass:Class):void 1027 { 1028 var subParaIter:int = 0; 1029 1030 //example, subPB has 2 elements, <span>bar</span> and <a><span>other</span></a> 1031 while(subParaIter < subPB.numChildren) 1032 { 1033 //subParaIter == 0, subFE = <span>bar</span> skip the FE and move to next 1034 //subParaIter == 1, subFE = <a><span>other</span></a> - is a spgClass 1035 var subFE:FlowElement = subPB.getChildAt(subParaIter); 1036 if(subFE is spgClass) 1037 { 1038 //subParaIter == 1, subFE = <a><span>other</span></a> 1039 var subChildFBE:FlowGroupElement = subFE as FlowGroupElement; 1040 while(subChildFBE.numChildren > 0) 1041 { 1042 //subFEChild = <span>other</span> 1043 var subFEChild:FlowElement = subChildFBE.getChildAt(0); 1044 //subFEChild = <a></a> 1045 subChildFBE.replaceChildren(0, 1); 1046 //subPB = <b>barother<a></a><b> 1047 subPB.replaceChildren(subParaIter, subParaIter, subFEChild); 1048 } 1049 1050 //increment so that subParaIter points to the element we just emptied 1051 ++subParaIter; 1052 //remove the empty child 1053 //subPB = <b>barother<b> 1054 subPB.replaceChildren(subParaIter, subParaIter + 1); 1055 } 1056 else if(subFE is SubParagraphGroupElementBase) 1057 { 1058 flushSPBlock(subFE as SubParagraphGroupElementBase, spgClass); 1059 ++subParaIter; 1060 } 1061 else 1062 ++subParaIter;//go to next child 1063 } 1064 } 1065 1066 /** returns next paragraph in reading order after para. Used for merging paragraphs after delete. */ 1067 tlf_internal static function findNextParagraph(para:ParagraphElement):ParagraphElement 1068 { 1069 if (para) 1070 { 1071 var leaf:FlowLeafElement = para.getLastLeaf(); 1072 leaf = leaf.getNextLeaf(); 1073 if (leaf) 1074 return leaf.getParagraph(); 1075 } 1076 return null; 1077 /* var sibParagraph:ParagraphElement; 1078 if (para && para.parent) 1079 { 1080 var child:FlowGroupElement = para; 1081 var parent:FlowGroupElement = para.parent; 1082 1083 var myidx:int = parent.getChildIndex(child); 1084 1085 // go up the chain till not on last child 1086 while(myidx == parent.numChildren-1) 1087 { 1088 child = parent; 1089 parent = parent.parent; 1090 myidx = parent.getChildIndex(child); 1091 } 1092 if (myidx != parent.numChildren-1) 1093 { 1094 // go down the first child descendents till reach a paragraph 1095 var sibElement:FlowGroupElement = parent.getChildAt(myidx+1) as FlowGroupElement; 1096 while(sibElement && !(sibElement is ParagraphElement)) 1097 { 1098 sibElement = sibElement.getChildAt(0) as FlowGroupElement; 1099 } 1100 sibParagraph = sibElement as ParagraphElement; 1101 } 1102 } 1103 return sibParagraph; */ 1104 } 1105 1106 /** if parent is a singleton element, deletes it, then repeats deletion of singletons up the parent chain. Used after paragraph merge. */ 1107 tlf_internal static function removeEmptyParentChain(parent:FlowGroupElement):IMemento 1108 { 1109 var mementoList:MementoList = new MementoList(parent.getTextFlow()); 1110 while(parent && (parent.numChildren == 0)) 1111 { 1112 var grandParent:FlowGroupElement = parent.parent; 1113 if(grandParent) 1114 { 1115 var parentIdx:int = grandParent.getChildIndex(parent); 1116 mementoList.push(ModelEdit.removeElements(grandParent.getTextFlow(), grandParent, parentIdx, 1)); 1117 //grandParent.replaceChildren(parentIdx, parentIdx+1); 1118 } 1119 parent = grandParent; 1120 } 1121 return mementoList; 1122 } 1123 1124 /** Joins this paragraph's next sibling to this if it is a paragraph */ 1125 static public function joinNextParagraph(para:ParagraphElement, inSameParent:Boolean):IMemento 1126 { 1127 var nextPara:ParagraphElement = findNextParagraph(para); 1128 if (nextPara && (!inSameParent || para.parent == nextPara.parent)) 1129 return joinToElement(para, nextPara); 1130 return null; 1131 } 1132 1133 /** Joins this paragraph's next sibling to this if it is a paragraph */ 1134 static public function joinToNextParagraph(para:ParagraphElement, inSameParent:Boolean):MementoList 1135 { 1136 var sibParagraph:ParagraphElement = findNextParagraph(para); 1137 if (sibParagraph && (!inSameParent || para.parent == sibParagraph.parent)) 1138 return joinToNextElement(para, sibParagraph); 1139 return null; 1140 } 1141 1142 /** Joins this element2 to element1 -- all children of element2 added to end of element1 */ 1143 static public function joinToElement(element1:FlowGroupElement, element2:FlowGroupElement):IMemento 1144 { 1145 var list:MementoList; 1146 1147 if (element1 && element2) 1148 { 1149 /* list = new MementoList(element1.getTextFlow()); 1150 1151 var elementList:Array = element2.mxmlChildren; 1152 1153 list.push(ModelEdit.removeElements(element2.getTextFlow(), element2, 0, element2.numChildren)); // remove children of the second element 1154 1155 for(var i:int=0; i<elementList.length; ++i) // add them to the first element 1156 { 1157 list.push(ModelEdit.addElement(element1.getTextFlow(), elementList[i], element1, element1.numChildren)); 1158 } 1159 // remove (empty) element2 and chain of any empty parents 1160 list.push(removeEmptyParentChain(element2)); 1161 return list; 1162 */ 1163 return ModelEdit.joinElement(element2.getTextFlow(), element1, element2); 1164 } 1165 return list; 1166 } 1167 1168 /** Joins this element1 to element2 -- all children of element1 added to front of element2 */ 1169 static public function joinToNextElement(element1:FlowGroupElement, element2:FlowGroupElement):MementoList 1170 { 1171 var list:MementoList; 1172 1173 if (element1 && element2) 1174 { 1175 list = new MementoList(element1.getTextFlow()); 1176 1177 var elementList:Array = element1.mxmlChildren; 1178 list.push(ModelEdit.removeElements(element1.getTextFlow(), element1, 0, element1.numChildren)); // remove children of the first element 1179 for(var i:int=elementList.length - 1; i>=0; --i) // add them to the second element 1180 { 1181 list.push(ModelEdit.addElement(element2.getTextFlow(), elementList[i], element2, 0)); 1182 } 1183 // remove (empty) element1 and chain of any empty parents 1184 list.push(removeEmptyParentChain(element1)); 1185 return list; 1186 } 1187 return list; 1188 } 1189 1190 1191 } 1192} 1193