// // // // // $Revision$ // using System; using System.Collections.Generic; using System.Drawing; namespace ICSharpCode.TextEditor.Undo { /// /// This class implements an undo stack /// public class UndoStack { Stack undostack = new Stack(); Stack redostack = new Stack(); public TextEditorControlBase TextEditorControl = null; /// /// public event EventHandler ActionUndone; /// /// public event EventHandler ActionRedone; public event OperationEventHandler OperationPushed; /// /// Gets/Sets if changes to the document are protocolled by the undo stack. /// Used internally to disable the undo stack temporarily while undoing an action. /// internal bool AcceptChanges = true; /// /// Gets if there are actions on the undo stack. /// public bool CanUndo { get { return undostack.Count > 0; } } /// /// Gets if there are actions on the redo stack. /// public bool CanRedo { get { return redostack.Count > 0; } } /// /// Gets the number of actions on the undo stack. /// public int UndoItemCount { get { return undostack.Count; } } /// /// Gets the number of actions on the redo stack. /// public int RedoItemCount { get { return redostack.Count; } } int undoGroupDepth; int actionCountInUndoGroup; public void StartUndoGroup() { if (undoGroupDepth == 0) { actionCountInUndoGroup = 0; } undoGroupDepth++; //Util.LoggingService.Debug("Open undo group (new depth=" + undoGroupDepth + ")"); } public void EndUndoGroup() { if (undoGroupDepth == 0) throw new InvalidOperationException("There are no open undo groups"); undoGroupDepth--; //Util.LoggingService.Debug("Close undo group (new depth=" + undoGroupDepth + ")"); if (undoGroupDepth == 0 && actionCountInUndoGroup > 1) { UndoQueue op = new UndoQueue(undostack, actionCountInUndoGroup); undostack.Push(op); if (OperationPushed != null) { OperationPushed(this, new OperationEventArgs(op)); } } } public void AssertNoUndoGroupOpen() { if (undoGroupDepth != 0) { undoGroupDepth = 0; throw new InvalidOperationException("No undo group should be open at this point"); } } /// /// Call this method to undo the last operation on the stack /// public void Undo() { AssertNoUndoGroupOpen(); if (undostack.Count > 0) { IUndoableOperation uedit = (IUndoableOperation)undostack.Pop(); redostack.Push(uedit); uedit.Undo(); OnActionUndone(); } } /// /// Call this method to redo the last undone operation /// public void Redo() { AssertNoUndoGroupOpen(); if (redostack.Count > 0) { IUndoableOperation uedit = (IUndoableOperation)redostack.Pop(); undostack.Push(uedit); uedit.Redo(); OnActionRedone(); } } /// /// Call this method to push an UndoableOperation on the undostack, the redostack /// will be cleared, if you use this method. /// public void Push(IUndoableOperation operation) { if (operation == null) { throw new ArgumentNullException("operation"); } if (AcceptChanges) { StartUndoGroup(); undostack.Push(operation); actionCountInUndoGroup++; if (TextEditorControl != null) { undostack.Push(new UndoableSetCaretPosition(this, TextEditorControl.ActiveTextAreaControl.Caret.Position)); actionCountInUndoGroup++; } EndUndoGroup(); ClearRedoStack(); } } /// /// Call this method, if you want to clear the redo stack /// public void ClearRedoStack() { redostack.Clear(); } /// /// Clears both the undo and redo stack. /// public void ClearAll() { AssertNoUndoGroupOpen(); undostack.Clear(); redostack.Clear(); actionCountInUndoGroup = 0; } /// /// protected void OnActionUndone() { if (ActionUndone != null) { ActionUndone(null, null); } } /// /// protected void OnActionRedone() { if (ActionRedone != null) { ActionRedone(null, null); } } class UndoableSetCaretPosition : IUndoableOperation { UndoStack stack; TextLocation pos; TextLocation redoPos; public UndoableSetCaretPosition(UndoStack stack, TextLocation pos) { this.stack = stack; this.pos = pos; } public void Undo() { redoPos = stack.TextEditorControl.ActiveTextAreaControl.Caret.Position; stack.TextEditorControl.ActiveTextAreaControl.Caret.Position = pos; stack.TextEditorControl.ActiveTextAreaControl.SelectionManager.ClearSelection(); } public void Redo() { stack.TextEditorControl.ActiveTextAreaControl.Caret.Position = redoPos; stack.TextEditorControl.ActiveTextAreaControl.SelectionManager.ClearSelection(); } } } public class OperationEventArgs : EventArgs { public OperationEventArgs(IUndoableOperation op) { this.op = op; } IUndoableOperation op; public IUndoableOperation Operation { get { return op; } } } public delegate void OperationEventHandler(object sender, OperationEventArgs e); }