修正版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>