1////////////////////////////////////////////////////////////////////////////////
2//
3// ADOBE SYSTEMS INCORPORATED
4// Copyright 2007-2010 Adobe Systems Incorporated
5// All Rights Reserved.
6//
7// NOTICE:  Adobe permits you to use, modify, and distribute this file
8// in accordance with the terms of the license agreement accompanying it.
9//
10////////////////////////////////////////////////////////////////////////////////
11package flashx.textLayout.edit
12{
13	import flash.display.DisplayObject;
14	import flash.display.DisplayObjectContainer;
15	import flash.display.InteractiveObject;
16	import flash.errors.IllegalOperationError;
17	import flash.events.Event;
18	import flash.events.FocusEvent;
19	import flash.events.IMEEvent;
20	import flash.events.KeyboardEvent;
21	import flash.events.MouseEvent;
22	import flash.events.TextEvent;
23	import flash.geom.Point;
24	import flash.geom.Rectangle;
25	import flash.system.Capabilities;
26	import flash.text.engine.TextLine;
27	import flash.ui.Keyboard;
28	import flash.utils.getQualifiedClassName;
29
30	import flashx.textLayout.compose.IFlowComposer;
31	import flashx.textLayout.container.ContainerController;
32	import flashx.textLayout.debug.Debugging;
33	import flashx.textLayout.debug.assert;
34	import flashx.textLayout.elements.Configuration;
35	import flashx.textLayout.elements.DivElement;
36	import flashx.textLayout.elements.FlowElement;
37	import flashx.textLayout.elements.FlowGroupElement;
38	import flashx.textLayout.elements.FlowLeafElement;
39	import flashx.textLayout.elements.GlobalSettings;
40	import flashx.textLayout.elements.InlineGraphicElement;
41	import flashx.textLayout.elements.LinkElement;
42	import flashx.textLayout.elements.ListElement;
43	import flashx.textLayout.elements.ListItemElement;
44	import flashx.textLayout.elements.ParagraphElement;
45	import flashx.textLayout.elements.SubParagraphGroupElement;
46	import flashx.textLayout.elements.TCYElement;
47	import flashx.textLayout.elements.TextFlow;
48	import flashx.textLayout.elements.TextRange;
49	import flashx.textLayout.events.FlowOperationEvent;
50	import flashx.textLayout.formats.IListMarkerFormat;
51	import flashx.textLayout.formats.ITextLayoutFormat;
52	import flashx.textLayout.formats.TextLayoutFormat;
53	import flashx.textLayout.operations.ApplyElementIDOperation;	import flashx.textLayout.operations.ApplyElementTypeNameOperation;
54	import flashx.textLayout.operations.ApplyFormatOperation;
55	import flashx.textLayout.operations.ApplyFormatToElementOperation;
56	import flashx.textLayout.operations.ApplyLinkOperation;
57	import flashx.textLayout.operations.ApplyTCYOperation;
58	import flashx.textLayout.operations.ClearFormatOnElementOperation;
59	import flashx.textLayout.operations.ClearFormatOperation;
60	import flashx.textLayout.operations.CompositeOperation;
61	import flashx.textLayout.operations.CreateDivOperation;
62	import flashx.textLayout.operations.CreateListOperation;
63	import flashx.textLayout.operations.CreateSubParagraphGroupOperation;
64	import flashx.textLayout.operations.CutOperation;
65	import flashx.textLayout.operations.DeleteTextOperation;
66	import flashx.textLayout.operations.FlowOperation;
67	import flashx.textLayout.operations.InsertInlineGraphicOperation;
68	import flashx.textLayout.operations.InsertTextOperation;
69	import flashx.textLayout.operations.ModifyInlineGraphicOperation;
70	import flashx.textLayout.operations.MoveChildrenOperation;
71	import flashx.textLayout.operations.PasteOperation;
72	import flashx.textLayout.operations.RedoOperation;
73	import flashx.textLayout.operations.SplitElementOperation;
74	import flashx.textLayout.operations.SplitParagraphOperation;
75	import flashx.textLayout.operations.UndoOperation;
76	import flashx.textLayout.tlf_internal;
77	import flashx.textLayout.utils.CharacterUtil;
78	import flashx.textLayout.utils.GeometryUtil;
79	import flashx.textLayout.utils.NavigationUtil;
80	import flashx.undo.IOperation;
81	import flashx.undo.IUndoManager;
82
83	use namespace tlf_internal;
84
85	/**
86	 * The EditManager class manages editing changes to a TextFlow.
87	 *
88	 * <p>To enable text flow editing, assign an EditManager object to the <code>interactionManager</code>
89	 * property of the TextFlow object. The edit manager handles changes to the text (such as insertions,
90	 * deletions, and format changes). Changes are reversible if the edit manager has an undo manager. The edit
91	 * manager triggers the recomposition and display of the text flow, as necessary.</p>
92	 *
93	 * <p>The EditManager class supports the following keyboard shortcuts:</p>
94	 *
95	 * <table class="innertable" width="100%">
96	 *  <tr><th>Keys</th><th>Result</th></tr>
97	 *  <tr><td>ctrl-z</td><td>undo</td></tr>
98	 * 	<tr><td>ctrl-y</td><td>redo</td></tr>
99	 * 	<tr><td>ctrl-backspace</td><td>deletePreviousWord</td></tr>
100	 * 	<tr><td>ctrl-delete</td><td>deleteNextWord</td></tr>
101	 * 	<tr><td>alt+delete</td><td>deleteNextWord</td></tr>
102	 * 	<tr><td>ctrl+alt-delete</td><td>deleteNextWord</td></tr>
103	 * 	<tr><td>ctrl-shift-hyphen</td><td>insert discretionary hyphen</td></tr>
104	 * 	<tr><td>ctrl+backspace</td><td>deletePreviousWord</td></tr>
105	 * 	<tr><td>alt+backspace</td><td>deletePreviousWord</td></tr>
106	 * 	<tr><td>ctrl+alt-backspace</td><td>deletePreviousWord</td></tr>
107	 * 	<tr><td>INSERT</td><td>toggles overWriteMode</td></tr>
108	 * 	<tr><td>backspace</td><td>deletePreviousCharacter</td></tr>
109	 * 	<tr><td>ENTER</td><td>if textFlow.configuration.manageEnterKey in a list it creates a new list item, otherwise creates a new paragraph</td></tr>
110	 * 	<tr><td>shift-ENTER</td><td>if textFlow.configuration.manageEnterKey creates a new paragraph</td></tr>
111	 * 	<tr><td>TAB</td><td>if textFlow.configuration.manageTabKey in a list it creates nested list, otherwise inserts a TAB or overwrites next character with a TAB</td></tr>
112	 * 	<tr><td>shift-TAB</td><td>if textFlow.configuration.manageTabKey in the first item of a list it moves the item out of the list (promotes it)</td></tr>
113	 * </table>
114	 *
115	 * <p><strong>Note:</strong> The following keys do not work on Windows: alt-backspace, alt-delete, ctrl+alt-backspace,
116	 * and ctrl+alt-delete. These keys do not generate an event for the runtime.</p>
117 	 *
118 	 * @see flashx.textLayout.elements.TextFlow
119 	 * @see flashx.undo.UndoManager
120	 *
121	 * @includeExample examples\EditManager_example.as -noswf
122	 *
123	 * @playerversion Flash 10
124	 * @playerversion AIR 1.5
125 	 * @langversion 3.0
126	 */
127	public class EditManager extends SelectionManager implements IEditManager
128	{
129		 /**
130		 *  To minimize expensive recompositions during fast typing, inserts
131		 *  don't necessarily take place immediately. An insert operation that
132		 *  hasn't yet executed is held here.
133		 */
134		private var pendingInsert:InsertTextOperation;
135
136		/*
137		 * The object that has the ENTER_FRAME event listener attached to perform pending inserts.
138		 */
139		private var enterFrameListener:DisplayObjectContainer;
140
141		/* True if updates get handled on enter_frame, and not immediately. */
142		private var _delayUpdates:Boolean = false;
143
144		/* True if some operations (e.g., handling of text events) can be delayed to next enterFrame by default. False for immediate handling */
145		private var _allowDelayedOperations:Boolean = true;
146
147		/*
148		 * The object that has the ENTER_FRAME event listener attached to perform pending updates.
149		 */
150		private var redrawListener:DisplayObjectContainer;
151
152		/*
153		 *  Some operations can be undone & redone. The undoManager keeps track
154		 *  of the operations that have been done or undone so that they can be undone or
155		 *  redone.  I'm not sure if only text operations can be undone. If so, the undoManager
156		 *  should probably be moved to EditManager.
157		 */
158		private var _undoManager:flashx.undo.IUndoManager;
159
160		private var _imeSession:IMEClient;
161		private var _imeOperationInProgress:Boolean;
162
163		/**
164		 * Indicates whether overwrite mode is on or off.
165		 *
166		 * <p>If <code>true</code>, then a keystroke overwrites the character following the cursor.
167		 * If <code>false</code>, then a keystroke is inserted at the cursor location.</p>
168		 *
169		 * @playerversion Flash 10
170		 * @playerversion AIR 1.5
171 	 	 * @langversion 3.0
172		*/
173		public static var overwriteMode:Boolean = false;
174
175		/**
176		 * Creates an EditManager object.
177		 *
178		 * <p>Assign an EditManager object to the <code>interactionManager</code> property
179		 * of a text flow to enable editing of that text flow. </p>
180		 *
181		 * <p>To enable support for undoing and redoing changes, pass an
182		 * IUndoManager instance to the EditManager constructor. You can use
183		 * the <code>flashx.undo.UndoManager</code> class
184		 * or create a custom IUndoManager instance. Use a custom IUndoManager instance
185		 * to integrate Text Layout Framework changes with an existing
186		 * undo manager that is not an instance of the UndoManager class.
187		 * To create a custom IUndoManager instance, ensure that the class
188		 * you use to define the undo manager
189		 * implements the IUndoManager interface.</p>
190		 *
191		 *
192		 * @param undo	The UndoManager for the application
193		 *
194		 * @see flashx.textLayout.elements.TextFlow#interactionManager
195		 * @see flashx.undo.IUndoManager
196		 *
197		 * @includeExample examples\EditManager_constructor.as -noswf
198		 * @playerversion Flash 10
199		 * @playerversion AIR 1.5
200 	 	 * @langversion 3.0
201		 */
202		public function EditManager(undoManager:flashx.undo.IUndoManager = null)
203		{
204			super();
205			_undoManager = undoManager;
206		}
207
208		/**
209		 * The IUndoManager assigned to this edit manager.
210		 *
211		 * <p>To allow edits to be undone (and redone), pass an IUndoManager instance to the EditManager
212		 * constructor. The undo manager maintains a stack of operations that have been executed, and it can
213		 * undo or redo individual operations. </p>
214		 *
215		 * <p><b>Note:</b> If the TextFlow is modified directly (not via
216		 * calls to the EditManager, but directly via calls to the managed FlowElement objects), then the EditManager
217		 * clears the undo stack to prevent the stack from getting out of sync with the current state.</p>
218		 *
219	 	 * @playerversion Flash 10
220	 	 * @playerversion AIR 1.5
221	 	 * @langversion 3.0
222	 	 */
223		public function get undoManager():flashx.undo.IUndoManager
224		{
225			return _undoManager;
226		}
227
228		// Backdoor provided so that IMEClient can temporarily use an undo manager to maintain the IME session state.
229		/** @private */
230		tlf_internal function setUndoManager(undoManager:flashx.undo.IUndoManager):void
231		{
232			_undoManager = undoManager;
233		}
234
235		/** @private */
236		override public function editHandler(event:Event):void
237		{
238			if (event.isDefaultPrevented())
239				return;
240
241			super.editHandler(event);
242			switch (event.type)
243			{
244				case Event.CUT:
245				case Event.CLEAR:
246					if (activePosition != anchorPosition)
247					{
248						if (event.type == Event.CUT)
249							TextClipboard.setContents(cutTextScrap());
250						else
251							deleteText(null);
252						event.preventDefault();
253					}
254					break;
255				case Event.PASTE:
256					pasteTextScrap(TextClipboard.getContents());
257					event.preventDefault();
258					break;
259			}
260		}
261
262		// ///////////////////////////////////
263		// keyboard methods
264		// ///////////////////////////////////
265
266		/** @private */
267		public override function keyDownHandler(event:KeyboardEvent):void
268		{
269			var listItem:ListItemElement;
270			var operationState:SelectionState;
271
272			if (!hasSelection() || event.isDefaultPrevented())
273				return;
274
275			if (redrawListener)		// pending redraw; handle this before anything else so TextLines on screen will be up to date
276				updateAllControllers();
277
278			super.keyDownHandler(event);
279
280			if (event.ctrlKey)
281			{
282				// The player subsequently sends a text input event (which should be ignored) as listed below:
283				// CTRL/CMD+z: Only on Mac when using a pre-Argo player version
284				// CTRL/CMD+y: On all platforms (the exact char code for the text input event is platform dependent)
285				if (!event.altKey)
286				{
287					if (_imeSession != null && ((event.charCode == 122) || (event.charCode == 121)))
288						_imeSession.compositionAbandoned();		// must be done before undo or redo start b/c IME session uses undo also for its own rollback
289					// do Operation will also cancel the session.
290
291					switch(event.charCode)
292					{
293						case 122:	// small z
294							/* pre-Argo and on the mac then ignoreNextTextEvent */
295							if (!Configuration.versionIsAtLeast(10,1) && (Capabilities.os.search("Mac OS") > -1))
296								ignoreNextTextEvent = true;
297							undo();
298							event.preventDefault();
299							break;
300						case 121:	// small y
301							ignoreNextTextEvent = true;
302							redo();
303							event.preventDefault();
304							break;
305						case Keyboard.BACKSPACE:
306							if (_imeSession)
307								_imeSession.compositionAbandoned();
308							deletePreviousWord();
309							event.preventDefault();
310							break;
311					}
312					if (event.keyCode == Keyboard.DELETE)
313					{
314						if (_imeSession)
315							_imeSession.compositionAbandoned();
316						deleteNextWord();
317						event.preventDefault();
318					}
319
320					if (event.shiftKey)
321					{
322						// detect ctrl-shift-"-" (cnd-shift-"-" on mac) and insert a DH
323						if (event.charCode == 95)
324						{
325							if (_imeSession)
326								_imeSession.compositionAbandoned();
327
328							//a discretionary hyphen is being inserted.
329							var discretionaryHyphenString:String = String.fromCharCode(0x000000AD);
330							overwriteMode ? overwriteText(discretionaryHyphenString) : insertText(discretionaryHyphenString);
331							event.preventDefault();
332						}
333					}
334				}
335			}
336			else if (event.altKey)
337			{
338				if (event.charCode == Keyboard.BACKSPACE)
339				{
340					deletePreviousWord();
341					event.preventDefault();
342				}
343				else if (event.keyCode == Keyboard.DELETE)
344				{
345					deleteNextWord();
346					event.preventDefault();
347				}
348			}
349			// not ctrl key or alt key
350			else if (event.keyCode == Keyboard.DELETE) //del
351			{
352				deleteNextCharacter();
353				event.preventDefault();
354			}
355			else if (event.keyCode == Keyboard.INSERT) //insert
356			{
357				overwriteMode = !overwriteMode;
358				event.preventDefault();
359			}
360			else switch(event.charCode) {
361				case Keyboard.BACKSPACE:
362					deletePreviousCharacter();
363					event.preventDefault();
364					break;
365				case Keyboard.ENTER:
366					if (textFlow.configuration.manageEnterKey)
367					{
368						var firstLeaf:FlowLeafElement = textFlow.findLeaf(absoluteStart);
369						listItem = firstLeaf.getParentByType(ListItemElement) as ListItemElement;
370						// if the listitem has a ListElement child then treat this as a regular Paragraph
371						if (listItem && firstLeaf.getParentByType(ListElement) != listItem.getParentByType(ListElement))
372							listItem = null;
373
374						// inside a list shift-enter splits a paragraph and shift splits the listitem
375						if (listItem && !event.shiftKey)
376						{
377							// if on last item of list and it's empty, remove it and put cursor on a new para immediatly following the list (new para should be wrapped in a new list item if parent of list is another list).
378							if(listItem.textLength == 1 && listItem.parent.getChildIndex(listItem) == listItem.parent.numChildren - 1)
379							{
380								operationState = defaultOperationState();
381								if (!operationState)
382									return;
383
384								doOperation(new MoveChildrenOperation(operationState, listItem.parent, listItem.parent.getChildIndex(listItem), 1, listItem.parent.parent, listItem.parent.parent.getChildIndex(listItem.parent) + 1));
385							}
386							else
387							{
388								splitElement(listItem);
389								// push cursor past the marker
390								selectRange(absoluteStart+1,absoluteStart+1);
391								refreshSelection();
392							}
393						}
394						else
395							splitParagraph();
396						event.preventDefault();
397						event.stopImmediatePropagation();
398					}
399					break;
400				case Keyboard.TAB:
401					if (textFlow.configuration.manageTabKey)
402					{
403						listItem  = textFlow.findLeaf(absoluteStart).getParentByType(ListItemElement) as ListItemElement;
404						if (listItem && listItem.getAbsoluteStart() == absoluteStart)
405						{
406							operationState = defaultOperationState();
407							if (!operationState)
408								return;
409
410							if(event.shiftKey)
411							{
412								// unindent by moving list element(s) out of parent into grandparent
413
414								// first make sure there is a grandparent
415								if(listItem.parent.parent is ListElement && listItem.parent.getChildIndex(listItem) == 0)
416								{
417									var source:FlowGroupElement;
418									var target:FlowGroupElement;
419									var numElements:int;
420									var sourceIndex:int;
421									var targetIndex:int;
422
423									{
424										source = listItem.parent;
425										target = listItem.parent.parent;
426										numElements = listItem.parent.numChildren;
427										sourceIndex = 0;
428										targetIndex = listItem.parent.parent.getChildIndex(listItem.parent);
429									}
430									doOperation(new MoveChildrenOperation(operationState, source, sourceIndex, numElements, target, targetIndex));
431								}
432							}
433							else
434							{
435								// create new list from list element(s)
436								var element:FlowGroupElement = listItem;
437								if(listItem.parent.getChildIndex(listItem) == 0)
438									element = listItem.parent;
439
440								doOperation(new CreateListOperation(new SelectionState(textFlow, element.getAbsoluteStart(), element.getAbsoluteStart() + element.textLength), listItem.parent));
441							}
442						}
443						else
444						{
445							overwriteMode ? overwriteText(String.fromCharCode(event.charCode)) : insertText(String.fromCharCode(event.charCode));
446						}
447						event.preventDefault();
448					}
449					break;
450			}
451		}
452
453		/** @private */
454		public override function keyUpHandler(event:KeyboardEvent):void
455		{
456			if (!hasSelection() || event.isDefaultPrevented())
457				return;
458
459			super.keyUpHandler(event);
460
461			if ((textFlow.configuration.manageEnterKey && event.charCode == Keyboard.ENTER) || (textFlow.configuration.manageTabKey && event.charCode == Keyboard.TAB)) {
462				event.stopImmediatePropagation();
463			}
464		}
465
466		/** @private */
467		public override function keyFocusChangeHandler(event:FocusEvent):void
468		{
469			if (textFlow.configuration.manageTabKey)
470				event.preventDefault();
471		}
472
473		/** @private */
474		public override function mouseDownHandler(event:MouseEvent):void
475		{
476			if (redrawListener)
477				updateAllControllers();
478			super.mouseDownHandler(event);
479		}
480
481		/** @private */
482		public override function textInputHandler(event:TextEvent):void
483		{
484			if (!ignoreNextTextEvent)
485			{
486				var charCode:int = event.text.charCodeAt(0);
487				// only if its a space or larger - ignore control characters here
488				if (charCode >=  32)
489					overwriteMode ? overwriteText(event.text) : insertText(event.text);
490			}
491			ignoreNextTextEvent = false;
492		}
493
494		/** @private */
495		override public function focusOutHandler(event:FocusEvent):void
496		{
497			super.focusOutHandler(event);
498			if (_imeSession && selectionFormatState != SelectionFormatState.FOCUSED)
499				_imeSession.compositionAbandoned();
500		}
501
502		/** @private */
503		override public function deactivateHandler(event:Event):void
504		{
505			super.deactivateHandler(event);
506			if (_imeSession)
507				_imeSession.compositionAbandoned();
508		}
509
510		/** @private */
511		override public function imeStartCompositionHandler(event:IMEEvent):void
512		{
513			CONFIG::debug{ assert(!_imeSession, "IME session already in progress: IME not reentrant!"); }
514		//	CONFIG::debug { Debugging.traceOut("imeStartComposition event"); }
515
516			// any pending operations must be executed first, to
517			// preserve operation order.
518			flushPendingOperations();
519
520			// Coded to avoid dependency on Argo (10.1).
521			if (!(event["imeClient"]))
522			{
523				_imeSession = new IMEClient(this);
524				_imeOperationInProgress = false;
525				event["imeClient"] = _imeSession;
526			}
527		}
528
529		/** @private */
530		override public function setFocus():void
531		{
532			var flowComposer:IFlowComposer = textFlow ? textFlow.flowComposer : null;
533			if (_imeSession && flowComposer && flowComposer.numControllers > 1)
534			{
535				// container with the ime start position gets the key focus
536				_imeSession.setFocus();
537
538				setSelectionFormatState(SelectionFormatState.FOCUSED);
539			}
540			else
541				super.setFocus();
542
543			/* CONFIG::debug
544			{
545				if (textFlow.flowComposer.getControllerAt(0).container.stage)
546				{
547					var focusDI:DisplayObject = textFlow.flowComposer.getControllerAt(0).container.stage.focus;
548					trace("set focus to ", focusDI.hasOwnProperty("name") ? focusDI["name"] : focusDI.toString());
549				}
550			} */
551		}
552		/** @private */
553		tlf_internal function endIMESession():void
554		{
555			_imeSession = null;
556			var flowComposer:IFlowComposer = textFlow ? textFlow.flowComposer : null;
557			if (flowComposer && flowComposer.numControllers > 1)
558				setFocus();
559		}
560		/** @private */
561		tlf_internal function beginIMEOperation():void
562		{
563			_imeOperationInProgress = true;
564			beginCompositeOperation();
565		}
566		/** @private */
567		tlf_internal function endIMEOperation():void
568		{
569			endCompositeOperation();
570			_imeOperationInProgress = false;
571		}
572
573		/** @private We track the nesting level of the doOperation, because in finalize we need to know if
574		we are at the outermost level and need to push the operation on the undo stack and redraw
575		the screen, or if we're in a nested level and need to append the operation to the next
576		level up. */
577		tlf_internal var captureLevel:int = 0;
578
579		/**
580		  * @copy IEditManager#doOperation()
581		  *
582		  * @includeExample examples\EditManager_doOperation.as -noswf
583		  *
584		  * @playerversion Flash 10
585		  * @playerversion AIR 1.5
586 	 	  * @langversion 3.0
587		  */
588		public override function doOperation(operation:FlowOperation):void
589		{
590			CONFIG::debug { assert(operation.textFlow == this.textFlow,"Operation from a different TextFlow"); }
591
592			// If we get any operation during an IME session that is not owned by the session, we cancel the IME
593			if (_imeSession && !_imeOperationInProgress)
594				_imeSession.compositionAbandoned();
595
596			// any pending operations must be executed first, to
597			// preserve operation order.
598			flushPendingOperations();
599
600			try
601			{
602				captureLevel++;
603				operation = doInternal(operation);
604			}
605			catch(e:Error)
606			{
607				captureLevel--;
608				throw(e);
609			}
610			captureLevel--;
611
612			if (operation)			// don't finalize if operation was cancelled
613				finalizeDo(operation);
614		}
615
616		private function finalizeDo(op:FlowOperation):void
617		{
618			// Handle operation if we're in a beginCompositeOperation/endCompositeOperation context
619			// In this case any nested commands we do will get added to the composite operation when
620			// they're done instead of added to the undo stack.
621			var parentOperation:CompositeOperation;
622			if (parentStack && parentStack.length > 0)
623			{
624				var parent:Object = parentStack[parentStack.length - 1];
625				if (parent.captureLevel == captureLevel)
626					parentOperation = parent.operation as CompositeOperation;
627			}
628
629	//		CONFIG::debug { assert(captureLevel == 0 || parentOperation != null, "missing parent for nested operation"); }
630
631			if (parentOperation)
632				parentOperation.addOperation(op);
633
634			else if (captureLevel == 0)
635			{
636				captureOperations.length = 0;
637				if (_undoManager)
638				{
639					if (_undoManager.canUndo() && allowOperationMerge)
640					{
641						var lastOp:FlowOperation = _undoManager.peekUndo() as FlowOperation;
642						if (lastOp)
643						{
644							// Try to merge the last operation on the stack with the current
645							// operation. This may modify lastOp, or return a new operation
646							var combinedOp:FlowOperation = lastOp.merge(op);
647							if (combinedOp)
648							{
649								CONFIG::debug { assert(combinedOp.endGeneration == textFlow.generation,"Who did what?"); }
650								CONFIG::debug { assert(combinedOp.canUndo() && combinedOp.endGeneration == op.endGeneration,"Bad operation merge in EditManager") };
651								_undoManager.popUndo();
652								op = combinedOp;
653							}
654						}
655					}
656					if (op.canUndo())
657						_undoManager.pushUndo(op);
658					allowOperationMerge = true;
659
660					// following operations are no longer redoable
661					_undoManager.clearRedo();
662				}
663
664				handleUpdate();
665
666				if (!_imeSession)
667				{
668					var opEvent:FlowOperationEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_COMPLETE,false,false,op,0,null);
669					textFlow.dispatchEvent(opEvent);
670				}
671			}
672		}
673
674		private var captureOperations:Array = [];
675
676		/** Internal guts of a dooperation - Execute a FlowOperation.  This function proceeds in steps.
677		  * <p>Step 2. Send a canceallable OperationEvent.  If cancelled this method returns immediately.</p>
678		  * If it is not cancelled, the listener may "do" other operations by calling back into the EditManager. This will result
679		  * in a nested call to do which will post additional commands to the captureOperations array.
680		  * <p>Step 3. Execute the operation.  The operation returns true or false.  false indicates no changes were made.</p>
681		  * <p>Step 7. Send a OperationEvent. </p>
682		  * The listener may "do" other operations by calling back into the EditManager. This will result
683		  * in a nested call to do which will post additional commands to the captureOperations array.
684		  * <p>Exception handling.  If the operation throws the exception is caught and the error is attached to the event dispatched
685		  * at step 7.  If the event is not cancelled the error is rethrown.</p>
686		  */
687		private function doInternal(op:FlowOperation):FlowOperation
688		{
689			CONFIG::debug { assert(op.textFlow == this.textFlow,"Operation from a different TextFlow"); }
690
691			var captureStart:int = captureOperations.length;
692			var success:Boolean = false;
693			var opEvent:FlowOperationEvent;
694
695			// tell any listeners about the operation
696			if (!_imeSession)
697			{
698				opEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_BEGIN,false,true,op,captureLevel-1,null);
699				textFlow.dispatchEvent(opEvent);
700				if (opEvent.isDefaultPrevented())
701					return null;
702				// user may replace the operation - TODO: WHAT IF SWITCH TO UNDO/REDO????
703				op = opEvent.operation;
704				if ((op is UndoOperation) || (op is RedoOperation))
705					throw new IllegalOperationError(GlobalSettings.resourceStringFunction("illegalOperation",[ getQualifiedClassName(op) ]));
706			}
707
708			var opError:Error = null;
709			try
710			{
711				// begin this op after pending ops are flushed
712				CONFIG::debug
713				{
714					if (captureLevel <= 1)
715						debugCheckTextFlow();
716				}
717
718				// null return implies no operation was done - just discard it
719				var beforeGeneration:uint = textFlow.generation;
720				op.setGenerations(beforeGeneration,0);
721
722				captureOperations.push(op);
723				success = op.doOperation();
724				if (success)		// operation succeeded
725				{
726					textFlow.normalize();   //force normalization at this point. Don't compose unless the captureLevel is 0
727
728					// This has to be done after the normalize, because normalize increments the generation number
729					op.setGenerations(beforeGeneration,textFlow.generation);
730				}
731				else
732				{
733					var index:int = captureOperations.indexOf(op);
734					if (index >= 0)
735						captureOperations.splice(index, 1);
736				}
737			}
738			catch(e:Error)
739			{
740				opError = e;
741			}
742
743			// operation completed - send event whether it succeeded or not.
744			// client can check generation number for changes
745			if (!_imeSession)
746			{
747				opEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_END,false,true,op,captureLevel-1,opError);
748				textFlow.dispatchEvent(opEvent);
749				opError = opEvent.isDefaultPrevented() ? null : opEvent.error;
750			}
751
752			if (opError)
753				throw (opError);
754
755			// If we fired off any subsidiary operations, create a composite operation to hold them all
756		 	if (captureOperations.length - captureStart > 1)
757		 	{
758				op = new CompositeOperation(captureOperations.slice(captureStart));
759				op.setGenerations(FlowOperation(CompositeOperation(op).operations[0]).beginGeneration,textFlow.generation);
760				allowOperationMerge = false;
761				captureOperations.length = captureStart;
762		 	}
763
764			return success ? op : null;
765		}
766
767		/** @private **/
768		override public function set textFlow(value:TextFlow):void
769		{
770			flushPendingOperations();
771			if (redrawListener)	// detach handler if there is one
772				updateAllControllers();
773			super.textFlow = value;
774		}
775
776		/**
777		 * @copy IEditManager.delayUpdates
778		 */
779		public function get delayUpdates():Boolean
780		{
781			return _delayUpdates;
782		}
783		public function set delayUpdates(value:Boolean):void
784		{
785			_delayUpdates = value;
786		}
787
788		private function redrawHandler(e:Event):void
789		{
790			// This is here because it has to take an argument
791			updateAllControllers();
792		}
793
794		/** @copy IEditManager.updateAllControllers
795		 */
796		public function updateAllControllers():void
797		{
798			flushPendingOperations();
799
800			if (redrawListener)	// detach handler if there is one
801			{
802				redrawListener.removeEventListener(Event.ENTER_FRAME, redrawHandler);
803				redrawListener = null;
804			}
805
806			if (textFlow.flowComposer)
807			{
808				 textFlow.flowComposer.updateAllControllers();
809
810				// Scroll to selection
811				if (hasSelection())
812				{
813					var controllerIndex:int = textFlow.flowComposer.findControllerIndexAtPosition(activePosition);
814					if (controllerIndex >= 0)
815						textFlow.flowComposer.getControllerAt(controllerIndex).scrollToRange(activePosition,anchorPosition);
816				}
817			}
818
819			selectionChanged(true, false);
820
821			CONFIG::debug { debugCheckTextFlow(); }
822		}
823
824		// By default, the EditManager will update in response to a model change immediately.
825		// Client may also request a delayed update; in this case, we schedule an update on the
826		// next enter frame event.
827		private function handleUpdate():void
828		{
829			if (_delayUpdates)
830			{
831				if (!redrawListener)	// only need to attach if we're not already
832				{
833					var controller:ContainerController = textFlow.flowComposer.getControllerAt(0);
834					if (controller)
835					{
836						redrawListener = controller.container;
837						if (redrawListener)
838							redrawListener.addEventListener(Event.ENTER_FRAME, redrawHandler, false, 1.0, true);
839					}
840				}
841			}
842			else		// redraw now
843			{
844				updateAllControllers();
845			}
846		}
847
848		/** @copy IEditManager#allowDelayedOperations() */
849		public function get allowDelayedOperations():Boolean
850		{
851			return _allowDelayedOperations;
852		}
853		public function set allowDelayedOperations(value:Boolean):void
854		{
855			if (!value)
856				flushPendingOperations();
857			_allowDelayedOperations = value;
858		}
859
860		/** @private */
861		public override function flushPendingOperations():void
862		{
863			super.flushPendingOperations();
864			if (pendingInsert)
865			{
866				var pi0:InsertTextOperation = pendingInsert;
867				pendingInsert = null;
868				if (enterFrameListener)
869				{
870					enterFrameListener.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
871					enterFrameListener = null;
872				}
873				doOperation(pi0);
874			}
875		}
876
877		/**
878		 * @copy IEditManager#undo()
879		 * @includeExample examples\EditManager_undo.as -noswf
880		 *
881		 * @see flashx.undo.IUndoManager#undo()
882		 *
883		 * @playerversion Flash 10
884		 * @playerversion AIR 1.5
885 	 	 * @langversion 3.0
886		 */
887		public function undo():void
888		{
889			// Cancel out of an IME session if there is one.
890			// Some IMEs are on all the time, and so the undo has to win over the IME,
891			// otherwise you would never be able to undo in Korean.
892			if (_imeSession)
893				_imeSession.compositionAbandoned();
894
895			if (undoManager)
896				undoManager.undo();
897		}
898
899		/** @private */
900		public function performUndo(theop:IOperation):void
901		{
902			var operation:FlowOperation = theop as FlowOperation;
903			if ((!operation) || (operation.textFlow != textFlow))
904				return;
905			// tell any listeners about the operation
906			if (!_imeSession)
907			{
908				var undoPsuedoOp:UndoOperation = new UndoOperation(operation);
909				var opEvent:FlowOperationEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_BEGIN,false,true,undoPsuedoOp,0,null);
910				textFlow.dispatchEvent(opEvent);
911				if (opEvent.isDefaultPrevented())
912				{
913					//operation cancelled by user. Push the operation back onto the undo stack
914					undoManager.pushUndo(operation);
915					return;
916				}
917				undoPsuedoOp = opEvent.operation as UndoOperation;
918				if (!undoPsuedoOp)
919					throw new IllegalOperationError(GlobalSettings.resourceStringFunction("illegalOperation",[ getQualifiedClassName(opEvent.operation) ]));
920				operation = undoPsuedoOp.operation;
921			}
922
923			if (operation.endGeneration != textFlow.generation)
924			{
925				//CONFIG::debug { trace("EditManager.undo: skipping undo due to mismatched generation numbers. textFlow",textFlow.generation,flash.utils.getQualifiedClassName(operation),operation.endGeneration); }
926				return;
927			}
928
929			var opError:Error = null;
930			try
931			{
932				CONFIG::debug { debugCheckTextFlow(); }
933
934				var rslt:SelectionState;
935				rslt = operation.undo();
936
937				CONFIG::debug { assert(rslt != null,"undoable operations must return a SelectionState"); }
938				setSelectionState(rslt);
939				if (_undoManager)
940					_undoManager.pushRedo(operation);
941
942			}
943			catch(e:Error)
944			{
945				opError = e;
946			}
947
948			// tell user its complete and give them a chance to cancel the rethrow
949			if (!_imeSession)
950			{
951				opEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_END,false,true,undoPsuedoOp,0,opError);
952				textFlow.dispatchEvent(opEvent);
953				opError = opEvent.isDefaultPrevented() ? null : opEvent.error;
954			}
955
956			if (opError)
957				throw (opError);
958
959			handleUpdate();
960
961			// push the generation of the textFlow backwards - must be done after update which does a normalize
962			textFlow.setGeneration(operation.beginGeneration);
963
964			if (!_imeSession)
965			{
966				opEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_COMPLETE,false,false,undoPsuedoOp,0,null);
967				textFlow.dispatchEvent(opEvent);
968			}
969		}
970
971		/**
972		 * @copy IEditManager#redo()
973		 * @includeExample examples\EditManager_redo.as -noswf
974		 *
975		 * @see flashx.undo.IUndoManager#redo()
976		 *
977		 * @playerversion Flash 10
978		 * @playerversion AIR 1.5
979 	 	 * @langversion 3.0
980		 */
981		public function redo():void
982		{
983			// Cancel out of an IME session if there is one.
984			// Some IMEs are on all the time, and so the undo has to win over the IME,
985			// otherwise you would never be able to undo in Korean.
986			if (_imeSession)
987				_imeSession.compositionAbandoned();
988
989			if (undoManager)
990				undoManager.redo();
991		}
992
993		/** @private */
994		public function performRedo(theop:IOperation):void
995		{
996			var opEvent:FlowOperationEvent;
997			var op:FlowOperation = theop as FlowOperation;
998			if ((!op) || (op.textFlow != textFlow))
999				return;
1000			// tell any listeners about the operation
1001			if (!_imeSession)
1002			{
1003				var redoPsuedoOp:RedoOperation = new RedoOperation(op);
1004				opEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_BEGIN,false,true,redoPsuedoOp,0,null);
1005				textFlow.dispatchEvent(opEvent);
1006				if (opEvent.isDefaultPrevented() && _undoManager)
1007				{
1008					//user cancelled the event. Push the operation back onto the redo stack
1009					_undoManager.pushRedo(op);
1010					return;
1011				}
1012				redoPsuedoOp = opEvent.operation as RedoOperation;
1013				if (!redoPsuedoOp)
1014					throw new IllegalOperationError(GlobalSettings.resourceStringFunction("illegalOperation",[ getQualifiedClassName(opEvent.operation) ]));
1015				op = redoPsuedoOp.operation;
1016			}
1017
1018			if (op.beginGeneration != textFlow.generation)
1019			{
1020				//CONFIG::debug { trace("EditManager.redo: skipping redo due to mismatched generation numbers."); }
1021				return;
1022			}
1023
1024			var opError:Error = null;
1025			try
1026			{
1027				CONFIG::debug { debugCheckTextFlow(); }
1028				var rslt:SelectionState;
1029				rslt = op.redo();
1030
1031				CONFIG::debug { assert(rslt != null,"redoable operations must return a SelectionState"); }
1032				setSelectionState(rslt);
1033				if (_undoManager)
1034					_undoManager.pushUndo(op);
1035
1036
1037			}
1038			catch(e:Error)
1039			{
1040				opError = e;
1041			}
1042
1043			// tell user its complete and give them a chance to cancel the rethrow
1044			if (!_imeSession)
1045			{
1046				opEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_END,false,true,redoPsuedoOp,0,opError);
1047				textFlow.dispatchEvent(opEvent);
1048				opError = opEvent.isDefaultPrevented() ? null : opEvent.error;
1049			}
1050			if (opError)
1051				throw (opError);
1052
1053			handleUpdate();
1054
1055			// push the generation of the textFlow backwards - must be done after update which does a normalize
1056			// set the generation of the textFlow to end of redoOp.
1057			textFlow.setGeneration(op.endGeneration);
1058
1059			if (hasSelection())
1060			{
1061				var controllerIndex:int = textFlow.flowComposer.findControllerIndexAtPosition(activePosition);
1062				if (controllerIndex >= 0)
1063					textFlow.flowComposer.getControllerAt(controllerIndex).scrollToRange(activePosition,anchorPosition);
1064			}
1065			if (!_imeSession)
1066			{
1067				opEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_COMPLETE,false,false,redoPsuedoOp,0,null);
1068				textFlow.dispatchEvent(opEvent);
1069			}
1070		}
1071
1072		/**
1073		 * @private
1074		 * Returns the editing mode (READ_ONLY, READ_SELECT, or READ_WRITE) of the EditManager.
1075		 * @playerversion Flash 10
1076		 * @playerversion AIR 1.5
1077 	 	 * @langversion 3.0
1078		 * @see flashx.textLayout.edit.EditingMode.
1079		 */
1080		 public override function get editingMode():String
1081		 {
1082		 	return EditingMode.READ_WRITE;
1083		 }
1084
1085		// Resolve the operationState.
1086		//		If the operation state is null...
1087		//			Return the active selection
1088		//			If there's no active selection, return null. The caller will have to check
1089		//		Otherwise (operation not null)
1090		//			just return it
1091		/** @private */
1092		tlf_internal function defaultOperationState(operationState:SelectionState = null):SelectionState
1093		{
1094			if (operationState)
1095			{
1096				// flush any pending operations and use marks to preserve the operationState positions
1097				var markActive:Mark = createMark();
1098				var markAnchor:Mark = createMark();
1099				try
1100				{
1101					markActive.position = operationState.activePosition;
1102					markAnchor.position = operationState.anchorPosition;
1103					flushPendingOperations();
1104				}
1105				finally
1106				{
1107					removeMark(markActive);
1108					removeMark(markAnchor);
1109					operationState.activePosition = markActive.position;
1110					operationState.anchorPosition = markAnchor.position;
1111				}
1112			}
1113			else
1114			{
1115				flushPendingOperations();
1116				if (hasSelection())
1117				{
1118					// tell the operation that the state is from the SelectionManager so it will update pending point formats
1119					operationState = getSelectionState();
1120					operationState.selectionManagerOperationState = true;
1121				}
1122			}
1123			return operationState;
1124		}
1125
1126		/**
1127		 * @copy IEditManager#splitParagraph()
1128		 * @includeExample examples\EditManager_splitParagraph.as -noswf
1129		 *
1130		 * @playerversion Flash 10
1131		 * @playerversion AIR 1.5
1132 	 	 * @langversion 3.0
1133		 */
1134		public function splitParagraph(operationState:SelectionState = null):ParagraphElement
1135		{
1136			operationState = defaultOperationState(operationState);
1137			if (!operationState)
1138				return null;
1139
1140			var op:SplitElementOperation = new SplitParagraphOperation(operationState);
1141			doOperation(op);
1142			return op.newElement as ParagraphElement;
1143		}
1144
1145
1146		/** @copy IEditManager#splitElement() */
1147		public function splitElement(target:FlowGroupElement, operationState:SelectionState = null):FlowGroupElement
1148		{
1149			operationState = defaultOperationState(operationState);
1150			if (!operationState)
1151				return null;
1152			var op:SplitElementOperation = new SplitElementOperation(operationState, target);
1153			doOperation(op);
1154			return op.newElement;
1155		}
1156
1157		/** @copy IEditManager#createDiv() */
1158		public function createDiv(parent:FlowGroupElement = null, format:ITextLayoutFormat = null, operationState:SelectionState = null):DivElement
1159		{
1160			operationState = defaultOperationState(operationState);
1161			if (!operationState)
1162				return null;
1163
1164			var operation:CreateDivOperation = new CreateDivOperation(operationState, parent, format);
1165			doOperation(operation);
1166			return operation.newDivElement;
1167		}
1168
1169		/** @copy IEditManager#createList() */
1170		public function createList(parent:FlowGroupElement = null, format:ITextLayoutFormat = null, operationState:SelectionState = null):ListElement
1171		{
1172			operationState = defaultOperationState(operationState);
1173			if (!operationState)
1174				return null;
1175
1176			var operation:CreateListOperation = new CreateListOperation(operationState, parent, format);
1177			doOperation(operation);
1178			return operation.newListElement;
1179		}
1180
1181		/** @copy IEditManager#moveChildren() */
1182		public function moveChildren(source:FlowGroupElement, sourceIndex:int, numChildren:int, destination:FlowGroupElement, destinationIndex:int, selectionState:SelectionState = null):void
1183		{
1184			selectionState = defaultOperationState(selectionState);
1185			if (!selectionState)
1186				return;
1187
1188			var operation:MoveChildrenOperation = new MoveChildrenOperation(selectionState, source, sourceIndex, numChildren, destination, destinationIndex);
1189			doOperation(operation);
1190		}
1191
1192		/** @copy IEditManager#createSubParagraphGroup() */
1193		public function createSubParagraphGroup(parent:FlowGroupElement = null, format:ITextLayoutFormat = null, operationState:SelectionState = null):SubParagraphGroupElement
1194		{
1195			operationState = defaultOperationState(operationState);
1196			if (!operationState)
1197				return null;
1198
1199			var operation:CreateSubParagraphGroupOperation = new CreateSubParagraphGroupOperation(operationState, parent, format);
1200			doOperation(operation);
1201			return operation.newSubParagraphGroupElement;
1202		}
1203
1204		/**
1205		 * @copy IEditManager#deleteText()
1206		 * @includeExample examples\EditManager_deleteText.as -noswf
1207		 *
1208		 * @playerversion Flash 10
1209		 * @playerversion AIR 1.5
1210 	 	 * @langversion 3.0
1211		 */
1212		public function deleteText(operationState:SelectionState = null):void
1213		{
1214
1215			operationState = defaultOperationState(operationState);
1216			if (!operationState)
1217				return;
1218
1219			doOperation(new DeleteTextOperation(operationState, operationState, false /* don't allow merge when deleting by range */));
1220		}
1221
1222		/**
1223		 * @copy IEditManager#deleteNextCharacter()
1224		 * @includeExample examples\EditManager_deleteNextCharacter.as -noswf
1225		 *
1226		 * @playerversion Flash 10
1227		 * @playerversion AIR 1.5
1228 	 	 * @langversion 3.0
1229		 */
1230		public function deleteNextCharacter(operationState:SelectionState = null):void
1231		{
1232			operationState = defaultOperationState(operationState);
1233			if (!operationState)
1234				return;
1235
1236			// Delete the next character if it's a caret selection, and allow adejacent delete next's to merge
1237			// If it's a range selection, delete the range and disallow merge
1238			var deleteOp:DeleteTextOperation;
1239			if (operationState.absoluteStart == operationState.absoluteEnd)
1240			{
1241				var nextPosition:int = NavigationUtil.nextAtomPosition(textFlow, absoluteStart);
1242				deleteOp = new DeleteTextOperation(operationState, new SelectionState(textFlow, absoluteStart, nextPosition, pointFormat), true /* allowMerge for deleteForward */);
1243			}
1244			else
1245				deleteOp = new DeleteTextOperation(operationState, operationState, false /* don't allow merge when deleting by range */);
1246			doOperation(deleteOp);
1247
1248		}
1249
1250		/**
1251		 * @copy IEditManager#deleteNextWord()
1252		 * @includeExample examples\EditManager_deleteNextWord.as -noswf
1253		 *
1254		 * @playerversion Flash 10
1255		 * @playerversion AIR 1.5
1256 	 	 * @langversion 3.0
1257		 */
1258		public function deleteNextWord(operationState:SelectionState = null):void
1259		{
1260			operationState = defaultOperationState(operationState);
1261			if ((!operationState) || ((operationState.anchorPosition == operationState.activePosition) && (operationState.anchorPosition >= textFlow.textLength - 1)))
1262				return;
1263
1264			var nextWordSelState:SelectionState = getNextWordForDelete(operationState.absoluteStart);
1265			if (nextWordSelState.anchorPosition == nextWordSelState.activePosition)
1266				//nothing to delete. No operation required.
1267				return;
1268
1269			setSelectionState(new SelectionState(textFlow, operationState.absoluteStart, operationState.absoluteStart, new TextLayoutFormat(textFlow.findLeaf(operationState.absoluteStart).format)));
1270			doOperation(new DeleteTextOperation(operationState, nextWordSelState, false));
1271		}
1272
1273		// Sadly, this is NOT the same as the cursor key movement - specialized for delete forward one word
1274		private function getNextWordForDelete(absoluteStart:int):SelectionState
1275		{
1276			var leafEl:FlowLeafElement = textFlow.findLeaf(absoluteStart);
1277			var paraEl:ParagraphElement = leafEl.getParagraph();
1278			var paraElAbsStart:int = paraEl.getAbsoluteStart();
1279
1280			var nextPosition:int = -1;
1281
1282			if ((absoluteStart - paraElAbsStart) >= (paraEl.textLength - 1))
1283			{
1284				// We're at the end of the paragraph, delete the following newline
1285				nextPosition = NavigationUtil.nextAtomPosition(textFlow, absoluteStart);
1286			}
1287			else
1288			{
1289				var curPos:int = absoluteStart - paraElAbsStart;
1290				var curPosCharCode:int = paraEl.getCharCodeAtPosition(curPos);
1291				var prevPosCharCode:int = -1;
1292				if (curPos > 0) prevPosCharCode = paraEl.getCharCodeAtPosition(curPos - 1);
1293				var nextPosCharCode:int = paraEl.getCharCodeAtPosition(curPos + 1);
1294				if (!CharacterUtil.isWhitespace(curPosCharCode) && ((curPos == 0) || ((curPos > 0) && CharacterUtil.isWhitespace(prevPosCharCode)))) {
1295					nextPosition = NavigationUtil.nextWordPosition(textFlow, absoluteStart);
1296				} else {
1297					if (CharacterUtil.isWhitespace(curPosCharCode) && ((curPos > 0) && !CharacterUtil.isWhitespace(prevPosCharCode))) {
1298						//if at beginning of space word then get through all the spaces
1299						curPos = paraEl.findNextWordBoundary(curPos);
1300					}
1301					nextPosition = paraElAbsStart + paraEl.findNextWordBoundary(curPos);
1302				}
1303			}
1304			return new SelectionState(textFlow, absoluteStart, nextPosition, pointFormat);
1305		}
1306
1307		/**
1308		 * @copy IEditManager#deletePreviousCharacter()
1309		 * @includeExample examples\EditManager_deletePreviousCharacter.as -noswf
1310		 *
1311		 * @playerversion Flash 10
1312		 * @playerversion AIR 1.5
1313 	 	 * @langversion 3.0
1314		 */
1315		public function deletePreviousCharacter(operationState:SelectionState = null):void
1316		{
1317			operationState = defaultOperationState(operationState);
1318			if (!operationState)
1319				return;
1320
1321			var deleteOp:DeleteTextOperation;
1322			if (operationState.absoluteStart == operationState.absoluteEnd)
1323			{
1324				// with a caret selection, generally delete the previous character, but also check whether to move the paragraph out of its parent chain (like backspacing at the beginning of a list)
1325				var leaf:FlowLeafElement = textFlow.findLeaf(operationState.absoluteStart);
1326				var para:ParagraphElement = leaf.getParagraph();
1327				var parent:FlowGroupElement = para.parent;
1328
1329				var movePara:Boolean = false;
1330				if(!(parent is TextFlow))
1331				{
1332					if(operationState.absoluteStart == para.getAbsoluteStart() && parent.getChildIndex(para) == 0 && // cursor is at start of this paragraph AND para is at beginning of parent AND
1333						(!(parent is ListItemElement) || parent.parent.getChildIndex(parent) == 0)) // if parent is a listItem, it's the first item in the list
1334					{
1335						movePara = true;
1336					}
1337				}
1338				if(movePara)
1339				{
1340					var source:FlowGroupElement;
1341					var target:FlowGroupElement;
1342					var numElementsToMove:int;
1343					var targetIndex:int;
1344
1345					if(parent is ListItemElement)
1346					{
1347						if(parent.parent.parent is ListElement)
1348						{
1349							// move the whole list item to grandparent list
1350							source = parent.parent;
1351							numElementsToMove = 1;
1352							target = parent.parent.parent;
1353							targetIndex = parent.parent.parent.getChildIndex(parent.parent);
1354						}
1355						else
1356						{
1357							// move everything inside the list item out into grandparent
1358							source = para.parent;
1359							numElementsToMove = para.parent.numChildren;
1360							target = parent.parent.parent;
1361							targetIndex = parent.parent.parent.getChildIndex(parent.parent);
1362						}
1363					}
1364					else
1365					{
1366						// move just the first paragraph out into grandparent
1367						source = para.parent;
1368						numElementsToMove = 1;
1369						target = parent.parent;
1370						targetIndex = parent.parent.getChildIndex(parent);
1371					}
1372					doOperation(new MoveChildrenOperation(operationState, source, 0, numElementsToMove, target, targetIndex));
1373				}
1374				else
1375				{
1376					var beginPrevious:int = NavigationUtil.previousAtomPosition(textFlow, operationState.absoluteStart);
1377					deleteOp = new DeleteTextOperation(operationState, new SelectionState(textFlow, beginPrevious, operationState.absoluteStart), true /* allowMerge */);
1378					doOperation(deleteOp);
1379				}
1380			}
1381			else	// just delete the range
1382			{
1383				deleteOp = new DeleteTextOperation(operationState);
1384				doOperation(deleteOp);
1385			}
1386		}
1387
1388		/**
1389		 * @copy IEditManager#deletePreviousWord()
1390		 * @includeExample examples\EditManager_deletePreviousWord.as -noswf
1391		 *
1392		 * @playerversion Flash 10
1393		 * @playerversion AIR 1.5
1394 	 	 * @langversion 3.0
1395		 */
1396		public function deletePreviousWord(operationState:SelectionState = null):void
1397		{
1398			operationState = defaultOperationState(operationState);
1399			if (!operationState)
1400				return;
1401
1402			var prevWordSelState:SelectionState = getPreviousWordForDelete(operationState.absoluteStart);
1403			if (prevWordSelState.anchorPosition == prevWordSelState.activePosition)
1404				//there is nothing to delete.  No operation required
1405				return;
1406
1407			setSelectionState(new SelectionState(textFlow, operationState.absoluteStart, operationState.absoluteStart, new TextLayoutFormat(textFlow.findLeaf(operationState.absoluteStart).format)));
1408			doOperation(new DeleteTextOperation(operationState, prevWordSelState, false /* don't allow merge */));
1409		}
1410
1411		// Sadly, this is NOT the same as the cursor key movement - specialized for delete backward one word
1412		private function getPreviousWordForDelete(absoluteStart:int):SelectionState
1413		{
1414			var leafEl:FlowLeafElement = textFlow.findLeaf(absoluteStart);
1415			var paraEl:ParagraphElement = leafEl.getParagraph();
1416			var paraElAbsStart:int = paraEl.getAbsoluteStart();
1417
1418			if (absoluteStart == paraElAbsStart)		// at the start of the paragraph, delete the previous newline. Should insert a space after punctuation.
1419			{
1420				var beginPrevious:int = NavigationUtil.previousAtomPosition(textFlow, absoluteStart);
1421				return new SelectionState(textFlow, beginPrevious, absoluteStart);
1422			}
1423
1424			var curPos:int = absoluteStart - paraElAbsStart;
1425			var curPosCharCode:int = paraEl.getCharCodeAtPosition(curPos);
1426			var prevPosCharCode:int = paraEl.getCharCodeAtPosition(curPos - 1);
1427			var curAbsStart:int = absoluteStart;
1428
1429			if (CharacterUtil.isWhitespace(curPosCharCode) && (curPos != (paraEl.textLength - 1)))
1430			{
1431				if (CharacterUtil.isWhitespace(prevPosCharCode)) //this will get you past the spaces
1432				{
1433					curPos = paraEl.findPreviousWordBoundary(curPos);
1434				}
1435				if (curPos > 0) {
1436					curPos = paraEl.findPreviousWordBoundary(curPos); //this will get you to the beginning of the word before the space.
1437					if (curPos > 0) {
1438						prevPosCharCode = paraEl.getCharCodeAtPosition(curPos - 1);
1439						if (CharacterUtil.isWhitespace(prevPosCharCode)) {
1440							curPos = paraEl.findPreviousWordBoundary(curPos);
1441						}
1442					}
1443				}
1444			} else { //you are here if you are not on a space
1445				if (CharacterUtil.isWhitespace(prevPosCharCode))
1446				{
1447					curPos = paraEl.findPreviousWordBoundary(curPos); //this will get you past the spaces
1448					if (curPos > 0) {
1449						curPos = paraEl.findPreviousWordBoundary(curPos);
1450						if (curPos > 0) {
1451							prevPosCharCode = paraEl.getCharCodeAtPosition(curPos - 1);
1452							if (!CharacterUtil.isWhitespace(prevPosCharCode)) {
1453								curAbsStart--; //Microsoft Word insists on keeping the original space
1454								               //if the ending position does not have a space.
1455							}
1456						}
1457					}
1458				} else { //just delete to the previous word boundary
1459					curPos = paraEl.findPreviousWordBoundary(curPos);
1460				}
1461			}
1462			return new SelectionState(textFlow, paraElAbsStart + curPos, curAbsStart);
1463		}
1464
1465		/**
1466		 * @copy IEditManager#insertText()
1467		 * @includeExample examples\EditManager_insertText.as -noswf
1468		 *
1469		 * @playerversion Flash 10
1470		 * @playerversion AIR 1.5
1471 	 	 * @langversion 3.0
1472		 */
1473		public function insertText(text:String, origOperationState:SelectionState = null):void
1474		{
1475			// if there's another insert operation waiting to be executed,
1476			// just add to it, if possible
1477			if (origOperationState == null && pendingInsert)
1478				pendingInsert.text += text;
1479			else
1480			{
1481				var operationState:SelectionState = defaultOperationState(origOperationState);
1482				if (!operationState)
1483					return;
1484
1485				// rather than execute the insert immediately, create
1486				// it and wait for the next frame, in order to batch
1487				// keystrokes.
1488				pendingInsert = new InsertTextOperation(operationState, text);
1489
1490				var controller:ContainerController = textFlow.flowComposer.getControllerAt(0);
1491				if (captureLevel == 0 && origOperationState == null && controller && controller.container && allowDelayedOperations)
1492				{
1493					enterFrameListener = controller.container;
1494					enterFrameListener.addEventListener(Event.ENTER_FRAME, enterFrameHandler, false, 1.0, true);
1495				}
1496				else
1497					flushPendingOperations();
1498			}
1499		}
1500
1501
1502
1503		/**
1504		 * @copy IEditManager#overwriteText()
1505		 *
1506		 * @includeExample examples\EditManager_overwriteText.as -noswf
1507		 *
1508		 * @playerversion Flash 10
1509		 * @playerversion AIR 1.5
1510 	 	 * @langversion 3.0
1511		 */
1512		public function overwriteText(text:String, operationState:SelectionState = null):void
1513		{
1514			operationState = defaultOperationState(operationState);
1515			if (!operationState)
1516				return;
1517			var selState:SelectionState = getSelectionState();
1518			NavigationUtil.nextCharacter(selState,true);
1519			doOperation(new InsertTextOperation(operationState, text, selState));
1520		}
1521
1522		/**
1523		 * @copy IEditManager#insertInlineGraphic()
1524		 * Returns the new InlineGraphicElement that was created.
1525		 * @includeExample examples\EditManager_insertInlineGraphic.as -noswf
1526		 *
1527		 * @playerversion Flash 10 + 10.2
1528		 * @playerversion AIR 1.5
1529 	 	 * @langversion 3.0
1530		 * @see flash.text.engine.TextRotation
1531		 */
1532		public function insertInlineGraphic(source:Object, width:Object, height:Object, options:Object = null, operationState:SelectionState = null):InlineGraphicElement
1533		{
1534			operationState = defaultOperationState(operationState);
1535			if (!operationState)
1536				return null;
1537
1538			var operation:InsertInlineGraphicOperation = new InsertInlineGraphicOperation(operationState, source, width, height, options);
1539			doOperation(operation);
1540			return operation.newInlineGraphicElement;
1541		}
1542
1543		/**
1544		 * @copy IEditManager#modifyInlineGraphic()
1545		 * @includeExample examples\EditManager_modifyInlineGraphic.as -noswf
1546		 *
1547		 * @playerversion Flash 10
1548		 * @playerversion AIR 1.5
1549 	 	 * @langversion 3.0
1550		 */
1551		public function modifyInlineGraphic(source:Object, width:Object, height:Object, options:Object = null, operationState:SelectionState = null):void
1552		{
1553			operationState = defaultOperationState(operationState);
1554			if (!operationState)
1555				return;
1556
1557			doOperation(new ModifyInlineGraphicOperation(operationState, source, width, height, options));
1558		}
1559
1560		/**
1561		 * @copy IEditManager#applyFormat()
1562		 *
1563		 * @includeExample examples\EditManager_applyFormat.as -noswf
1564		 * @playerversion Flash 10
1565		 * @playerversion AIR 1.5
1566 	 	 * @langversion 3.0
1567		 */
1568		public function applyFormat(leafFormat:ITextLayoutFormat, paragraphFormat:ITextLayoutFormat, containerFormat:ITextLayoutFormat, operationState:SelectionState = null):void
1569		{
1570			operationState = defaultOperationState(operationState);
1571			if (!operationState)
1572				return;
1573
1574			// apply to the current selection else remember new format for next char typed
1575			doOperation(new ApplyFormatOperation(operationState, leafFormat, paragraphFormat, containerFormat));
1576		}
1577		/**
1578		 * @copy IEditManager#clearFormat()
1579		 *
1580		 * Known issue is that undefines of leafFormat values with a point selection are not applied at the next insertion.
1581		 *
1582		 * @playerversion Flash 10
1583		 * @playerversion AIR 1.5
1584		 * @langversion 3.0
1585		 */
1586		public function clearFormat(leafFormat:ITextLayoutFormat, paragraphFormat:ITextLayoutFormat, containerFormat:ITextLayoutFormat, operationState:SelectionState = null):void
1587		{
1588			operationState = defaultOperationState(operationState);
1589			if (!operationState)
1590				return;
1591
1592			// apply to the current selection else remember new format for next char typed
1593			doOperation(new ClearFormatOperation(operationState, leafFormat, paragraphFormat, containerFormat));
1594		}
1595		/**
1596		 * @copy IEditManager#applyLeafFormat()
1597		 *
1598		 * @includeExample examples\EditManager_applyLeafFormat.as -noswf
1599		 *
1600		 * @playerversion Flash 10
1601		 * @playerversion AIR 1.5
1602 	 	 * @langversion 3.0
1603		 */
1604		public function applyLeafFormat(characterFormat:ITextLayoutFormat, operationState:SelectionState = null):void
1605		{
1606			applyFormat(characterFormat, null, null, operationState);
1607		}
1608
1609		/**
1610		 * @copy IEditManager#applyParagraphFormat()
1611		 *
1612		 * @includeExample examples\EditManager_applyParagraphFormat.as -noswf
1613		 *
1614		 * @playerversion Flash 10
1615		 * @playerversion AIR 1.5
1616 	 	 * @langversion 3.0
1617 		 */
1618		public function applyParagraphFormat(paragraphFormat:ITextLayoutFormat, operationState:SelectionState = null):void
1619		{
1620			applyFormat(null, paragraphFormat, null, operationState);
1621		}
1622
1623		/**
1624		 * @copy IEditManager#applyContainerFormat()
1625		 *
1626		 * @includeExample examples\EditManager_applyContainerFormat.as -noswf
1627		 *
1628		 * @playerversion Flash 10
1629		 * @playerversion AIR 1.5
1630 	 	 * @langversion 3.0
1631		 */
1632		public function applyContainerFormat(containerFormat:ITextLayoutFormat, operationState:SelectionState = null):void
1633		{
1634			applyFormat(null, null, containerFormat, operationState);
1635		}
1636
1637		/**
1638		 * @copy IEditManager#applyFormatToElement()
1639		 *
1640		 * @playerversion Flash 10
1641		 * @playerversion AIR 1.5
1642 	 	 * @langversion 3.0
1643		 */
1644		public function applyFormatToElement(targetElement:FlowElement, format:ITextLayoutFormat, relativeStart:int = 0, relativeEnd:int = -1, operationState:SelectionState = null):void
1645		{
1646			operationState = defaultOperationState(operationState);
1647			if (!operationState)
1648				return;
1649
1650			doOperation(new ApplyFormatToElementOperation(operationState, targetElement, format, relativeStart, relativeEnd));
1651		}
1652
1653		/**
1654		 * @copy IEditManager#clearFormatOnElement()
1655		 *
1656		 * @playerversion Flash 10
1657		 * @playerversion AIR 1.5
1658		 * @langversion 3.0
1659		 */
1660		public function clearFormatOnElement(targetElement:FlowElement, format:ITextLayoutFormat, operationState:SelectionState = null):void
1661		{
1662			operationState = defaultOperationState(operationState);
1663			if (!operationState)
1664				return;
1665
1666			doOperation(new ClearFormatOnElementOperation(operationState, targetElement, format));
1667		}
1668
1669		/**
1670		 * @copy IEditManager#cutTextScrap()
1671		 * @includeExample examples\EditManager_cutTextScrap.as -noswf
1672		 *
1673		 * @playerversion Flash 10
1674		 * @playerversion AIR 1.5
1675 	 	 * @langversion 3.0
1676 	 	 *
1677		 *  @see flashx.textLayout.edit.TextScrap
1678		 */
1679		public function cutTextScrap(operationState:SelectionState = null):TextScrap
1680		{
1681			operationState = defaultOperationState(operationState);
1682			if (!operationState)
1683				return null;
1684
1685			if (operationState.anchorPosition == operationState.activePosition)
1686				return null;
1687
1688			var tScrap:TextScrap = TextScrap.createTextScrap(operationState);
1689			var beforeOpLen:int = textFlow.textLength;
1690			doOperation(new CutOperation(operationState, tScrap));
1691			if (operationState.textFlow.textLength != beforeOpLen)
1692			{
1693				return tScrap;
1694			}
1695			return null;
1696		}
1697
1698		/**
1699		 * @copy IEditManager#pasteTextScrap()
1700		 * @includeExample examples\EditManager_pasteTextScrap.as -noswf
1701		 *
1702		 * @playerversion Flash 10
1703		 * @playerversion AIR 1.5
1704 	 	 * @langversion 3.0
1705 	 	 *
1706		 *  @see flashx.textLayout.edit.TextScrap
1707		 */
1708		public function pasteTextScrap(scrapToPaste:TextScrap, operationState:SelectionState = null):void
1709		{
1710			operationState = defaultOperationState(operationState);
1711			if (!operationState)
1712				return;
1713
1714			doOperation(new PasteOperation(operationState, scrapToPaste));
1715		}
1716
1717		/**
1718		 * @copy IEditManager#applyTCY()
1719		 * Returns the new TCYElement that was created.
1720		 * @includeExample examples\EditManager_applyTCY.as -noswf
1721		 *
1722		 * @playerversion Flash 10 + 10.2
1723		 * @playerversion AIR 1.5
1724 	 	 * @langversion 3.0
1725		 */
1726		public function applyTCY(tcyOn:Boolean, operationState:SelectionState = null):TCYElement
1727		{
1728			operationState = defaultOperationState(operationState);
1729			if (!operationState)
1730				return null;
1731
1732			var operation:ApplyTCYOperation = new ApplyTCYOperation(operationState, tcyOn);
1733			doOperation(operation);
1734			return operation.newTCYElement;
1735		}
1736
1737		/**
1738		 * @copy IEditManager#applyLink()
1739		 * Returns the new LinkElement that was created.
1740		 * @includeExample examples\EditManager_applyLink.as -noswf
1741		 *
1742		 * @playerversion Flash 10 + 10.2
1743		 * @playerversion AIR 1.5
1744 	 	 * @langversion 3.0
1745		 */
1746		public function applyLink(href:String, targetString:String = null, extendToLinkBoundary:Boolean=false, operationState:SelectionState = null):LinkElement
1747		{
1748			operationState = defaultOperationState(operationState);
1749			if (!operationState)
1750				return null;
1751
1752			if (operationState.absoluteStart == operationState.absoluteEnd)
1753				return null;
1754
1755			var operation:ApplyLinkOperation = new ApplyLinkOperation(operationState, href, targetString, extendToLinkBoundary);
1756			doOperation(operation);
1757			return operation.newLinkElement;
1758		}
1759
1760		/**
1761		 * @copy IEditManager#changeElementID()
1762		 * @includeExample examples\EditManager_changeElementID.as -noswf
1763		 *
1764		 * @playerversion Flash 10
1765		 * @playerversion AIR 1.5
1766	 	 * @langversion 3.0
1767	 	*/
1768		public function changeElementID(newID:String, targetElement:FlowElement, relativeStart:int = 0, relativeEnd:int = -1, operationState:SelectionState = null):void
1769		{
1770			operationState = defaultOperationState(operationState);
1771			if (!operationState)
1772				return;
1773
1774			if (operationState.absoluteStart == operationState.absoluteEnd)
1775				return;
1776
1777			doOperation(new ApplyElementIDOperation(operationState, targetElement, newID, relativeStart, relativeEnd));
1778		}
1779
1780		[Deprecated(replacement="applyFormatToElement", deprecatedSince="2.0")]
1781		/**
1782		 * @copy IEditManager#changeStyleName()
1783		 * @includeExample examples\EditManager_changeStyleName.as -noswf
1784		 *
1785		 * @playerversion Flash 10
1786		 * @playerversion AIR 1.5
1787		 * @langversion 3.0
1788		 */
1789		public function changeStyleName(newName:String, targetElement:FlowElement, relativeStart:int = 0, relativeEnd:int = -1, operationState:SelectionState = null):void
1790		{
1791			operationState = defaultOperationState(operationState);
1792			if (!operationState)
1793				return;
1794
1795			var format:TextLayoutFormat = new TextLayoutFormat();
1796			format.styleName = newName;
1797			doOperation(new ApplyFormatToElementOperation(operationState, targetElement, format, relativeStart, relativeEnd));
1798		}
1799
1800		/**
1801		 * @copy IEditManager#changeTypeName()
1802		 *
1803		 * @playerversion Flash 10
1804		 * @playerversion AIR 1.5
1805		 * @langversion 3.0
1806		 */
1807		public function changeTypeName(newName:String, targetElement:FlowElement, relativeStart:int = 0, relativeEnd:int = -1, operationState:SelectionState = null):void
1808		{
1809			operationState = defaultOperationState(operationState);
1810			if (!operationState)
1811				return;
1812
1813			doOperation(new ApplyElementTypeNameOperation(operationState, targetElement, newName, relativeStart, relativeEnd));
1814		}
1815
1816		/* CompositeOperations
1817			Normally when you call doOperation, it gets executed immediately. By calling beginCompositeOperation, you can instead accumulate the
1818			operations into a CompositeOperation. The CompositeOperation is completed and returned when you call endCompositeOperation, and
1819			processing returns to normal state. The client code can then either call doOperation on the CompositeOperation that was returned,
1820			or just drop it if the operation should be aborted.
1821
1822			The parentStack is a stack of pending CompositeOperations.
1823		*/
1824		private var parentStack:Array;
1825
1826		/**
1827		 * @copy IEditManager#beginCompositeOperation()
1828		 *
1829		 * @playerversion Flash 10
1830		 * @playerversion AIR 1.5
1831 	 	 * @langversion 3.0
1832 	 	 *
1833		 * @includeExample examples\EditManager_beginCompositeOperation.as -noswf
1834		 */
1835		public function beginCompositeOperation():void
1836		{
1837			flushPendingOperations();
1838
1839			if (!parentStack)
1840				parentStack = [];
1841			var operation:CompositeOperation = new CompositeOperation();
1842
1843			if (!_imeSession)
1844			{
1845				var opEvent:FlowOperationEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_BEGIN,false,false,operation,captureLevel,null);
1846				textFlow.dispatchEvent(opEvent);
1847			}
1848
1849			CONFIG::debug { assert(!operation.operations  || operation.operations.length == 0, "opening a composite operation that already has operations"); }
1850			operation.setGenerations(textFlow.generation, 0);
1851			++captureLevel;
1852			var parent:Object = new Object();
1853			parent.operation = operation;
1854			parent.captureLevel = captureLevel;
1855			parentStack.push(parent);
1856		}
1857
1858		/**
1859		 * @copy IEditManager#endCompositeOperation()
1860		 *
1861		 * @playerversion Flash 10
1862		 * @playerversion AIR 1.5
1863 	 	 * @langversion 3.0
1864 	 	 *
1865		 * @includeExample examples\EditManager_beginCompositeOperation.as -noswf
1866		 */
1867		public function endCompositeOperation():void
1868		{
1869			CONFIG::debug { assert( parentStack.length > 0 || captureLevel <= 0, "EditManager.endOperation - no composite operation in progress"); }
1870
1871			--captureLevel;
1872
1873			var parent:Object = parentStack.pop();
1874			var operation:FlowOperation = parent.operation;
1875			if (!_imeSession)
1876			{
1877				var opEvent:FlowOperationEvent = new FlowOperationEvent(FlowOperationEvent.FLOW_OPERATION_END,false,false,operation,captureLevel,null);
1878				textFlow.dispatchEvent(opEvent);
1879			}
1880			operation.setGenerations(operation.beginGeneration, textFlow.generation);
1881			finalizeDo(operation);
1882		}
1883
1884		/** @private
1885		 * Handler function called when the selection has been changed.
1886		 * @playerversion Flash 10
1887		 * @playerversion AIR 1.5
1888 	 	 * @langversion 3.0
1889		 * @param doDispatchEvent	true if a selection changed event will be sent
1890		 * @param resetPointFormat	true if the attributes associated with the caret should be discarded
1891		 */
1892		tlf_internal override function selectionChanged(doDispatchEvent:Boolean = true, resetPointFormat:Boolean=true):void
1893		{
1894			if (_imeSession)
1895				_imeSession.selectionChanged();
1896
1897			super.selectionChanged(doDispatchEvent, resetPointFormat);
1898		}
1899
1900
1901	}
1902}
1903