// // System.ComponentModel.Design.UndoEngine.cs // // Author: // Ivan N. Zlatev // // Copyright (C) 2007 Ivan N. Zlatev // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #if NET_2_0 using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Collections.Generic; using System.ComponentModel.Design.Serialization; namespace System.ComponentModel.Design { public abstract class UndoEngine : IDisposable { private bool _undoing; private UndoUnit _currentUnit; private IServiceProvider _provider; private bool _enabled; protected UndoEngine (IServiceProvider provider) { if (provider == null) throw new ArgumentNullException ("provider"); _provider = provider; _currentUnit = null; Enable (); } private void Enable () { if (!_enabled) { IComponentChangeService changeService = GetRequiredService (typeof (IComponentChangeService)) as IComponentChangeService; changeService.ComponentAdding += new ComponentEventHandler (OnComponentAdding); changeService.ComponentAdded += new ComponentEventHandler (OnComponentAdded); changeService.ComponentRemoving += new ComponentEventHandler (OnComponentRemoving); changeService.ComponentRemoved += new ComponentEventHandler (OnComponentRemoved); changeService.ComponentChanging += new ComponentChangingEventHandler (OnComponentChanging); changeService.ComponentChanged += new ComponentChangedEventHandler (OnComponentChanged); changeService.ComponentRename += new ComponentRenameEventHandler (OnComponentRename); IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost; host.TransactionClosed += new DesignerTransactionCloseEventHandler (OnTransactionClosed); host.TransactionOpened += new EventHandler (OnTransactionOpened); _enabled = true; } } private void Disable () { if (_enabled) { IComponentChangeService changeService = GetRequiredService (typeof (IComponentChangeService)) as IComponentChangeService; changeService.ComponentAdding -= new ComponentEventHandler (OnComponentAdding); changeService.ComponentAdded -= new ComponentEventHandler (OnComponentAdded); changeService.ComponentRemoving -= new ComponentEventHandler (OnComponentRemoving); changeService.ComponentRemoved -= new ComponentEventHandler (OnComponentRemoved); changeService.ComponentChanging -= new ComponentChangingEventHandler (OnComponentChanging); changeService.ComponentChanged -= new ComponentChangedEventHandler (OnComponentChanged); changeService.ComponentRename -= new ComponentRenameEventHandler (OnComponentRename); IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost; host.TransactionClosed -= new DesignerTransactionCloseEventHandler (OnTransactionClosed); host.TransactionOpened -= new EventHandler (OnTransactionOpened); _enabled = false; } } // FIXME: there could be more transactions opened and closed (but not commited) after the first one!!! // This means that there should be multiple units. Only the top level transaction is commited though // private void OnTransactionOpened (object sender, EventArgs args) { if (_currentUnit == null) { IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost; _currentUnit = CreateUndoUnit (host.TransactionDescription, true); } } private void OnTransactionClosed (object sender, DesignerTransactionCloseEventArgs args) { // Console.WriteLine ("TransactionClosed: Commited: " + args.TransactionCommitted.ToString ()); IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost; if (!host.InTransaction) { // the "top-most"/last transaction was closed (currentUnit one) _currentUnit.Close (); if (args.TransactionCommitted) { AddUndoUnit (_currentUnit); } else { _currentUnit.Undo (); DiscardUndoUnit (_currentUnit); } _currentUnit = null; } } private void OnComponentAdding (object sender, ComponentEventArgs args) { if (_currentUnit == null) _currentUnit = CreateUndoUnit ("Add " + args.Component.GetType ().Name, true); _currentUnit.ComponentAdding (args); } private void OnComponentAdded (object sender, ComponentEventArgs args) { if (_currentUnit == null) _currentUnit = CreateUndoUnit ("Add " + args.Component.Site.Name, true); _currentUnit.ComponentAdded (args); IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost; if (!host.InTransaction) { _currentUnit.Close (); AddUndoUnit (_currentUnit); _currentUnit = null; } } private void OnComponentRemoving (object sender, ComponentEventArgs args) { if (_currentUnit == null) _currentUnit = CreateUndoUnit ("Remove " + args.Component.Site.Name, true); _currentUnit.ComponentRemoving (args); } private void OnComponentRemoved (object sender, ComponentEventArgs args) { if (_currentUnit == null) _currentUnit = CreateUndoUnit ("Remove " + args.Component.GetType ().Name, true); _currentUnit.ComponentRemoved (args); IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost; if (!host.InTransaction) { _currentUnit.Close (); AddUndoUnit (_currentUnit); _currentUnit = null; } } private void OnComponentChanging (object sender, ComponentChangingEventArgs args) { if (_currentUnit == null) _currentUnit = CreateUndoUnit ("Modify " + ((IComponent)args.Component).Site.Name + (args.Member != null ? "." + args.Member.Name : ""), true); _currentUnit.ComponentChanging (args); } private void OnComponentChanged (object sender, ComponentChangedEventArgs args) { if (_currentUnit == null) _currentUnit = CreateUndoUnit ("Modify " + ((IComponent)args.Component).Site.Name + "." + (args.Member != null ? "." + args.Member.Name : ""), true); _currentUnit.ComponentChanged (args); IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost; if (!host.InTransaction) { _currentUnit.Close (); AddUndoUnit (_currentUnit); _currentUnit = null; } } private void OnComponentRename (object sender, ComponentRenameEventArgs args) { if (_currentUnit == null) _currentUnit = CreateUndoUnit ("Rename " + ((IComponent)args.Component).Site.Name, true); _currentUnit.ComponentRename (args); IDesignerHost host = GetRequiredService (typeof (IDesignerHost)) as IDesignerHost; if (!host.InTransaction) { _currentUnit.Close (); AddUndoUnit (_currentUnit); _currentUnit = null; } } public event EventHandler Undoing; public event EventHandler Undone; public bool Enabled { get { return _enabled; } set { if (value) Enable (); else Disable (); } } public bool UndoInProgress { get { return _undoing; } } protected virtual UndoEngine.UndoUnit CreateUndoUnit (string name, bool primary) { // Console.WriteLine ("CreateUndoUnit: " + name); return new UndoUnit (this, name); } public void Dispose () { Dispose (true); } protected virtual void Dispose (bool disposing) { if (disposing) { if (_currentUnit != null) { _currentUnit.Close (); _currentUnit = null; } } } protected object GetRequiredService (Type serviceType) { object service = this.GetService (serviceType); if (service == null) throw new NotSupportedException ("Service '" + serviceType.Name + "' missing"); return service; } protected object GetService (Type serviceType) { if (serviceType == null) throw new ArgumentNullException ("serviceType"); if (_provider != null) return _provider.GetService (serviceType); return null; } protected virtual void OnUndoing (EventArgs e) { Disable (); _undoing = true; if (Undoing != null) Undoing (this, e); } protected virtual void OnUndone (EventArgs e) { Enable (); _undoing = false; if (Undone != null) Undone (this, e); } protected abstract void AddUndoUnit (UndoEngine.UndoUnit unit); protected virtual void DiscardUndoUnit (UndoEngine.UndoUnit unit) { // Console.WriteLine ("DiscardUndoUnit: " + unit.Name); } protected class UndoUnit { private class Action { public virtual void Undo (UndoEngine engine) { } } private class ComponentRenameAction : Action { private string _oldName; private string _currentName; public ComponentRenameAction (string currentName, string oldName) { // Console.WriteLine ("ComponentRenameAction (" + oldName + "): " + currentName); _currentName = currentName; _oldName = oldName; } public override void Undo (UndoEngine engine) { // Console.WriteLine ("ComponentRenameAction.Undo (" + _currentName + "): " + _oldName); IDesignerHost host = engine.GetRequiredService (typeof (IDesignerHost)) as IDesignerHost; IComponent component = host.Container.Components[_currentName]; component.Site.Name = _oldName; string tmp = _currentName; _currentName = _oldName; _oldName = tmp; } } // ComponentRenameAction private class ComponentAddRemoveAction : Action { private string _componentName; private SerializationStore _serializedComponent; private bool _added; public ComponentAddRemoveAction (UndoEngine engine, IComponent component, bool added) { if (component == null) throw new ArgumentNullException ("component"); // Console.WriteLine ((added ? "Component*Add*RemoveAction" : "ComponentAdd*Remove*Action") + // " (" + component.Site.Name + ")"); ComponentSerializationService serializationService = engine.GetRequiredService ( typeof (ComponentSerializationService)) as ComponentSerializationService; _serializedComponent = serializationService.CreateStore (); serializationService.Serialize (_serializedComponent, component); _serializedComponent.Close (); _added = added; _componentName = component.Site.Name; } public override void Undo (UndoEngine engine) { IDesignerHost host = engine.GetRequiredService (typeof (IDesignerHost)) as IDesignerHost; if (_added) { // Console.WriteLine ("Component*Add*RemoveAction.Undo (" + _componentName + ")"); IComponent component = host.Container.Components[_componentName]; if (component != null) // the component might have been destroyed already host.DestroyComponent (component); _added = false; } else { // Console.WriteLine ("ComponentAdd*Remove*Action.Undo (" + _componentName + ")"); ComponentSerializationService serializationService = engine.GetRequiredService ( typeof (ComponentSerializationService)) as ComponentSerializationService; serializationService.DeserializeTo (_serializedComponent, host.Container); _added = true; } } } // ComponentAddRemoveAction private class ComponentChangeAction : Action { private string _componentName; private MemberDescriptor _member; private IComponent _component; private SerializationStore _afterChange; private SerializationStore _beforeChange; public ComponentChangeAction () { } public void SetOriginalState (UndoEngine engine, IComponent component, MemberDescriptor member) { _member = member; _component = component; _componentName = component.Site != null ? component.Site.Name : null; // Console.WriteLine ("ComponentChangeAction.SetOriginalState (" + (_componentName != null ? (_componentName + ".") : "") + // member.Name + "): " + // (((PropertyDescriptor)member).GetValue (component) == null ? "null" : // ((PropertyDescriptor)member).GetValue (component).ToString ())); ComponentSerializationService serializationService = engine.GetRequiredService ( typeof (ComponentSerializationService)) as ComponentSerializationService; _beforeChange = serializationService.CreateStore (); serializationService.SerializeMemberAbsolute (_beforeChange, component, member); _beforeChange.Close (); } public void SetModifiedState (UndoEngine engine, IComponent component, MemberDescriptor member) { // Console.WriteLine ("ComponentChangeAction.SetModifiedState (" + (_componentName != null ? (_componentName + ".") : "") + // member.Name + "): " + // (((PropertyDescriptor)member).GetValue (component) == null ? "null" : // ((PropertyDescriptor)member).GetValue (component).ToString ())); ComponentSerializationService serializationService = engine.GetRequiredService ( typeof (ComponentSerializationService)) as ComponentSerializationService; _afterChange = serializationService.CreateStore (); serializationService.SerializeMemberAbsolute (_afterChange, component, member); _afterChange.Close (); } public bool IsComplete { get { return (_beforeChange != null && _afterChange != null); } } public string ComponentName { get { return _componentName; } } public IComponent Component { get { return _component; } } public MemberDescriptor Member { get { return _member; } } // Reminder: _component might no longer be a valid instance // so one should request a new one. // public override void Undo (UndoEngine engine) { if (_beforeChange == null) { // Console.WriteLine ("ComponentChangeAction.Undo: ERROR: UndoUnit is not complete."); return; } // Console.WriteLine ("ComponentChangeAction.Undo (" + _componentName + "." + _member.Name + ")"); IDesignerHost host = (IDesignerHost)engine.GetRequiredService (typeof(IDesignerHost)); _component = host.Container.Components[_componentName]; ComponentSerializationService serializationService = engine.GetRequiredService ( typeof (ComponentSerializationService)) as ComponentSerializationService; serializationService.DeserializeTo (_beforeChange, host.Container); SerializationStore tmp = _beforeChange; _beforeChange = _afterChange; _afterChange = tmp; } } // ComponentChangeAction private UndoEngine _engine; private string _name; private bool _closed; private List _actions; public UndoUnit (UndoEngine engine, string name) { if (engine == null) throw new ArgumentNullException ("engine"); if (name == null) throw new ArgumentNullException ("name"); _engine = engine; _name = name; _actions = new List (); } public void Undo () { _engine.OnUndoing (EventArgs.Empty); UndoCore (); _engine.OnUndone (EventArgs.Empty); } protected virtual void UndoCore () { for (int i = _actions.Count - 1; i >= 0; i--) { // Console.WriteLine ("Undoing action type: " + _actions[i].GetType ().Name); _actions[i].Undo (_engine); } // Also reverses the stack of actions, so that // if Undo is called twice it will Redo in the proper order // _actions.Reverse (); } protected UndoEngine UndoEngine { get { return _engine; } } public virtual bool IsEmpty { get { return _actions.Count == 0; } } public virtual string Name { get { return _name; } } public virtual void Close () { // Console.WriteLine ("UndoUnot.Close (" + _name + ")"); _closed = true; } public virtual void ComponentAdded (ComponentEventArgs e) { if (!_closed) { // Console.WriteLine ("New Action: Component*Add*RemoveAction (" + ((IComponent)e.Component).Site.Name + ")"); _actions.Add (new ComponentAddRemoveAction (_engine, (IComponent) e.Component, true)); } } public virtual void ComponentAdding (ComponentEventArgs e) { } public virtual void ComponentChanged (ComponentChangedEventArgs e) { if (_closed) return; // A component starts getting removed. // ComponentRemoving -> remove component -> ComponentRemoved // The problem is that someone can subscribe to the Removed event after us (the UndoEngine) - e.g // ParentControlDesigner will explicitly request (by setting it to null between Removing and Removed // the serialization of the Parent property of the removed child. // In the case where someone subscribes after and performs changes to the component, we might get // ComponentChanged events after we've already created the addremove action, but the componentchangeaction // will be incomplete standing before the addremove one. // ComponentChangeAction changeAction = null; for (int i=0; i < _actions.Count; i++) { changeAction = _actions[i] as ComponentChangeAction; if (changeAction != null && !changeAction.IsComplete && changeAction.Component == e.Component && changeAction.Member.Equals (e.Member)) { changeAction.SetModifiedState (_engine, (IComponent) e.Component, e.Member); break; } } } public virtual void ComponentChanging (ComponentChangingEventArgs e) { if (_closed) return; // Console.WriteLine ("New Action: ComponentChangeAction (" + ((IComponent)e.Component).Site.Name + ")"); ComponentChangeAction action = new ComponentChangeAction (); action.SetOriginalState (_engine, (IComponent) e.Component, e.Member); _actions.Add (action); } public virtual void ComponentRemoved (ComponentEventArgs e) { } public virtual void ComponentRemoving (ComponentEventArgs e) { if (!_closed) { // Console.WriteLine ("New Action: ComponentAdd*Remove*Action (" + ((IComponent)e.Component).Site.Name + ")"); _actions.Add (new ComponentAddRemoveAction (_engine, e.Component, false)); } } public virtual void ComponentRename (ComponentRenameEventArgs e) { if (!_closed) { // Console.WriteLine ("New Action: ComponentRenameAction (" + ((IComponent)e.Component).Site.Name + ")"); _actions.Add (new ComponentRenameAction (e.NewName, e.OldName)); } } protected object GetService (Type serviceType) { return _engine.GetService (serviceType); } public override string ToString () { return _name; } } } } #endif