blob: 7281c99875c3c2db75a57fdef477941414a8bcd4 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package flashx.textLayout.edit
{
import flashx.textLayout.debug.assert;
import flashx.textLayout.elements.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);
}
}