2 // System.ComponentModel.Design.UndoEngine.cs
5 // Ivan N. Zlatev <contact@i-nz.net>
7 // Copyright (C) 2007 Ivan N. Zlatev <contact@i-nz.net>
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.ComponentModel;
33 using System.ComponentModel.Design;
34 using System.Collections.Generic;
35 using System.ComponentModel.Design.Serialization;
37 namespace System.ComponentModel.Design
39 public abstract class UndoEngine : IDisposable
41 private bool _undoing;
42 private UndoUnit _currentUnit;
43 private IServiceProvider _provider;
44 private bool _enabled;
46 protected UndoEngine (IServiceProvider provider)
49 throw new ArgumentNullException ("provider");
56 private void Enable ()
59 IComponentChangeService changeService = GetRequiredService (typeof (IComponentChangeService)) as IComponentChangeService;
60 changeService.ComponentAdding += new ComponentEventHandler (OnComponentAdding);
61 changeService.ComponentAdded += new ComponentEventHandler (OnComponentAdded);
62 changeService.ComponentRemoving += new ComponentEventHandler (OnComponentRemoving);
63 changeService.ComponentRemoved += new ComponentEventHandler (OnComponentRemoved);
64 changeService.ComponentChanging += new ComponentChangingEventHandler (OnComponentChanging);
65 changeService.ComponentChanged += new ComponentChangedEventHandler (OnComponentChanged);
66 changeService.ComponentRename += new ComponentRenameEventHandler (OnComponentRename);
68 IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost;
69 host.TransactionClosed += new DesignerTransactionCloseEventHandler (OnTransactionClosed);
70 host.TransactionOpened += new EventHandler (OnTransactionOpened);
76 private void Disable ()
79 IComponentChangeService changeService = GetRequiredService (typeof (IComponentChangeService)) as IComponentChangeService;
80 changeService.ComponentAdding -= new ComponentEventHandler (OnComponentAdding);
81 changeService.ComponentAdded -= new ComponentEventHandler (OnComponentAdded);
82 changeService.ComponentRemoving -= new ComponentEventHandler (OnComponentRemoving);
83 changeService.ComponentRemoved -= new ComponentEventHandler (OnComponentRemoved);
84 changeService.ComponentChanging -= new ComponentChangingEventHandler (OnComponentChanging);
85 changeService.ComponentChanged -= new ComponentChangedEventHandler (OnComponentChanged);
86 changeService.ComponentRename -= new ComponentRenameEventHandler (OnComponentRename);
88 IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost;
89 host.TransactionClosed -= new DesignerTransactionCloseEventHandler (OnTransactionClosed);
90 host.TransactionOpened -= new EventHandler (OnTransactionOpened);
96 // FIXME: there could be more transactions opened and closed (but not commited) after the first one!!!
97 // This means that there should be multiple units. Only the top level transaction is commited though
99 private void OnTransactionOpened (object sender, EventArgs args)
101 if (_currentUnit == null) {
102 IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost;
103 _currentUnit = CreateUndoUnit (host.TransactionDescription, true);
108 private void OnTransactionClosed (object sender, DesignerTransactionCloseEventArgs args)
110 // Console.WriteLine ("TransactionClosed: Commited: " + args.TransactionCommitted.ToString ());
111 IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost;
112 if (!host.InTransaction) { // the "top-most"/last transaction was closed (currentUnit one)
113 _currentUnit.Close ();
114 if (args.TransactionCommitted) {
115 AddUndoUnit (_currentUnit);
117 _currentUnit.Undo ();
118 DiscardUndoUnit (_currentUnit);
124 private void OnComponentAdding (object sender, ComponentEventArgs args)
126 if (_currentUnit == null)
127 _currentUnit = CreateUndoUnit ("Add " + args.Component.GetType ().Name, true);
128 _currentUnit.ComponentAdding (args);
131 private void OnComponentAdded (object sender, ComponentEventArgs args)
133 if (_currentUnit == null)
134 _currentUnit = CreateUndoUnit ("Add " + args.Component.Site.Name, true);
135 _currentUnit.ComponentAdded (args);
137 IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost;
138 if (!host.InTransaction) {
139 _currentUnit.Close ();
140 AddUndoUnit (_currentUnit);
145 private void OnComponentRemoving (object sender, ComponentEventArgs args)
147 if (_currentUnit == null)
148 _currentUnit = CreateUndoUnit ("Remove " + args.Component.Site.Name, true);
149 _currentUnit.ComponentRemoving (args);
152 private void OnComponentRemoved (object sender, ComponentEventArgs args)
154 if (_currentUnit == null)
155 _currentUnit = CreateUndoUnit ("Remove " + args.Component.GetType ().Name, true);
156 _currentUnit.ComponentRemoved (args);
158 IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost;
159 if (!host.InTransaction) {
160 _currentUnit.Close ();
161 AddUndoUnit (_currentUnit);
166 private void OnComponentChanging (object sender, ComponentChangingEventArgs args)
168 if (_currentUnit == null)
169 _currentUnit = CreateUndoUnit ("Modify " + ((IComponent)args.Component).Site.Name +
170 (args.Member != null ? "." + args.Member.Name : ""),
172 _currentUnit.ComponentChanging (args);
175 private void OnComponentChanged (object sender, ComponentChangedEventArgs args)
177 if (_currentUnit == null)
178 _currentUnit = CreateUndoUnit ("Modify " + ((IComponent)args.Component).Site.Name + "." +
179 (args.Member != null ? "." + args.Member.Name : ""),
181 _currentUnit.ComponentChanged (args);
183 IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost;
184 if (!host.InTransaction) {
185 _currentUnit.Close ();
186 AddUndoUnit (_currentUnit);
191 private void OnComponentRename (object sender, ComponentRenameEventArgs args)
193 if (_currentUnit == null)
194 _currentUnit = CreateUndoUnit ("Rename " + ((IComponent)args.Component).Site.Name, true);
195 _currentUnit.ComponentRename (args);
197 IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost;
198 if (!host.InTransaction) {
199 _currentUnit.Close ();
200 AddUndoUnit (_currentUnit);
205 public event EventHandler Undoing;
206 public event EventHandler Undone;
208 public bool Enabled {
209 get { return _enabled; }
218 public bool UndoInProgress {
219 get { return _undoing; }
222 protected virtual UndoEngine.UndoUnit CreateUndoUnit (string name, bool primary)
224 // Console.WriteLine ("CreateUndoUnit: " + name);
225 return new UndoUnit (this, name);
228 public void Dispose ()
233 protected virtual void Dispose (bool disposing)
236 if (_currentUnit != null) {
237 _currentUnit.Close ();
243 protected object GetRequiredService (Type serviceType)
245 object service = this.GetService (serviceType);
247 throw new NotSupportedException ("Service '" + serviceType.Name + "' missing");
251 protected object GetService (Type serviceType)
253 if (serviceType == null)
254 throw new ArgumentNullException ("serviceType");
256 if (_provider != null)
257 return _provider.GetService (serviceType);
261 protected virtual void OnUndoing (EventArgs e)
269 protected virtual void OnUndone (EventArgs e)
278 protected abstract void AddUndoUnit (UndoEngine.UndoUnit unit);
280 protected virtual void DiscardUndoUnit (UndoEngine.UndoUnit unit)
282 // Console.WriteLine ("DiscardUndoUnit: " + unit.Name);
286 protected class UndoUnit
290 public virtual void Undo (UndoEngine engine)
295 private class ComponentRenameAction : Action
297 private string _oldName;
298 private string _currentName;
300 public ComponentRenameAction (string currentName, string oldName)
302 // Console.WriteLine ("ComponentRenameAction (" + oldName + "): " + currentName);
303 _currentName = currentName;
307 public override void Undo (UndoEngine engine)
309 // Console.WriteLine ("ComponentRenameAction.Undo (" + _currentName + "): " + _oldName);
310 IDesignerHost host = engine.GetRequiredService (typeof (IDesignerHost)) as IDesignerHost;
311 IComponent component = host.Container.Components[_currentName];
312 component.Site.Name = _oldName;
313 string tmp = _currentName;
314 _currentName = _oldName;
317 } // ComponentRenameAction
319 private class ComponentAddRemoveAction : Action
321 private string _componentName;
322 private SerializationStore _serializedComponent;
325 public ComponentAddRemoveAction (UndoEngine engine, IComponent component, bool added)
327 if (component == null)
328 throw new ArgumentNullException ("component");
329 // Console.WriteLine ((added ? "Component*Add*RemoveAction" : "ComponentAdd*Remove*Action") +
330 // " (" + component.Site.Name + ")");
331 ComponentSerializationService serializationService = engine.GetRequiredService (
332 typeof (ComponentSerializationService)) as ComponentSerializationService;
334 _serializedComponent = serializationService.CreateStore ();
335 serializationService.Serialize (_serializedComponent, component);
336 _serializedComponent.Close ();
339 _componentName = component.Site.Name;
342 public override void Undo (UndoEngine engine)
344 IDesignerHost host = engine.GetRequiredService (typeof (IDesignerHost)) as IDesignerHost;
346 // Console.WriteLine ("Component*Add*RemoveAction.Undo (" + _componentName + ")");
347 IComponent component = host.Container.Components[_componentName];
348 if (component != null) // the component might have been destroyed already
349 host.DestroyComponent (component);
352 // Console.WriteLine ("ComponentAdd*Remove*Action.Undo (" + _componentName + ")");
353 ComponentSerializationService serializationService = engine.GetRequiredService (
354 typeof (ComponentSerializationService)) as ComponentSerializationService;
356 serializationService.DeserializeTo (_serializedComponent, host.Container);
360 } // ComponentAddRemoveAction
363 private class ComponentChangeAction : Action
365 private string _componentName;
366 private MemberDescriptor _member;
367 private IComponent _component;
368 private SerializationStore _afterChange;
369 private SerializationStore _beforeChange;
371 public ComponentChangeAction ()
375 public void SetOriginalState (UndoEngine engine, IComponent component, MemberDescriptor member)
378 _component = component;
379 _componentName = component.Site != null ? component.Site.Name : null;
380 // Console.WriteLine ("ComponentChangeAction.SetOriginalState (" + (_componentName != null ? (_componentName + ".") : "") +
381 // member.Name + "): " +
382 // (((PropertyDescriptor)member).GetValue (component) == null ? "null" :
383 // ((PropertyDescriptor)member).GetValue (component).ToString ()));
384 ComponentSerializationService serializationService = engine.GetRequiredService (
385 typeof (ComponentSerializationService)) as ComponentSerializationService;
386 _beforeChange = serializationService.CreateStore ();
387 serializationService.SerializeMemberAbsolute (_beforeChange, component, member);
388 _beforeChange.Close ();
392 public void SetModifiedState (UndoEngine engine, IComponent component, MemberDescriptor member)
394 // Console.WriteLine ("ComponentChangeAction.SetModifiedState (" + (_componentName != null ? (_componentName + ".") : "") +
395 // member.Name + "): " +
396 // (((PropertyDescriptor)member).GetValue (component) == null ? "null" :
397 // ((PropertyDescriptor)member).GetValue (component).ToString ()));
398 ComponentSerializationService serializationService = engine.GetRequiredService (
399 typeof (ComponentSerializationService)) as ComponentSerializationService;
400 _afterChange = serializationService.CreateStore ();
401 serializationService.SerializeMemberAbsolute (_afterChange, component, member);
402 _afterChange.Close ();
405 public bool IsComplete {
406 get { return (_beforeChange != null && _afterChange != null); }
409 public string ComponentName {
410 get { return _componentName; }
413 public IComponent Component {
414 get { return _component; }
417 public MemberDescriptor Member {
418 get { return _member; }
421 // Reminder: _component might no longer be a valid instance
422 // so one should request a new one.
424 public override void Undo (UndoEngine engine)
426 if (_beforeChange == null) {
427 // Console.WriteLine ("ComponentChangeAction.Undo: ERROR: UndoUnit is not complete.");
431 // Console.WriteLine ("ComponentChangeAction.Undo (" + _componentName + "." + _member.Name + ")");
432 IDesignerHost host = (IDesignerHost)engine.GetRequiredService (typeof(IDesignerHost));
433 _component = host.Container.Components[_componentName];
435 ComponentSerializationService serializationService = engine.GetRequiredService (
436 typeof (ComponentSerializationService)) as ComponentSerializationService;
437 serializationService.DeserializeTo (_beforeChange, host.Container);
439 SerializationStore tmp = _beforeChange;
440 _beforeChange = _afterChange;
443 } // ComponentChangeAction
445 private UndoEngine _engine;
446 private string _name;
447 private bool _closed;
448 private List<Action> _actions;
450 public UndoUnit (UndoEngine engine, string name)
453 throw new ArgumentNullException ("engine");
455 throw new ArgumentNullException ("name");
459 _actions = new List <Action> ();
464 _engine.OnUndoing (EventArgs.Empty);
466 _engine.OnUndone (EventArgs.Empty);
469 protected virtual void UndoCore ()
471 for (int i = _actions.Count - 1; i >= 0; i--) {
472 // Console.WriteLine ("Undoing action type: " + _actions[i].GetType ().Name);
473 _actions[i].Undo (_engine);
475 // Also reverses the stack of actions, so that
476 // if Undo is called twice it will Redo in the proper order
481 protected UndoEngine UndoEngine {
482 get { return _engine; }
485 public virtual bool IsEmpty {
486 get { return _actions.Count == 0; }
489 public virtual string Name {
490 get { return _name; }
493 public virtual void Close ()
495 // Console.WriteLine ("UndoUnot.Close (" + _name + ")");
499 public virtual void ComponentAdded (ComponentEventArgs e)
502 // Console.WriteLine ("New Action: Component*Add*RemoveAction (" + ((IComponent)e.Component).Site.Name + ")");
503 _actions.Add (new ComponentAddRemoveAction (_engine, (IComponent) e.Component, true));
507 public virtual void ComponentAdding (ComponentEventArgs e)
511 public virtual void ComponentChanged (ComponentChangedEventArgs e)
516 // A component starts getting removed.
517 // ComponentRemoving -> remove component -> ComponentRemoved
518 // The problem is that someone can subscribe to the Removed event after us (the UndoEngine) - e.g
519 // ParentControlDesigner will explicitly request (by setting it to null between Removing and Removed
520 // the serialization of the Parent property of the removed child.
521 // In the case where someone subscribes after and performs changes to the component, we might get
522 // ComponentChanged events after we've already created the addremove action, but the componentchangeaction
523 // will be incomplete standing before the addremove one.
525 ComponentChangeAction changeAction = null;
526 for (int i=0; i < _actions.Count; i++) {
527 changeAction = _actions[i] as ComponentChangeAction;
528 if (changeAction != null && !changeAction.IsComplete &&
529 changeAction.Component == e.Component &&
530 changeAction.Member.Equals (e.Member)) {
531 changeAction.SetModifiedState (_engine, (IComponent) e.Component, e.Member);
537 public virtual void ComponentChanging (ComponentChangingEventArgs e)
542 // Console.WriteLine ("New Action: ComponentChangeAction (" + ((IComponent)e.Component).Site.Name + ")");
543 ComponentChangeAction action = new ComponentChangeAction ();
544 action.SetOriginalState (_engine, (IComponent) e.Component, e.Member);
545 _actions.Add (action);
548 public virtual void ComponentRemoved (ComponentEventArgs e)
552 public virtual void ComponentRemoving (ComponentEventArgs e)
555 // Console.WriteLine ("New Action: ComponentAdd*Remove*Action (" + ((IComponent)e.Component).Site.Name + ")");
556 _actions.Add (new ComponentAddRemoveAction (_engine, e.Component, false));
560 public virtual void ComponentRename (ComponentRenameEventArgs e)
563 // Console.WriteLine ("New Action: ComponentRenameAction (" + ((IComponent)e.Component).Site.Name + ")");
564 _actions.Add (new ComponentRenameAction (e.NewName, e.OldName));
568 protected object GetService (Type serviceType)
570 return _engine.GetService (serviceType);
573 public override string ToString ()