| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| package flashx.textLayout.edit |
| { |
| import flashx.textLayout.debug.assert; |
| import flashx.textLayout.elements.FlowElement; |
| import flashx.textLayout.elements.FlowGroupElement; |
| import flashx.textLayout.elements.FlowLeafElement; |
| import flashx.textLayout.elements.ListItemElement; |
| import flashx.textLayout.elements.ParagraphElement; |
| import flashx.textLayout.elements.SpanElement; |
| import flashx.textLayout.elements.TextFlow; |
| import flashx.textLayout.events.ModelChange; |
| import flashx.textLayout.tlf_internal; |
| |
| use namespace tlf_internal; |
| |
| [ExcludeClass] |
| /** |
| * The ModelEdit class contains static functions for performing speficic suboperations. Each suboperation returns a "memento" for undo/redo. |
| * |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| public class ModelEdit |
| { |
| public static function splitElement(textFlow:TextFlow, elemToSplit:FlowGroupElement, relativePosition:int):IMemento |
| { |
| return SplitMemento.perform(textFlow,elemToSplit,relativePosition,true); |
| } |
| |
| public static function joinElement(textFlow:TextFlow, element1:FlowGroupElement, element2:FlowGroupElement):IMemento |
| { |
| return JoinMemento.perform(textFlow, element1, element2, true); |
| } |
| |
| public static function addElement(textFlow:TextFlow, elemToAdd:FlowElement, parent:FlowGroupElement, index:int):IMemento |
| { |
| CONFIG::debug { assert(elemToAdd.parent == null,"Use moveElement"); } |
| return AddElementMemento.perform(textFlow,elemToAdd,parent,index,true); |
| } |
| |
| public static function moveElement(textFlow:TextFlow, elemToMove:FlowElement, parent:FlowGroupElement, index:int):IMemento |
| { |
| CONFIG::debug { assert(elemToMove.parent != null,"Use addElement"); } |
| return MoveElementMemento.perform(textFlow,elemToMove,parent,index,true); |
| } |
| |
| public static function removeElements(textFlow:TextFlow, elemtToRemoveParent:FlowGroupElement,startIndex:int, numElements:int):IMemento |
| { |
| return RemoveElementsMemento.perform(textFlow,elemtToRemoveParent,startIndex,numElements,true); |
| } |
| |
| public static function deleteText(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int, createMemento:Boolean):IMemento |
| { |
| var memento:MementoList; |
| var mergePara:ParagraphElement; |
| |
| // 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 |
| // so that if there is a list or a div at the end, it will be entirely removed. |
| if (absoluteEnd == textFlow.textLength - 1) |
| { |
| var lastElement:FlowElement = textFlow.getChildAt(textFlow.numChildren - 1); |
| if (absoluteStart <= lastElement.getAbsoluteStart()) |
| absoluteEnd = textFlow.textLength; |
| } |
| |
| // Special case for when the last paragraph in the flow is deleted. We clone the last paragraph |
| // before letting the delete get processed. This lets whatever hierarchy is associated with the |
| // old last paragraph die a natural death, but doesn't leave the flow with no terminator. |
| var newLastParagraph:ParagraphElement; |
| if (absoluteEnd >= textFlow.textLength) |
| { |
| var lastSpan:FlowLeafElement = textFlow.getLastLeaf(); |
| var lastParagraph:ParagraphElement = lastSpan.getParagraph(); |
| newLastParagraph = new ParagraphElement(); |
| var newLastSpan:SpanElement = new SpanElement(); |
| newLastParagraph.replaceChildren(0, 0, newLastSpan); |
| newLastParagraph.format = lastParagraph.format; |
| newLastSpan.format = lastSpan.format; |
| absoluteEnd = textFlow.textLength; |
| } |
| |
| if (createMemento) |
| { |
| memento = new MementoList(textFlow); |
| if (newLastParagraph) |
| memento.push(addElement(textFlow, newLastParagraph, textFlow, textFlow.numChildren)); |
| var deleteTextMemento:DeleteTextMemento = new DeleteTextMemento(textFlow, absoluteStart, absoluteEnd); |
| memento.push(deleteTextMemento); |
| |
| mergePara = TextFlowEdit.deleteRange(textFlow, absoluteStart, absoluteEnd); |
| memento.push(TextFlowEdit.joinNextParagraph(mergePara, false)); |
| checkNormalize(textFlow, deleteTextMemento.commonRoot, memento); |
| } |
| else |
| { |
| if (newLastParagraph) |
| textFlow.replaceChildren(textFlow.numChildren, textFlow.numChildren, newLastParagraph); |
| mergePara = TextFlowEdit.deleteRange(textFlow, absoluteStart, absoluteEnd); |
| TextFlowEdit.joinNextParagraph(mergePara, false); |
| } |
| |
| if (textFlow.interactionManager) |
| textFlow.interactionManager.notifyInsertOrDelete(absoluteStart, -(absoluteEnd - absoluteStart)); |
| |
| return memento; |
| } |
| |
| private static function checkNormalize(textFlow:TextFlow, commonRoot:FlowGroupElement, mementoList:MementoList):void |
| { |
| if ((commonRoot is ListItemElement) && (commonRoot as ListItemElement).normalizeNeedsInitialParagraph()) |
| { |
| var paragraph:ParagraphElement = new ParagraphElement(); |
| paragraph.replaceChildren(0, 0, new SpanElement()); |
| mementoList.push(ModelEdit.addElement(textFlow, paragraph, commonRoot, 0)); |
| } |
| for (var index:int = 0; index < commonRoot.numChildren; ++index) |
| { |
| var child:FlowGroupElement = commonRoot.getChildAt(index) as FlowGroupElement; |
| if (child) |
| checkNormalize(textFlow, child, mementoList); |
| } |
| } |
| |
| public static function saveCurrentState(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int):IMemento |
| { |
| return new TextRangeMemento(textFlow,absoluteStart,absoluteEnd); |
| } |
| } |
| } |
| |
| import flash.utils.getQualifiedClassName; |
| |
| import flashx.textLayout.debug.Debugging; |
| import flashx.textLayout.debug.assert; |
| import flashx.textLayout.edit.ElementMark; |
| import flashx.textLayout.edit.IMemento; |
| import flashx.textLayout.edit.ModelEdit; |
| import flashx.textLayout.elements.*; |
| import flashx.textLayout.elements.FlowElement; |
| import flashx.textLayout.elements.FlowGroupElement; |
| import flashx.textLayout.elements.ParagraphElement; |
| import flashx.textLayout.elements.TextFlow; |
| import flashx.textLayout.tlf_internal; |
| |
| use namespace tlf_internal; |
| |
| |
| |
| class BaseMemento |
| { |
| protected var _textFlow:TextFlow; |
| |
| public function BaseMemento(textFlow:TextFlow) |
| { _textFlow = textFlow; } |
| |
| CONFIG::debug public function debugCheckTextFlow(s:String):void |
| { |
| trace(s); |
| var saveDebugCheckTextFlow:Boolean = Debugging.debugCheckTextFlow; |
| var saveVerbose:Boolean = Debugging.verbose; |
| Debugging.debugCheckTextFlow = true; |
| Debugging.verbose = true; |
| try |
| { |
| _textFlow.debugCheckTextFlow(false); |
| } |
| finally |
| { |
| Debugging.debugCheckTextFlow = saveDebugCheckTextFlow; |
| Debugging.verbose = saveVerbose; |
| } |
| } |
| |
| } |
| |
| import flashx.textLayout.conversion.ConversionType; |
| import flashx.textLayout.conversion.TextConverter; |
| |
| // Use this for operations that undo using copy & paste |
| class DeleteTextMemento extends BaseMemento implements IMemento |
| { |
| private var _commonRootMark:ElementMark; |
| private var _startChildIndex:int; |
| private var _endChildIndex:int; |
| private var _originalChildren:Array; |
| private var _absoluteStart:int; |
| |
| protected var scrapChildren:Array; |
| protected var replaceCount:int; |
| |
| public function DeleteTextMemento(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int) |
| { |
| super(textFlow); |
| |
| // Find the lowest possible common root that contains both start and end, and is at least one paragraph |
| // We move the common root to the paragraph level so that we don't have to worry on undo about spans that have merged. |
| var startLeaf:FlowLeafElement = textFlow.findLeaf(absoluteStart); |
| //var commonRoot:FlowGroupElement = startLeaf.parent; |
| var commonRoot:FlowGroupElement = startLeaf.getParagraph().parent; |
| while (commonRoot && (commonRoot.getAbsoluteStart() + commonRoot.textLength < absoluteEnd || (commonRoot.getAbsoluteStart() == absoluteStart && commonRoot.getAbsoluteStart() + commonRoot.textLength == absoluteEnd))) |
| commonRoot = commonRoot.parent; |
| |
| // Find even element boundaries smallest amount that contains the entire range |
| if (commonRoot) |
| { |
| var rootStart:int = commonRoot.getAbsoluteStart(); |
| _startChildIndex = commonRoot.findChildIndexAtPosition(absoluteStart - rootStart); |
| _endChildIndex = commonRoot.findChildIndexAtPosition(absoluteEnd - rootStart - 1); |
| if (_endChildIndex < 0) |
| _endChildIndex = commonRoot.numChildren - 1; |
| |
| var startChild:FlowElement = commonRoot.getChildAt(_startChildIndex); |
| var absoluteStartAdjusted:int = startChild.getAbsoluteStart(); |
| var endChild:FlowElement = commonRoot.getChildAt(_endChildIndex); |
| var absoluteEndAdjusted:int = endChild.getAbsoluteStart() + endChild.textLength; |
| |
| // 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 |
| // (if there was one) will have been undone before DeleteTextMemento.undo() is called. |
| // 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 |
| // with that content that should get replaced. Likewise for if there's content after the delete range in the common root. The exception |
| // 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. |
| replaceCount = 0; // how many original (post-do) elements we're replacing |
| if (_startChildIndex == _endChildIndex) |
| { |
| if (absoluteStartAdjusted < absoluteStart || absoluteEndAdjusted > absoluteEnd) // if we're deleting the entire element, nothing to replace |
| replaceCount = 1; |
| } |
| else |
| { |
| if (absoluteStartAdjusted < absoluteStart) |
| replaceCount++; |
| if (absoluteEndAdjusted > absoluteEnd) |
| replaceCount++; |
| } |
| |
| var scrapRoot:FlowGroupElement = commonRoot.deepCopy(absoluteStartAdjusted - rootStart, absoluteEndAdjusted - rootStart) as FlowGroupElement; |
| scrapChildren = scrapRoot.mxmlChildren; |
| } |
| |
| _commonRootMark = new ElementMark(commonRoot, 0); |
| _absoluteStart = absoluteStart; |
| } |
| |
| public function undo():* |
| { |
| var root:FlowGroupElement = commonRoot; |
| |
| // Save off the original children for later redo |
| _originalChildren = []; |
| for (var childIndex:int = _startChildIndex; childIndex < _startChildIndex + replaceCount; ++childIndex) |
| _originalChildren.push(root.getChildAt(childIndex)); |
| |
| // Make copies of the scrapChildren, and add the copies to the main flow |
| var addToFlow:Array = []; |
| for each (var element:FlowElement in scrapChildren) |
| addToFlow.push(element.deepCopy()); |
| root.replaceChildren(_startChildIndex, _startChildIndex + replaceCount, addToFlow); |
| } |
| |
| public function redo():* |
| { |
| commonRoot.replaceChildren(_startChildIndex, _startChildIndex + scrapChildren.length, _originalChildren); |
| } |
| |
| public function get commonRoot():FlowGroupElement |
| { |
| return _commonRootMark.findElement(_textFlow) as FlowGroupElement; |
| } |
| |
| } |
| |
| // Use this for operations that undo using copy & paste |
| class TextRangeMemento extends DeleteTextMemento implements IMemento |
| { |
| public function TextRangeMemento(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int) |
| { |
| super(textFlow, absoluteStart, absoluteEnd); |
| replaceCount = scrapChildren.length; |
| } |
| } |
| |
| |
| |
| |
| class InternalSplitFGEMemento extends BaseMemento implements IMemento |
| { |
| private var _target:ElementMark; |
| private var _undoTarget:ElementMark; |
| private var _newSibling:FlowGroupElement; |
| private var _skipUndo:Boolean; |
| |
| public function InternalSplitFGEMemento(textFlow:TextFlow, target:ElementMark, undoTarget:ElementMark, newSibling:FlowGroupElement) |
| { |
| super(textFlow); |
| _target = target; |
| _undoTarget = undoTarget; |
| _newSibling = newSibling; |
| _skipUndo = (newSibling is SubParagraphGroupElementBase); |
| } |
| |
| public function get newSibling():FlowGroupElement |
| { |
| return _newSibling; |
| } |
| |
| static public function perform(textFlow:TextFlow, elemToSplit:FlowElement, relativePosition:int, createMemento:Boolean):* |
| { |
| var target:ElementMark = new ElementMark(elemToSplit,relativePosition); |
| var newSibling:FlowGroupElement = performInternal(textFlow, target); |
| |
| if (createMemento) |
| { |
| var undoTarget:ElementMark = new ElementMark(newSibling,0); |
| return new InternalSplitFGEMemento(textFlow, target, undoTarget, newSibling); |
| } |
| else |
| return newSibling; |
| } |
| |
| static public function performInternal(textFlow:TextFlow, target:ElementMark):* |
| { |
| var targetElement:FlowGroupElement = target.findElement(textFlow) as FlowGroupElement; |
| var childIdx:int = target.elemStart == targetElement.textLength ? targetElement.numChildren-1 : targetElement.findChildIndexAtPosition(target.elemStart); |
| var child:FlowElement = targetElement.getChildAt(childIdx); |
| var newSibling:FlowGroupElement; |
| if (child.parentRelativeStart == target.elemStart) |
| newSibling = targetElement.splitAtIndex(childIdx); |
| else |
| newSibling = targetElement.splitAtPosition(target.elemStart) as FlowGroupElement; |
| |
| if (targetElement is ParagraphElement) |
| { |
| if (targetElement.textLength <= 1) |
| { |
| targetElement.normalizeRange(0,targetElement.textLength); |
| targetElement.getLastLeaf().quickCloneTextLayoutFormat(newSibling.getFirstLeaf()); |
| } |
| else if (newSibling.textLength <= 1) |
| { |
| newSibling.normalizeRange(0,newSibling.textLength); |
| newSibling.getFirstLeaf().quickCloneTextLayoutFormat(targetElement.getLastLeaf()); |
| } |
| } |
| // debugCheckTextFlow("After InternalSplitFGEMemento.perform"); |
| |
| return newSibling; |
| |
| } |
| |
| public function undo():* |
| { |
| // debugCheckTextFlow("Before InternalSplitFGEMemento.undo"); |
| if (_skipUndo) |
| return; |
| |
| var target:FlowGroupElement = _undoTarget.findElement(_textFlow) as FlowGroupElement; |
| // move all children of target into previoussibling and delete target |
| CONFIG::debug { assert(target != null,"Missing FlowGroupElement from undoTarget"); } |
| var prevSibling:FlowGroupElement = target.getPreviousSibling() as FlowGroupElement; |
| CONFIG::debug { assert(getQualifiedClassName(target) == getQualifiedClassName(prevSibling),"Mismatched class in InternalSplitFGEMemento"); } |
| |
| target.parent.removeChild(target); |
| var lastLeaf:FlowLeafElement = prevSibling.getLastLeaf(); |
| prevSibling.replaceChildren(prevSibling.numChildren,prevSibling.numChildren,target.mxmlChildren); |
| |
| // paragraphs only - watch out for trailing empty spans that need to be removed |
| if (prevSibling is ParagraphElement && lastLeaf.textLength == 0) |
| prevSibling.removeChild(lastLeaf); |
| |
| // debugCheckTextFlow("After InternalSplitFGEMemento.undo"); |
| } |
| |
| public function redo():* |
| { return performInternal(_textFlow, _target ); } |
| } |
| |
| class SplitMemento extends BaseMemento implements IMemento |
| { |
| private var _mementoList:Array; |
| private var _target:ElementMark; |
| |
| public function SplitMemento(textFlow:TextFlow, target:ElementMark, mementoList:Array) |
| { |
| super(textFlow); |
| _target = target; |
| _mementoList = mementoList; |
| } |
| |
| static public function perform(textFlow:TextFlow, elemToSplit:FlowGroupElement, relativePosition:int, createMemento:Boolean):* |
| { |
| var target:ElementMark = new ElementMark(elemToSplit,relativePosition); |
| var mementoList:Array = []; |
| |
| var newChild:FlowGroupElement = performInternal(textFlow, target, createMemento ? mementoList : null); |
| |
| if (createMemento) |
| return new SplitMemento(textFlow, target, mementoList); |
| |
| return newChild; |
| } |
| |
| static private function testValidLeadingParagraph(elem:FlowGroupElement):Boolean |
| { |
| // listitems have to have the very first item as a paragraph |
| if (elem is ListItemElement) |
| return !(elem as ListItemElement).normalizeNeedsInitialParagraph(); |
| |
| while (elem && !(elem is ParagraphElement)) |
| elem = elem.getChildAt(0) as FlowGroupElement; |
| return elem is ParagraphElement; |
| } |
| |
| static public function performInternal(textFlow:TextFlow, target:ElementMark, mementoList:Array):FlowGroupElement |
| { |
| // split all the way up the chain and then do a move |
| var targetElement:FlowGroupElement = target.findElement(textFlow) as FlowGroupElement; |
| var child:FlowGroupElement = (target.elemStart == targetElement.textLength ? targetElement.getLastLeaf() : targetElement.findLeaf(target.elemStart)).parent; |
| var newChild:FlowGroupElement; |
| |
| var splitStart:int = target.elemStart; |
| var memento:IMemento; |
| |
| for (;;) |
| { |
| var splitPos:int = splitStart - (child.getAbsoluteStart()-targetElement.getAbsoluteStart()); |
| //if (splitPos != 0) |
| { |
| var splitMemento:InternalSplitFGEMemento = InternalSplitFGEMemento.perform(textFlow,child,splitPos, true); |
| if (mementoList) |
| mementoList.push(splitMemento); |
| newChild = splitMemento.newSibling; |
| |
| if (child is ParagraphElement && !(target.elemStart == targetElement.textLength)) |
| { |
| // count the terminator |
| splitStart++; |
| } |
| else if (child is ContainerFormattedElement) |
| { |
| // if its a ContainerFormattedElement there needs to be a paragraph at position zero on each side |
| if (!testValidLeadingParagraph(child)) |
| { |
| memento = ModelEdit.addElement(textFlow,new ParagraphElement,child,0); |
| if (mementoList) |
| mementoList.push(memento); |
| splitStart++; |
| } |
| if (!testValidLeadingParagraph(newChild)) |
| { |
| memento = ModelEdit.addElement(textFlow,new ParagraphElement,newChild,0); |
| if (mementoList) |
| mementoList.push(memento); |
| } |
| } |
| } |
| if (child == targetElement) |
| break; |
| child = child.parent; |
| } |
| |
| return newChild; |
| } |
| |
| public function undo():* |
| { |
| _mementoList.reverse(); |
| for each (var memento:IMemento in _mementoList) |
| memento.undo(); |
| _mementoList.reverse(); |
| } |
| |
| public function redo():* |
| { return performInternal(_textFlow, _target, null); } |
| } |
| |
| import flashx.textLayout.edit.TextFlowEdit; |
| |
| class JoinMemento extends BaseMemento implements IMemento |
| { |
| private var _element1:ElementMark; |
| private var _element2:ElementMark; |
| private var _joinPosition:int; |
| private var _removeParentChain:IMemento; |
| |
| public function JoinMemento(textFlow:TextFlow, element1:ElementMark, element2:ElementMark, joinPosition:int, removeParentChain:IMemento) |
| { |
| super(textFlow); |
| _element1 = element1; |
| _element2 = element2; |
| _joinPosition = joinPosition; |
| _removeParentChain = removeParentChain; |
| } |
| |
| static public function perform(textFlow:TextFlow, element1:FlowGroupElement, element2:FlowGroupElement, createMemento:Boolean):* |
| { |
| var joinPosition:int = element1.textLength - 1; |
| |
| var element1Mark:ElementMark = new ElementMark(element1,0); |
| var element2Mark:ElementMark = new ElementMark(element2,0); |
| performInternal(textFlow, element1Mark, element2Mark); |
| var removeParentChain:IMemento = TextFlowEdit.removeEmptyParentChain(element2); |
| |
| if (createMemento) |
| { |
| return new JoinMemento(textFlow, element1Mark, element2Mark, joinPosition, removeParentChain); |
| } |
| |
| return null; |
| } |
| |
| static public function performInternal(textFlow:TextFlow, element1Mark:ElementMark, element2Mark:ElementMark):void |
| { |
| var element1:FlowGroupElement = element1Mark.findElement(textFlow) as FlowGroupElement; |
| var element2:FlowGroupElement = element2Mark.findElement(textFlow) as FlowGroupElement; |
| |
| moveChildren(element2, element1); |
| } |
| |
| static private function moveChildren(elementSource:FlowGroupElement, elementDestination:FlowGroupElement): void |
| { |
| // move children of elementSource to end of elementDestination |
| var childrenToMove:Array = elementSource.mxmlChildren; |
| elementSource.replaceChildren(0, elementSource.numChildren); |
| elementDestination.replaceChildren(elementDestination.numChildren, elementDestination.numChildren, childrenToMove); |
| } |
| |
| public function undo():* |
| { |
| _removeParentChain.undo(); |
| |
| var element1:FlowGroupElement = _element1.findElement(_textFlow) as FlowGroupElement; |
| var element2:FlowGroupElement = _element2.findElement(_textFlow) as FlowGroupElement; |
| var tmpElement:FlowGroupElement = element1.splitAtPosition(_joinPosition) as FlowGroupElement; |
| // everything after the split moves to element2 |
| moveChildren(tmpElement, element2); |
| tmpElement.parent.removeChild(tmpElement); |
| } |
| |
| public function redo():* |
| { |
| performInternal(_textFlow, _element1, _element2); |
| _removeParentChain.redo(); |
| } |
| } |
| |
| class AddElementMemento extends BaseMemento implements IMemento |
| { |
| private var _target:ElementMark; |
| private var _targetIndex:int; |
| private var _elemToAdd:FlowElement; |
| |
| public function AddElementMemento(textFlow:TextFlow, elemToAdd:FlowElement, target:ElementMark, index:int) |
| { |
| super(textFlow); |
| _target = target; |
| _targetIndex = index; |
| _elemToAdd = elemToAdd; |
| } |
| |
| static public function perform(textFlow:TextFlow, elemToAdd:FlowElement, parent:FlowGroupElement, index:int, createMemento:Boolean):* |
| { |
| var elem:FlowElement = elemToAdd; |
| if (createMemento) |
| elemToAdd = elem.deepCopy(); // for redo |
| |
| var target:ElementMark = new ElementMark(parent,0); |
| |
| var targetElement:FlowGroupElement = target.findElement(textFlow) as FlowGroupElement; |
| targetElement.addChildAt(index,elem); |
| if (createMemento) |
| return new AddElementMemento(textFlow, elemToAdd, target, index); |
| return null; |
| } |
| |
| public function undo():* |
| { |
| var target:FlowGroupElement = _target.findElement(_textFlow) as FlowGroupElement; |
| target.removeChildAt(_targetIndex); |
| } |
| |
| public function redo():* |
| { |
| var parent:FlowGroupElement = _target.findElement(_textFlow) as FlowGroupElement; |
| return perform(_textFlow, _elemToAdd, parent, _targetIndex, false); |
| } |
| } |
| |
| class MoveElementMemento extends BaseMemento implements IMemento |
| { |
| private var _target:ElementMark; |
| private var _targetIndex:int; |
| |
| private var _elemBeforeMove:ElementMark; |
| private var _elemAfterMove:ElementMark; |
| private var _source:ElementMark; // original parent |
| private var _sourceIndex:int; // original index |
| |
| public function MoveElementMemento(textFlow:TextFlow, elemBeforeMove:ElementMark, elemAfterMove:ElementMark, target:ElementMark, targetIndex:int, source:ElementMark, sourceIndex:int) |
| { |
| super(textFlow); |
| _elemBeforeMove = elemBeforeMove; |
| _elemAfterMove = elemAfterMove; |
| _target = target; |
| _targetIndex = targetIndex; |
| _source = source; |
| _sourceIndex = sourceIndex; |
| } |
| |
| static public function perform(textFlow:TextFlow, elem:FlowElement, newParent:FlowGroupElement, newIndex:int, createMemento:Boolean):* |
| { |
| var target:ElementMark = new ElementMark(newParent,0); |
| var elemBeforeMove:ElementMark = new ElementMark(elem, 0); |
| |
| var source:FlowGroupElement = elem.parent; |
| var sourceIndex:int = source.getChildIndex(elem); |
| var sourceMark:ElementMark = new ElementMark(source, 0); |
| |
| newParent.addChildAt(newIndex,elem); |
| if (createMemento) |
| return new MoveElementMemento(textFlow, elemBeforeMove, new ElementMark(elem, 0), target, newIndex, sourceMark, sourceIndex); |
| return elem; |
| } |
| |
| public function undo():* |
| { |
| var elem:FlowElement = _elemAfterMove.findElement(_textFlow); |
| elem.parent.removeChildAt(elem.parent.getChildIndex(elem)); |
| var source:FlowGroupElement = _source.findElement(_textFlow) as FlowGroupElement; |
| source.addChildAt(_sourceIndex,elem); |
| } |
| |
| public function redo():* |
| { |
| var target:FlowGroupElement = _target.findElement(_textFlow) as FlowGroupElement; |
| var elem:FlowElement = _elemBeforeMove.findElement(_textFlow) as FlowElement; |
| return perform(_textFlow, elem, target, _targetIndex, false); |
| } |
| } |
| |
| class RemoveElementsMemento extends BaseMemento implements IMemento |
| { |
| private var _elements:Array; |
| |
| private var _elemParent:ElementMark; |
| private var _startIndex:int; |
| private var _numElements:int; |
| |
| /** |
| * RemoveElements from the TextFlow, |
| * @param parent parent of elements to rmeove |
| * @param startIndex index of first child to remove |
| * @param numElements number of elements to remove |
| */ |
| public function RemoveElementsMemento(textFlow:TextFlow, elementParent:ElementMark, startIndex:int, numElements:int, elements:Array) |
| { |
| super(textFlow); |
| _elemParent = elementParent; |
| _startIndex = startIndex; |
| _numElements = numElements; |
| _elements = elements; |
| } |
| |
| static public function perform(textFlow:TextFlow, parent:FlowGroupElement, startIndex:int, numElements:int, createMemento:Boolean):* |
| { |
| var elemParent:ElementMark = new ElementMark(parent,0); |
| |
| // hold on to elements for undo |
| var elements:Array = parent.mxmlChildren.slice(startIndex, startIndex + numElements); |
| // now remove them |
| parent.replaceChildren(startIndex, startIndex + numElements); |
| if (createMemento) |
| return new RemoveElementsMemento(textFlow, elemParent, startIndex, numElements, elements); |
| return elements; |
| } |
| |
| public function undo():* |
| { |
| var parent:FlowGroupElement = _elemParent.findElement(_textFlow) as FlowGroupElement; |
| parent.replaceChildren(_startIndex,_startIndex,_elements); |
| _elements = null; // release the saved elements array |
| return parent.mxmlChildren.slice(_startIndex,_startIndex+_numElements); |
| } |
| |
| public function redo():* |
| { |
| var parent:FlowGroupElement = _elemParent.findElement(_textFlow) as FlowGroupElement; |
| _elements = perform(_textFlow, parent, _startIndex, _numElements, false); |
| } |
| } |