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 flashx.textLayout.debug.assert; 14 import flashx.textLayout.elements.FlowElement; 15 import flashx.textLayout.elements.FlowGroupElement; 16 import flashx.textLayout.elements.FlowLeafElement; 17 import flashx.textLayout.elements.ListItemElement; 18 import flashx.textLayout.elements.ParagraphElement; 19 import flashx.textLayout.elements.SpanElement; 20 import flashx.textLayout.elements.TextFlow; 21 import flashx.textLayout.events.ModelChange; 22 import flashx.textLayout.tlf_internal; 23 24 use namespace tlf_internal; 25 26 [ExcludeClass] 27 /** 28 * The ModelEdit class contains static functions for performing speficic suboperations. Each suboperation returns a "memento" for undo/redo. 29 * 30 * @playerversion Flash 10 31 * @playerversion AIR 1.5 32 * @langversion 3.0 33 */ 34 public class ModelEdit 35 { 36 public static function splitElement(textFlow:TextFlow, elemToSplit:FlowGroupElement, relativePosition:int):IMemento 37 { 38 return SplitMemento.perform(textFlow,elemToSplit,relativePosition,true); 39 } 40 41 public static function joinElement(textFlow:TextFlow, element1:FlowGroupElement, element2:FlowGroupElement):IMemento 42 { 43 return JoinMemento.perform(textFlow, element1, element2, true); 44 } 45 46 public static function addElement(textFlow:TextFlow, elemToAdd:FlowElement, parent:FlowGroupElement, index:int):IMemento 47 { 48 CONFIG::debug { assert(elemToAdd.parent == null,"Use moveElement"); } 49 return AddElementMemento.perform(textFlow,elemToAdd,parent,index,true); 50 } 51 52 public static function moveElement(textFlow:TextFlow, elemToMove:FlowElement, parent:FlowGroupElement, index:int):IMemento 53 { 54 CONFIG::debug { assert(elemToMove.parent != null,"Use addElement"); } 55 return MoveElementMemento.perform(textFlow,elemToMove,parent,index,true); 56 } 57 58 public static function removeElements(textFlow:TextFlow, elemtToRemoveParent:FlowGroupElement,startIndex:int, numElements:int):IMemento 59 { 60 return RemoveElementsMemento.perform(textFlow,elemtToRemoveParent,startIndex,numElements,true); 61 } 62 63 public static function deleteText(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int, createMemento:Boolean):IMemento 64 { 65 var memento:MementoList; 66 var mergePara:ParagraphElement; 67 68 // Special case to see if the whole of the last element of the flow is selected. If so, force the terminator at the end to be deleted 69 // so that if there is a list or a div at the end, it will be entirely removed. 70 if (absoluteEnd == textFlow.textLength - 1) 71 { 72 var lastElement:FlowElement = textFlow.getChildAt(textFlow.numChildren - 1); 73 if (absoluteStart <= lastElement.getAbsoluteStart()) 74 absoluteEnd = textFlow.textLength; 75 } 76 77 // Special case for when the last paragraph in the flow is deleted. We clone the last paragraph 78 // before letting the delete get processed. This lets whatever hierarchy is associated with the 79 // old last paragraph die a natural death, but doesn't leave the flow with no terminator. 80 var newLastParagraph:ParagraphElement; 81 if (absoluteEnd >= textFlow.textLength) 82 { 83 var lastSpan:FlowLeafElement = textFlow.getLastLeaf(); 84 var lastParagraph:ParagraphElement = lastSpan.getParagraph(); 85 newLastParagraph = new ParagraphElement(); 86 var newLastSpan:SpanElement = new SpanElement(); 87 newLastParagraph.replaceChildren(0, 0, newLastSpan); 88 newLastParagraph.format = lastParagraph.format; 89 newLastSpan.format = lastSpan.format; 90 absoluteEnd = textFlow.textLength; 91 } 92 93 if (createMemento) 94 { 95 memento = new MementoList(textFlow); 96 if (newLastParagraph) 97 memento.push(addElement(textFlow, newLastParagraph, textFlow, textFlow.numChildren)); 98 var deleteTextMemento:DeleteTextMemento = new DeleteTextMemento(textFlow, absoluteStart, absoluteEnd); 99 memento.push(deleteTextMemento); 100 101 mergePara = TextFlowEdit.deleteRange(textFlow, absoluteStart, absoluteEnd); 102 memento.push(TextFlowEdit.joinNextParagraph(mergePara, false)); 103 checkNormalize(textFlow, deleteTextMemento.commonRoot, memento); 104 } 105 else 106 { 107 if (newLastParagraph) 108 textFlow.replaceChildren(textFlow.numChildren, textFlow.numChildren, newLastParagraph); 109 mergePara = TextFlowEdit.deleteRange(textFlow, absoluteStart, absoluteEnd); 110 TextFlowEdit.joinNextParagraph(mergePara, false); 111 } 112 113 if (textFlow.interactionManager) 114 textFlow.interactionManager.notifyInsertOrDelete(absoluteStart, -(absoluteEnd - absoluteStart)); 115 116 return memento; 117 } 118 119 private static function checkNormalize(textFlow:TextFlow, commonRoot:FlowGroupElement, mementoList:MementoList):void 120 { 121 if ((commonRoot is ListItemElement) && (commonRoot as ListItemElement).normalizeNeedsInitialParagraph()) 122 { 123 var paragraph:ParagraphElement = new ParagraphElement(); 124 paragraph.replaceChildren(0, 0, new SpanElement()); 125 mementoList.push(ModelEdit.addElement(textFlow, paragraph, commonRoot, 0)); 126 } 127 for (var index:int = 0; index < commonRoot.numChildren; ++index) 128 { 129 var child:FlowGroupElement = commonRoot.getChildAt(index) as FlowGroupElement; 130 if (child) 131 checkNormalize(textFlow, child, mementoList); 132 } 133 } 134 135 public static function saveCurrentState(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int):IMemento 136 { 137 return new TextRangeMemento(textFlow,absoluteStart,absoluteEnd); 138 } 139 } 140} 141 142import flash.utils.getQualifiedClassName; 143 144import flashx.textLayout.debug.Debugging; 145import flashx.textLayout.debug.assert; 146import flashx.textLayout.edit.ElementMark; 147import flashx.textLayout.edit.IMemento; 148import flashx.textLayout.edit.ModelEdit; 149import flashx.textLayout.elements.*; 150import flashx.textLayout.elements.FlowElement; 151import flashx.textLayout.elements.FlowGroupElement; 152import flashx.textLayout.elements.ParagraphElement; 153import flashx.textLayout.elements.TextFlow; 154import flashx.textLayout.tlf_internal; 155 156use namespace tlf_internal; 157 158 159 160class BaseMemento 161{ 162 protected var _textFlow:TextFlow; 163 164 public function BaseMemento(textFlow:TextFlow) 165 { _textFlow = textFlow; } 166 167 CONFIG::debug public function debugCheckTextFlow(s:String):void 168 { 169 trace(s); 170 var saveDebugCheckTextFlow:Boolean = Debugging.debugCheckTextFlow; 171 var saveVerbose:Boolean = Debugging.verbose; 172 Debugging.debugCheckTextFlow = true; 173 Debugging.verbose = true; 174 try 175 { 176 _textFlow.debugCheckTextFlow(false); 177 } 178 finally 179 { 180 Debugging.debugCheckTextFlow = saveDebugCheckTextFlow; 181 Debugging.verbose = saveVerbose; 182 } 183 } 184 185} 186 187import flashx.textLayout.conversion.ConversionType; 188import flashx.textLayout.conversion.TextConverter; 189 190// Use this for operations that undo using copy & paste 191class DeleteTextMemento extends BaseMemento implements IMemento 192{ 193 private var _commonRootMark:ElementMark; 194 private var _startChildIndex:int; 195 private var _endChildIndex:int; 196 private var _originalChildren:Array; 197 private var _absoluteStart:int; 198 199 protected var scrapChildren:Array; 200 protected var replaceCount:int; 201 202 public function DeleteTextMemento(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int) 203 { 204 super(textFlow); 205 206 // Find the lowest possible common root that contains both start and end, and is at least one paragraph 207 // We move the common root to the paragraph level so that we don't have to worry on undo about spans that have merged. 208 var startLeaf:FlowLeafElement = textFlow.findLeaf(absoluteStart); 209 //var commonRoot:FlowGroupElement = startLeaf.parent; 210 var commonRoot:FlowGroupElement = startLeaf.getParagraph().parent; 211 while (commonRoot && (commonRoot.getAbsoluteStart() + commonRoot.textLength < absoluteEnd || (commonRoot.getAbsoluteStart() == absoluteStart && commonRoot.getAbsoluteStart() + commonRoot.textLength == absoluteEnd))) 212 commonRoot = commonRoot.parent; 213 214 // Find even element boundaries smallest amount that contains the entire range 215 if (commonRoot) 216 { 217 var rootStart:int = commonRoot.getAbsoluteStart(); 218 _startChildIndex = commonRoot.findChildIndexAtPosition(absoluteStart - rootStart); 219 _endChildIndex = commonRoot.findChildIndexAtPosition(absoluteEnd - rootStart - 1); 220 if (_endChildIndex < 0) 221 _endChildIndex = commonRoot.numChildren - 1; 222 223 var startChild:FlowElement = commonRoot.getChildAt(_startChildIndex); 224 var absoluteStartAdjusted:int = startChild.getAbsoluteStart(); 225 var endChild:FlowElement = commonRoot.getChildAt(_endChildIndex); 226 var absoluteEndAdjusted:int = endChild.getAbsoluteStart() + endChild.textLength; 227 228 // Set how many elements we expect to replace on undo. Although the delete does a merge at the end if a CR was deleted, the merge 229 // (if there was one) will have been undone before DeleteTextMemento.undo() is called. 230 // Basic rule is that if there was content before the delete range in the common root, then there will be an element after the delete 231 // with that content that should get replaced. Likewise for if there's content after the delete range in the common root. The exception 232 // to the rule is if the common root is a grandparent of the range to be deleted, then there will be just one element getting replaced. 233 replaceCount = 0; // how many original (post-do) elements we're replacing 234 if (_startChildIndex == _endChildIndex) 235 { 236 if (absoluteStartAdjusted < absoluteStart || absoluteEndAdjusted > absoluteEnd) // if we're deleting the entire element, nothing to replace 237 replaceCount = 1; 238 } 239 else 240 { 241 if (absoluteStartAdjusted < absoluteStart) 242 replaceCount++; 243 if (absoluteEndAdjusted > absoluteEnd) 244 replaceCount++; 245 } 246 247 var scrapRoot:FlowGroupElement = commonRoot.deepCopy(absoluteStartAdjusted - rootStart, absoluteEndAdjusted - rootStart) as FlowGroupElement; 248 scrapChildren = scrapRoot.mxmlChildren; 249 } 250 251 _commonRootMark = new ElementMark(commonRoot, 0); 252 _absoluteStart = absoluteStart; 253 } 254 255 public function undo():* 256 { 257 var root:FlowGroupElement = commonRoot; 258 259 // Save off the original children for later redo 260 _originalChildren = []; 261 for (var childIndex:int = _startChildIndex; childIndex < _startChildIndex + replaceCount; ++childIndex) 262 _originalChildren.push(root.getChildAt(childIndex)); 263 264 // Make copies of the scrapChildren, and add the copies to the main flow 265 var addToFlow:Array = []; 266 for each (var element:FlowElement in scrapChildren) 267 addToFlow.push(element.deepCopy()); 268 root.replaceChildren(_startChildIndex, _startChildIndex + replaceCount, addToFlow); 269 } 270 271 public function redo():* 272 { 273 commonRoot.replaceChildren(_startChildIndex, _startChildIndex + scrapChildren.length, _originalChildren); 274 } 275 276 public function get commonRoot():FlowGroupElement 277 { 278 return _commonRootMark.findElement(_textFlow) as FlowGroupElement; 279 } 280 281} 282 283// Use this for operations that undo using copy & paste 284class TextRangeMemento extends DeleteTextMemento implements IMemento 285{ 286 public function TextRangeMemento(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int) 287 { 288 super(textFlow, absoluteStart, absoluteEnd); 289 replaceCount = scrapChildren.length; 290 } 291} 292 293 294 295 296class InternalSplitFGEMemento extends BaseMemento implements IMemento 297{ 298 private var _target:ElementMark; 299 private var _undoTarget:ElementMark; 300 private var _newSibling:FlowGroupElement; 301 private var _skipUndo:Boolean; 302 303 public function InternalSplitFGEMemento(textFlow:TextFlow, target:ElementMark, undoTarget:ElementMark, newSibling:FlowGroupElement) 304 { 305 super(textFlow); 306 _target = target; 307 _undoTarget = undoTarget; 308 _newSibling = newSibling; 309 _skipUndo = (newSibling is SubParagraphGroupElementBase); 310 } 311 312 public function get newSibling():FlowGroupElement 313 { 314 return _newSibling; 315 } 316 317 static public function perform(textFlow:TextFlow, elemToSplit:FlowElement, relativePosition:int, createMemento:Boolean):* 318 { 319 var target:ElementMark = new ElementMark(elemToSplit,relativePosition); 320 var newSibling:FlowGroupElement = performInternal(textFlow, target); 321 322 if (createMemento) 323 { 324 var undoTarget:ElementMark = new ElementMark(newSibling,0); 325 return new InternalSplitFGEMemento(textFlow, target, undoTarget, newSibling); 326 } 327 else 328 return newSibling; 329 } 330 331 static public function performInternal(textFlow:TextFlow, target:ElementMark):* 332 { 333 var targetElement:FlowGroupElement = target.findElement(textFlow) as FlowGroupElement; 334 var childIdx:int = target.elemStart == targetElement.textLength ? targetElement.numChildren-1 : targetElement.findChildIndexAtPosition(target.elemStart); 335 var child:FlowElement = targetElement.getChildAt(childIdx); 336 var newSibling:FlowGroupElement; 337 if (child.parentRelativeStart == target.elemStart) 338 newSibling = targetElement.splitAtIndex(childIdx); 339 else 340 newSibling = targetElement.splitAtPosition(target.elemStart) as FlowGroupElement; 341 342 if (targetElement is ParagraphElement) 343 { 344 if (targetElement.textLength <= 1) 345 { 346 targetElement.normalizeRange(0,targetElement.textLength); 347 targetElement.getLastLeaf().quickCloneTextLayoutFormat(newSibling.getFirstLeaf()); 348 } 349 else if (newSibling.textLength <= 1) 350 { 351 newSibling.normalizeRange(0,newSibling.textLength); 352 newSibling.getFirstLeaf().quickCloneTextLayoutFormat(targetElement.getLastLeaf()); 353 } 354 } 355 // debugCheckTextFlow("After InternalSplitFGEMemento.perform"); 356 357 return newSibling; 358 359 } 360 361 public function undo():* 362 { 363 // debugCheckTextFlow("Before InternalSplitFGEMemento.undo"); 364 if (_skipUndo) 365 return; 366 367 var target:FlowGroupElement = _undoTarget.findElement(_textFlow) as FlowGroupElement; 368 // move all children of target into previoussibling and delete target 369 CONFIG::debug { assert(target != null,"Missing FlowGroupElement from undoTarget"); } 370 var prevSibling:FlowGroupElement = target.getPreviousSibling() as FlowGroupElement; 371 CONFIG::debug { assert(getQualifiedClassName(target) == getQualifiedClassName(prevSibling),"Mismatched class in InternalSplitFGEMemento"); } 372 373 target.parent.removeChild(target); 374 var lastLeaf:FlowLeafElement = prevSibling.getLastLeaf(); 375 prevSibling.replaceChildren(prevSibling.numChildren,prevSibling.numChildren,target.mxmlChildren); 376 377 // paragraphs only - watch out for trailing empty spans that need to be removed 378 if (prevSibling is ParagraphElement && lastLeaf.textLength == 0) 379 prevSibling.removeChild(lastLeaf); 380 381 // debugCheckTextFlow("After InternalSplitFGEMemento.undo"); 382 } 383 384 public function redo():* 385 { return performInternal(_textFlow, _target ); } 386} 387 388class SplitMemento extends BaseMemento implements IMemento 389{ 390 private var _mementoList:Array; 391 private var _target:ElementMark; 392 393 public function SplitMemento(textFlow:TextFlow, target:ElementMark, mementoList:Array) 394 { 395 super(textFlow); 396 _target = target; 397 _mementoList = mementoList; 398 } 399 400 static public function perform(textFlow:TextFlow, elemToSplit:FlowGroupElement, relativePosition:int, createMemento:Boolean):* 401 { 402 var target:ElementMark = new ElementMark(elemToSplit,relativePosition); 403 var mementoList:Array = []; 404 405 var newChild:FlowGroupElement = performInternal(textFlow, target, createMemento ? mementoList : null); 406 407 if (createMemento) 408 return new SplitMemento(textFlow, target, mementoList); 409 410 return newChild; 411 } 412 413 static private function testValidLeadingParagraph(elem:FlowGroupElement):Boolean 414 { 415 // listitems have to have the very first item as a paragraph 416 if (elem is ListItemElement) 417 return !(elem as ListItemElement).normalizeNeedsInitialParagraph(); 418 419 while (elem && !(elem is ParagraphElement)) 420 elem = elem.getChildAt(0) as FlowGroupElement; 421 return elem is ParagraphElement; 422 } 423 424 static public function performInternal(textFlow:TextFlow, target:ElementMark, mementoList:Array):FlowGroupElement 425 { 426 // split all the way up the chain and then do a move 427 var targetElement:FlowGroupElement = target.findElement(textFlow) as FlowGroupElement; 428 var child:FlowGroupElement = (target.elemStart == targetElement.textLength ? targetElement.getLastLeaf() : targetElement.findLeaf(target.elemStart)).parent; 429 var newChild:FlowGroupElement; 430 431 var splitStart:int = target.elemStart; 432 var memento:IMemento; 433 434 for (;;) 435 { 436 var splitPos:int = splitStart - (child.getAbsoluteStart()-targetElement.getAbsoluteStart()); 437 //if (splitPos != 0) 438 { 439 var splitMemento:InternalSplitFGEMemento = InternalSplitFGEMemento.perform(textFlow,child,splitPos, true); 440 if (mementoList) 441 mementoList.push(splitMemento); 442 newChild = splitMemento.newSibling; 443 444 if (child is ParagraphElement && !(target.elemStart == targetElement.textLength)) 445 { 446 // count the terminator 447 splitStart++; 448 } 449 else if (child is ContainerFormattedElement) 450 { 451 // if its a ContainerFormattedElement there needs to be a paragraph at position zero on each side 452 if (!testValidLeadingParagraph(child)) 453 { 454 memento = ModelEdit.addElement(textFlow,new ParagraphElement,child,0); 455 if (mementoList) 456 mementoList.push(memento); 457 splitStart++; 458 } 459 if (!testValidLeadingParagraph(newChild)) 460 { 461 memento = ModelEdit.addElement(textFlow,new ParagraphElement,newChild,0); 462 if (mementoList) 463 mementoList.push(memento); 464 } 465 } 466 } 467 if (child == targetElement) 468 break; 469 child = child.parent; 470 } 471 472 return newChild; 473 } 474 475 public function undo():* 476 { 477 _mementoList.reverse(); 478 for each (var memento:IMemento in _mementoList) 479 memento.undo(); 480 _mementoList.reverse(); 481 } 482 483 public function redo():* 484 { return performInternal(_textFlow, _target, null); } 485} 486 487import flashx.textLayout.edit.TextFlowEdit; 488 489class JoinMemento extends BaseMemento implements IMemento 490{ 491 private var _element1:ElementMark; 492 private var _element2:ElementMark; 493 private var _joinPosition:int; 494 private var _removeParentChain:IMemento; 495 496 public function JoinMemento(textFlow:TextFlow, element1:ElementMark, element2:ElementMark, joinPosition:int, removeParentChain:IMemento) 497 { 498 super(textFlow); 499 _element1 = element1; 500 _element2 = element2; 501 _joinPosition = joinPosition; 502 _removeParentChain = removeParentChain; 503 } 504 505 static public function perform(textFlow:TextFlow, element1:FlowGroupElement, element2:FlowGroupElement, createMemento:Boolean):* 506 { 507 var joinPosition:int = element1.textLength - 1; 508 509 var element1Mark:ElementMark = new ElementMark(element1,0); 510 var element2Mark:ElementMark = new ElementMark(element2,0); 511 performInternal(textFlow, element1Mark, element2Mark); 512 var removeParentChain:IMemento = TextFlowEdit.removeEmptyParentChain(element2); 513 514 if (createMemento) 515 { 516 return new JoinMemento(textFlow, element1Mark, element2Mark, joinPosition, removeParentChain); 517 } 518 519 return null; 520 } 521 522 static public function performInternal(textFlow:TextFlow, element1Mark:ElementMark, element2Mark:ElementMark):void 523 { 524 var element1:FlowGroupElement = element1Mark.findElement(textFlow) as FlowGroupElement; 525 var element2:FlowGroupElement = element2Mark.findElement(textFlow) as FlowGroupElement; 526 527 moveChildren(element2, element1); 528 } 529 530 static private function moveChildren(elementSource:FlowGroupElement, elementDestination:FlowGroupElement): void 531 { 532 // move children of elementSource to end of elementDestination 533 var childrenToMove:Array = elementSource.mxmlChildren; 534 elementSource.replaceChildren(0, elementSource.numChildren); 535 elementDestination.replaceChildren(elementDestination.numChildren, elementDestination.numChildren, childrenToMove); 536 } 537 538 public function undo():* 539 { 540 _removeParentChain.undo(); 541 542 var element1:FlowGroupElement = _element1.findElement(_textFlow) as FlowGroupElement; 543 var element2:FlowGroupElement = _element2.findElement(_textFlow) as FlowGroupElement; 544 var tmpElement:FlowGroupElement = element1.splitAtPosition(_joinPosition) as FlowGroupElement; 545 // everything after the split moves to element2 546 moveChildren(tmpElement, element2); 547 tmpElement.parent.removeChild(tmpElement); 548 } 549 550 public function redo():* 551 { 552 performInternal(_textFlow, _element1, _element2); 553 _removeParentChain.redo(); 554 } 555} 556 557class AddElementMemento extends BaseMemento implements IMemento 558{ 559 private var _target:ElementMark; 560 private var _targetIndex:int; 561 private var _elemToAdd:FlowElement; 562 563 public function AddElementMemento(textFlow:TextFlow, elemToAdd:FlowElement, target:ElementMark, index:int) 564 { 565 super(textFlow); 566 _target = target; 567 _targetIndex = index; 568 _elemToAdd = elemToAdd; 569 } 570 571 static public function perform(textFlow:TextFlow, elemToAdd:FlowElement, parent:FlowGroupElement, index:int, createMemento:Boolean):* 572 { 573 var elem:FlowElement = elemToAdd; 574 if (createMemento) 575 elemToAdd = elem.deepCopy(); // for redo 576 577 var target:ElementMark = new ElementMark(parent,0); 578 579 var targetElement:FlowGroupElement = target.findElement(textFlow) as FlowGroupElement; 580 targetElement.addChildAt(index,elem); 581 if (createMemento) 582 return new AddElementMemento(textFlow, elemToAdd, target, index); 583 return null; 584 } 585 586 public function undo():* 587 { 588 var target:FlowGroupElement = _target.findElement(_textFlow) as FlowGroupElement; 589 target.removeChildAt(_targetIndex); 590 } 591 592 public function redo():* 593 { 594 var parent:FlowGroupElement = _target.findElement(_textFlow) as FlowGroupElement; 595 return perform(_textFlow, _elemToAdd, parent, _targetIndex, false); 596 } 597} 598 599class MoveElementMemento extends BaseMemento implements IMemento 600{ 601 private var _target:ElementMark; 602 private var _targetIndex:int; 603 604 private var _elemBeforeMove:ElementMark; 605 private var _elemAfterMove:ElementMark; 606 private var _source:ElementMark; // original parent 607 private var _sourceIndex:int; // original index 608 609 public function MoveElementMemento(textFlow:TextFlow, elemBeforeMove:ElementMark, elemAfterMove:ElementMark, target:ElementMark, targetIndex:int, source:ElementMark, sourceIndex:int) 610 { 611 super(textFlow); 612 _elemBeforeMove = elemBeforeMove; 613 _elemAfterMove = elemAfterMove; 614 _target = target; 615 _targetIndex = targetIndex; 616 _source = source; 617 _sourceIndex = sourceIndex; 618 } 619 620 static public function perform(textFlow:TextFlow, elem:FlowElement, newParent:FlowGroupElement, newIndex:int, createMemento:Boolean):* 621 { 622 var target:ElementMark = new ElementMark(newParent,0); 623 var elemBeforeMove:ElementMark = new ElementMark(elem, 0); 624 625 var source:FlowGroupElement = elem.parent; 626 var sourceIndex:int = source.getChildIndex(elem); 627 var sourceMark:ElementMark = new ElementMark(source, 0); 628 629 newParent.addChildAt(newIndex,elem); 630 if (createMemento) 631 return new MoveElementMemento(textFlow, elemBeforeMove, new ElementMark(elem, 0), target, newIndex, sourceMark, sourceIndex); 632 return elem; 633 } 634 635 public function undo():* 636 { 637 var elem:FlowElement = _elemAfterMove.findElement(_textFlow); 638 elem.parent.removeChildAt(elem.parent.getChildIndex(elem)); 639 var source:FlowGroupElement = _source.findElement(_textFlow) as FlowGroupElement; 640 source.addChildAt(_sourceIndex,elem); 641 } 642 643 public function redo():* 644 { 645 var target:FlowGroupElement = _target.findElement(_textFlow) as FlowGroupElement; 646 var elem:FlowElement = _elemBeforeMove.findElement(_textFlow) as FlowElement; 647 return perform(_textFlow, elem, target, _targetIndex, false); 648 } 649} 650 651class RemoveElementsMemento extends BaseMemento implements IMemento 652{ 653 private var _elements:Array; 654 655 private var _elemParent:ElementMark; 656 private var _startIndex:int; 657 private var _numElements:int; 658 659 /** 660 * RemoveElements from the TextFlow, 661 * @param parent parent of elements to rmeove 662 * @param startIndex index of first child to remove 663 * @param numElements number of elements to remove 664 */ 665 public function RemoveElementsMemento(textFlow:TextFlow, elementParent:ElementMark, startIndex:int, numElements:int, elements:Array) 666 { 667 super(textFlow); 668 _elemParent = elementParent; 669 _startIndex = startIndex; 670 _numElements = numElements; 671 _elements = elements; 672 } 673 674 static public function perform(textFlow:TextFlow, parent:FlowGroupElement, startIndex:int, numElements:int, createMemento:Boolean):* 675 { 676 var elemParent:ElementMark = new ElementMark(parent,0); 677 678 // hold on to elements for undo 679 var elements:Array = parent.mxmlChildren.slice(startIndex, startIndex + numElements); 680 // now remove them 681 parent.replaceChildren(startIndex, startIndex + numElements); 682 if (createMemento) 683 return new RemoveElementsMemento(textFlow, elemParent, startIndex, numElements, elements); 684 return elements; 685 } 686 687 public function undo():* 688 { 689 var parent:FlowGroupElement = _elemParent.findElement(_textFlow) as FlowGroupElement; 690 parent.replaceChildren(_startIndex,_startIndex,_elements); 691 _elements = null; // release the saved elements array 692 return parent.mxmlChildren.slice(_startIndex,_startIndex+_numElements); 693 } 694 695 public function redo():* 696 { 697 var parent:FlowGroupElement = _elemParent.findElement(_textFlow) as FlowGroupElement; 698 _elements = perform(_textFlow, parent, _startIndex, _numElements, false); 699 } 700} 701