/// <summary> /// This does two different things, depending on the MergeUndoTransactionPolicys in question. /// It either simply pushes the current transaction to the undo stack, OR it merges it with /// the most recent item in the stack. /// </summary> private void MergeOrPushToUndoStack(MockTextUndoTransaction transaction) { ITextUndoTransaction transactionAdded; TextUndoTransactionCompletionResult transactionResult; MockTextUndoTransaction utPrevious = _undoStack.Count > 0 ? _undoStack.Peek() as MockTextUndoTransaction : null; if (utPrevious != null && ProceedWithMerge(transaction, utPrevious)) { // Temporarily make utPrevious non-read-only, during merge. utPrevious.IsReadOnly = false; try { transaction.MergePolicy.PerformTransactionMerge(utPrevious, transaction); } finally { utPrevious.IsReadOnly = true; } // utPrevious is already on the undo stack, so we don't need to add it; but report // it as the added transaction in the UndoTransactionCompleted event. transactionAdded = utPrevious; transactionResult = TextUndoTransactionCompletionResult.TransactionMerged; } else { _undoStack.Push(transaction); transactionAdded = transaction; transactionResult = TextUndoTransactionCompletionResult.TransactionAdded; } RaiseUndoTransactionCompleted(transactionAdded, transactionResult); }
/// <summary> /// Creates a new transaction, nests it in the previously current transaction, and marks it current. /// If there is a redo stack, it gets cleared. /// UNDONE: should the redo-clearing happen now or when the new transaction is committed? /// </summary> /// <param name="description">A string description for the transaction.</param> /// <returns></returns> public ITextUndoTransaction CreateTransaction(string description) { description = description ?? string.Empty; // If there is a pending transaction that has already been completed, we should not be permitted // to open a new transaction, since it cannot later be added to its parent. if ((_currentTransaction != null) && (_currentTransaction.State != UndoTransactionState.Open)) { throw new InvalidOperationException("Strings.CannotCreateTransactionWhenCurrentTransactionNotOpen"); } // new transactions that are visible should clear the redo stack. if (_currentTransaction == null) { foreach (var textUndoTransaction in _redoStack) { var redoTransaction = (MockTextUndoTransaction)textUndoTransaction; redoTransaction.Invalidate(); } _redoStack.Clear(); } MockTextUndoTransaction newTransaction = new MockTextUndoTransaction(this, _currentTransaction, description); _currentTransaction = newTransaction; return(_currentTransaction); }
/// <summary> /// Copies all of the primitives from the given transaction, and appends them to the UndoPrimitives list. /// </summary> /// <param name="transaction">The MockTextUndoTransaction to copy from.</param> public void CopyPrimitivesFrom(MockTextUndoTransaction transaction) { foreach (var p in transaction.UndoPrimitives) { AddUndo(p); } }
private bool ProceedWithMerge(MockTextUndoTransaction transaction1, MockTextUndoTransaction transaction2) { MockTextUndoHistoryRegistry registry = UndoHistoryRegistry; return(transaction1.MergePolicy != null && transaction2.MergePolicy != null && transaction1.MergePolicy.TestCompatiblePolicy(transaction2.MergePolicy) && transaction1.MergePolicy.CanMerge(transaction1, transaction2)); }
public MockTextUndoHistory(MockTextUndoHistoryRegistry undoHistoryRegistry) { _currentTransaction = null; UndoHistoryRegistry = undoHistoryRegistry; _undoStack = new Stack <ITextUndoTransaction>(); _redoStack = new Stack <ITextUndoTransaction>(); _activeUndoOperationPrimitive = null; _state = TextUndoHistoryState.Idle; }
public MockTextUndoTransaction(ITextUndoHistory history, ITextUndoTransaction parent, string description) { _history = history as MockTextUndoHistory; _parent = parent as MockTextUndoTransaction; Description = description; _state = UndoTransactionState.Open; _primitives = new List<ITextUndoPrimitive>(); MergePolicy = NullMergeUndoTransactionPolicy.Instance; IsReadOnly = true; }
/// <summary> /// This is how the transactions alert their containing history that they have finished /// (likely from the Dispose() method). /// </summary> /// <param name="transaction">This is the transaction that's finishing. It should match the history's current transaction. /// If it does not match, then the current transaction will be discarded and an exception will be thrown.</param> public void EndTransaction(ITextUndoTransaction transaction) { if (_currentTransaction != transaction) { _currentTransaction = null; throw new InvalidOperationException("Strings.EndTransactionOutOfOrder"); } // only add completed transactions to their parents (or the stack) if (_currentTransaction.State == UndoTransactionState.Completed) { if (_currentTransaction.Parent == null) // stack bottomed out! { MergeOrPushToUndoStack(_currentTransaction); } } _currentTransaction = _currentTransaction.Parent as MockTextUndoTransaction; }
/// <summary> /// Performs a redo operation and places the primitives on the redo stack, up until (and /// including) the transaction indicated. This is called by the linked undo transaction that /// is aware of the linking relationship between transactions, and it does not call back into /// the transactions' public Redo(). /// </summary> /// <param name="transaction"></param> public void RedoInIsolation(MockTextUndoTransaction transaction) { TextUndoHistoryState originalState = _state; _state = TextUndoHistoryState.Redoing; using (new AutoEnclose(delegate { this._state = originalState; })) { if (_redoStack.Contains(transaction)) { MockTextUndoTransaction redone = null; while (redone != transaction) { MockTextUndoTransaction ut = _redoStack.Pop() as MockTextUndoTransaction; ut.Do(); _undoStack.Push(ut); RaiseUndoRedoHappened(_state, ut); redone = ut; } } } }