修正版UndoableTextArea

以前のバージョン(http://d.hatena.ne.jp/masatoshisw20/20080529/1212020624)では、Redo用のカーソル位置情報をUndo時に使っていたので不具合がありました。その修正版を公開します。
元々、javaのStateEditクラスを参考にしているのですが、Undo/Redo用にテキストデータを二重に持つのをやめた分、トリッキーになっています。自分でUndo/Redoの実装を作りたい場合には、StateEditクラスとその使い方を参考にするのをお勧めします。

<?xml version="1.0" encoding="utf-8"?>
<mx:TextArea xmlns:mx="http://www.adobe.com/2006/mxml"
	keyDown="keyDown(event)"
	change="change(event)"
	creationComplete="init()">
	
	<mx:Script>
		<![CDATA[
			import mx.collections.ArrayCollection;
			
			private var edits:ArrayCollection;
			private var index:int = -1;
			private var latestCursorIndex:int;

			private function init():void {
				enableUndo(editable);
				this.addEventListener("editableChanged", editableChanged);
			}	
			
			private function editableChanged(ev:Event):void {
				enableUndo(editable);
			}
			
			private function enableUndo(value:Boolean):void {
				if (value) {
					edits = new ArrayCollection();
					index = -1;
					addEdit();
				} else {
					if (edits != null) {
						edits.removeAll();
						edits = null;
					}
				}
			}
			
			private function addEdit():void {
				cleanUselessEdits(edits.length);
				edits.addItem({str:text,
								preCursorIndex:latestCursorIndex,
								postCursorIndex:selectionEndIndex});
				index = edits.length - 1;
			}
	
			private function cleanUselessEdits(length:int):void {
				for (var i:int = length - 1; i > index; i--) {
					edits.removeItemAt(edits.length - 1);
				}
			}
			
			public function undo():void {
				if (!canUndo()) {
					return;
				}
				var cursorIndex:int = edits.getItemAt(index).preCursorIndex;
				setSelection(cursorIndex, cursorIndex);
				text = edits.getItemAt(--index).str;
			}
	
			public function redo():void {
				if (!canRedo()) {
					return;
				}
				var cursorIndex:int = edits.getItemAt(++index).postCursorIndex;
				setSelection(cursorIndex, cursorIndex);
				text = edits.getItemAt(index).str;
			}

			public function canUndo():Boolean {
				if (index < 1) {
					return false;
				} else {
					return true;
				}
			}
	
			public function canRedo():Boolean {
				if (index == edits.length - 1) {
					return false;
				} else {
					return true;
				}
			}
			
			public function clearUndoHistory():void {
				edits.removeAll();
				index = -1;
			}

			private function keyDown(ev:KeyboardEvent):void {
				if (ev.controlKey) {
					if (ev.keyCode == Keyboard.Z) {
						undo();
						ev.preventDefault();
					} else if (ev.keyCode == Keyboard.Y) {
						redo();
						ev.preventDefault();
					}
				} else {
					latestCursorIndex = selectionEndIndex;
				}
			}
			
			private function change(ev:Event):void {
				addEdit();
			}
			
		]]>
	</mx:Script>
	
</mx:TextArea>