1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
5 namespace System.Activities.Presentation
8 using System.Activities.Presentation.Model;
9 using System.Collections.Generic;
10 using System.Diagnostics.CodeAnalysis;
11 using System.Globalization;
15 [Fx.Tag.XamlVisible(false)]
16 public class UndoEngine : IUndoEngineOperations
18 const int capacity = 100;
19 List<UndoUnit> undoBuffer;
20 List<UndoUnit> redoBuffer;
21 EditingContext context;
23 //interface which contains actual implementation of AddUndoUnit, Undo and Redo operations
24 IUndoEngineOperations undoEngineImpl = null;
26 public event EventHandler<UndoUnitEventArgs> UndoUnitAdded;
27 public event EventHandler<UndoUnitEventArgs> UndoCompleted;
28 public event EventHandler<UndoUnitEventArgs> RedoCompleted;
29 public event EventHandler<UndoUnitEventArgs> UndoUnitCancelled;
30 public event EventHandler UndoUnitDiscarded;
31 public event EventHandler UndoRedoBufferChanged;
33 public UndoEngine(EditingContext context)
35 this.context = context;
36 undoBuffer = new List<UndoUnit>(capacity);
37 redoBuffer = new List<UndoUnit>(capacity);
38 this.undoEngineImpl = this;
41 internal bool IsBookmarkInPlace { get { return this.bookmark != null; } }
43 // CreateImmediateEditingScope - creates a new ImmediateEditingScope which gatters all edits in its
44 // undo unit list. all changes in ImmediateEditingScope appear as a one change and can be
45 // undoned or redoned as a one set.
46 internal ImmediateEditingScope CreateImmediateEditingScope(string bookmarkName, ModelTreeManager modelTreeManager)
48 Fx.Assert(modelTreeManager != null, "modelTreeManager should not be null.");
50 //only one bookmark is supported
51 Fx.Assert(this.bookmark == null, "Nested bookmarks are not supported.");
53 //create bookmark undo unit, and give it a description
54 BookmarkUndoUnit unit = new BookmarkUndoUnit(this.context, modelTreeManager)
56 Description = bookmarkName ?? string.Empty,
58 //create bookmark, and pass bookmark undo unit to it.
59 this.bookmark = new Bookmark(this, unit);
60 //switch implementation of AddUndoUnit, Undo, Redo to be delegated through bookmark
61 this.undoEngineImpl = bookmark;
62 return new ImmediateEditingScope(modelTreeManager, this.bookmark);
65 public IEnumerable<string> GetUndoActions()
67 return this.undoBuffer.Select(p => p.Description);
70 public IEnumerable<string> GetRedoActions()
72 return this.redoBuffer.Select(p => p.Description);
75 public void AddUndoUnit(UndoUnit unit)
79 throw FxTrace.Exception.ArgumentNull("unit");
81 this.undoEngineImpl.AddUndoUnitCore(unit);
86 this.IsUndoRedoInProgress = true;
87 bool succeeded = this.undoEngineImpl.UndoCore();
88 this.IsUndoRedoInProgress = false;
91 this.NotifyUndoRedoBufferChanged();
98 this.IsUndoRedoInProgress = true;
99 bool succeeded = this.undoEngineImpl.RedoCore();
100 this.IsUndoRedoInProgress = false;
103 this.NotifyUndoRedoBufferChanged();
108 public bool IsUndoRedoInProgress
114 void IUndoEngineOperations.AddUndoUnitCore(UndoUnit unit)
116 undoBuffer.Add(unit);
117 this.NotifyUndoUnitAdded(unit);
119 if (undoBuffer.Count > capacity)
121 undoBuffer.RemoveAt(0);
122 NotifyUndoUnitDiscarded();
126 this.NotifyUndoRedoBufferChanged();
129 bool IUndoEngineOperations.UndoCore()
131 bool succeeded = false;
132 if (undoBuffer.Count > 0)
134 UndoUnit unitToUndo = undoBuffer.Last();
135 undoBuffer.RemoveAt(undoBuffer.Count - 1);
137 redoBuffer.Add(unitToUndo);
138 NotifyUndoExecuted(unitToUndo);
144 bool IUndoEngineOperations.RedoCore()
146 bool succeeded = false;
147 if (redoBuffer.Count > 0)
149 UndoUnit unitToRedo = redoBuffer.Last();
150 redoBuffer.RemoveAt(redoBuffer.Count - 1);
152 undoBuffer.Add(unitToRedo);
153 NotifyRedoExecuted(unitToRedo);
159 private void NotifyUndoUnitAdded(UndoUnit unit)
161 if (this.UndoUnitAdded != null)
163 this.UndoUnitAdded(this, new UndoUnitEventArgs() { UndoUnit = unit });
167 private void NotifyUndoExecuted(UndoUnit unit)
169 if (this.UndoCompleted != null)
171 this.UndoCompleted(this, new UndoUnitEventArgs() { UndoUnit = unit });
175 private void NotifyRedoExecuted(UndoUnit unit)
177 if (this.RedoCompleted != null)
179 this.RedoCompleted(this, new UndoUnitEventArgs() { UndoUnit = unit });
183 private void NotifyUndoUnitCancelled(UndoUnit unit)
185 if (this.UndoUnitCancelled != null)
187 this.UndoUnitCancelled(this, new UndoUnitEventArgs() { UndoUnit = unit });
191 private void NotifyUndoUnitDiscarded()
193 if (this.UndoUnitDiscarded != null)
195 this.UndoUnitDiscarded(this, null);
199 private void NotifyUndoRedoBufferChanged()
201 if (null != this.UndoRedoBufferChanged)
203 this.UndoRedoBufferChanged(this, EventArgs.Empty);
207 //Bookmark implementation - implements core UndoEngine operations + IDisposable -
208 //default bookmark behavior is to Rollback changes, unless committed explicitly.
209 //usage of IDisposable enables usage of pattern:
210 // using (Bookmark b = new Bookmark())....
211 internal sealed class Bookmark : IDisposable, IUndoEngineOperations
213 BookmarkUndoUnit containerUndoUnit;
214 UndoEngine undoEngine;
215 bool isCommitted = false;
216 bool isRolledBack = false;
217 bool isDisposed = false;
219 internal Bookmark(UndoEngine undoEngine, BookmarkUndoUnit undoUnit)
221 this.undoEngine = undoEngine;
222 this.containerUndoUnit = undoUnit;
225 public void CommitBookmark()
227 //cannot commit more than once...
230 throw FxTrace.Exception.AsError(new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.UndoEngine_OperationNotAllowed, "CommitBookmark")));
233 this.isCommitted = true;
234 //restore original undo engine implementation
235 this.undoEngine.undoEngineImpl = this.undoEngine;
236 //get rid of the bookmark
237 this.undoEngine.bookmark = null;
238 //check if bookmark has any changes
239 if (this.containerUndoUnit.DoList.Count != 0 || this.containerUndoUnit.RedoList.Count != 0)
241 //add all changes in bookmark into a undo list as a one element
242 this.undoEngine.AddUndoUnit(this.containerUndoUnit);
248 public void RollbackBookmark()
250 //cannot rollback more than once
253 throw FxTrace.Exception.AsError(new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.UndoEngine_OperationNotAllowed, "RollbackBookmark")));
255 this.isRolledBack = true;
256 //get through the list of all accumulated changes and reverse each of them
257 foreach (UndoUnit unit in this.containerUndoUnit.DoList.Reverse<UndoUnit>())
262 this.containerUndoUnit.DoList.Clear();
263 this.containerUndoUnit.RedoList.Clear();
265 //restore original undo engine implementation
266 this.undoEngine.undoEngineImpl = this.undoEngine;
267 //get rid of the bookmark
268 this.undoEngine.bookmark = null;
269 this.undoEngine.NotifyUndoUnitCancelled(this.containerUndoUnit);
274 public void Dispose()
276 if (!this.isDisposed)
278 GC.SuppressFinalize(this);
283 void DisposeInternal()
285 if (!this.isDisposed)
287 //if not committed or rolled back - rollback by default
288 if (!this.isCommitted && !this.isRolledBack)
290 this.RollbackBookmark();
292 this.isDisposed = true;
296 void IUndoEngineOperations.AddUndoUnitCore(UndoUnit unit)
298 //add element to Undo list
299 this.containerUndoUnit.DoList.Add(unit);
301 this.containerUndoUnit.RedoList.Clear();
304 bool IUndoEngineOperations.UndoCore()
306 //if there is anything to undo
307 bool succeeded = false;
308 if (this.containerUndoUnit.DoList.Count > 0)
310 //get the last element done
311 UndoUnit unitToUndo = this.containerUndoUnit.DoList.Last();
313 this.containerUndoUnit.DoList.RemoveAt(this.containerUndoUnit.DoList.Count - 1);
316 //and insert to the head of redo list
317 this.containerUndoUnit.RedoList.Insert(0, unitToUndo);
323 bool IUndoEngineOperations.RedoCore()
325 //if there is anything to redo
326 bool succeeded = false;
327 if (this.containerUndoUnit.RedoList.Count > 0)
329 //get first element to redo
330 UndoUnit unitToRedo = this.containerUndoUnit.RedoList.First();
332 this.containerUndoUnit.RedoList.RemoveAt(0);
335 //add it to the end of undo list
336 this.containerUndoUnit.DoList.Add(unitToRedo);