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