context: 
  using System;
  using System.Text;
  using System.Xml;
  using System.CodeDom;
  using System.Collections;
  
  namespace Stetic.Wrapper
  {
  	
  	public class ActionTree: ActionTreeNode
  	{
  		public event EventHandler Changed;
  		
  		public ActionTree()
  		{
  		}
  		
  		public void GenerateBuildCode (GeneratorContext ctx, CodeFieldReferenceExpression uiManager)
  		{
  			StringBuilder sb = new StringBuilder ();
  			sb.Append ("<ui>");
  			GenerateUiString (sb);
  			sb.Append ("</ui>");
  			
  			CodeMethodInvokeExpression exp = new CodeMethodInvokeExpression (
  				uiManager,
  				"AddUiFromString",
  				new CodePrimitiveExpression (sb.ToString ())
  			);
  			ctx.Statements.Add (exp);
  		}
  		
  		public ActionGroup[] GetRequiredGroups ()
  		{
  			ArrayList list = new ArrayList ();
  			GetRequiredGroups (list);
  			return (ActionGroup[]) list.ToArray (typeof(ActionGroup));
  		}
  		
  		internal override void NotifyChanged ()
  		{
  			if (Changed != null)
  				Changed (this, EventArgs.Empty);
  		}
  	}
  	
  	public class ActionTreeNode: IDisposable
  	{
  		Gtk.UIManagerItemType type;
  		string name;
  		Action action;
  		ActionTreeNodeCollection children;
  		ActionTreeNode parentNode;
  		bool loading;
  		string lastActionName;
  		
  		public ActionTreeNode ()
  		{
  		}
  		
  		public ActionTreeNode Clone ()
  		{
  			return new ActionTreeNode (type, name, action);
  		}
  		
  		public ActionTreeNode (Gtk.UIManagerItemType type, string name, Action action)
  		{
  			this.type = type;
  			this.name = name;
  			this.action = action;
  			if (this.action != null) {
  				lastActionName = this.action.Name;
  				this.action.Deleted += OnActionDeleted;
  				this.action.ObjectChanged += OnActionChanged;
  			}
  		}
  		
  		public virtual void Dispose ()
  		{
  			if (action != null) {
  				action.Deleted -= OnActionDeleted;
  				action.ObjectChanged -= OnActionChanged;
  			}
  			if (children != null)
  				foreach (ActionTreeNode node in children)
  					node.Dispose ();
  		}
  		
  		void OnActionDeleted (object s, EventArgs args)
  		{
  			if (parentNode != null)
  				parentNode.Children.Remove (this);
  		}
  		
  		void OnActionChanged (object s, ObjectWrapperEventArgs args)
  		{
  			// If the name of the action changes, consider it a change in
  			// the node, since the generated xml will be different
  
  			Action ac = (Action) args.Wrapper;
  			if (ac.Name != lastActionName) {
  				lastActionName = ac.Name;
  				NotifyChanged ();
  			}
  		}
  
  		internal virtual void NotifyChanged ()
  		{
  			if (parentNode != null)
  				parentNode.NotifyChanged ();
  		}
  		
  		public XmlElement Write (XmlDocument doc, FileFormat format)
  		{
  			XmlElement elem = doc.CreateElement ("node");
  			if (name != null && name.Length > 0)
  				elem.SetAttribute ("name", name);
  			elem.SetAttribute ("type", type.ToString ());
  			if (action != null)
  				elem.SetAttribute ("action", action.Name);
  			
  			if (children != null) {
  				foreach (ActionTreeNode child in children) {
  					if (child.Action != null && child.Action.Name.Length == 0)
  						continue;
  					elem.AppendChild (child.Write (doc, format));
  				}
  			}
  			return elem;
  		}
  		
  		public void Read (Wrapper.Widget baseWidget, XmlElement elem)
  		{
  			name = elem.GetAttribute ("name");
  			if (elem.HasAttribute ("type"))
  				type = (Gtk.UIManagerItemType) Enum.Parse (typeof(Gtk.UIManagerItemType), elem.GetAttribute ("type"));
  			
  			// The name of an action may be empty in some situations (e.g. when adding a new action but before entering the name)
  			XmlAttribute actionAt = elem.Attributes ["action"];
  			if (actionAt != null) {
  				string aname = actionAt.Value;
  				foreach (ActionGroup grp in baseWidget.LocalActionGroups) {
  					action = grp.GetAction (aname);
  					if (action != null)
  						break;
  				}
  				if (action == null) {
  					foreach (ActionGroup group in baseWidget.Project.ActionGroups) {
  						action = group.GetAction (aname);
  						if (action != null)
  							break;
  					}
  				}
  				if (action != null) {
  					lastActionName = action.Name;
  					action.Deleted += OnActionDeleted;
  					action.ObjectChanged += OnActionChanged;
  				}
  			}
  			
  			try {
  				loading = true;
  				foreach (XmlElement child in elem.SelectNodes ("node")) {
  					ActionTreeNode node = new ActionTreeNode ();
  					node.Read (baseWidget, child);
  					Children.Add (node);
  				}
  			} finally {
  				loading = false;
  			}
  		}
  		
  		public virtual void GenerateBuildCode (GeneratorContext ctx, CodeVariableReferenceExpression uiManager, string path)
  		{
  			CodeMethodInvokeExpression exp = new CodeMethodInvokeExpression (
  				uiManager,
  				"AddUi",
  				new CodePrimitiveExpression (0),
  				new CodePrimitiveExpression (path),
  				new CodePrimitiveExpression (name),
  				new CodePrimitiveExpression (action != null ? action.Name : null),
  				new CodeFieldReferenceExpression (
  					new CodeTypeReferenceExpression (new CodeTypeReference (typeof(Gtk.UIManagerItemType), CodeTypeReferenceOptions.GlobalReference)),
  					type.ToString()
  				),
  				new CodePrimitiveExpression (false)
  			);
  			ctx.Statements.Add (exp);
  			
  			string localName = (name != null && name.Length > 0 ? name : (action != null ? action.Name : null));
  			if (localName != null) {
  				if (path != "/")
  					path = path + "/" + localName;
  				else
  					path += localName;
  			}
  			
  			foreach (ActionTreeNode node in Children)
  				node.GenerateBuildCode (ctx, uiManager, path);
  		}
  		
  		public void GenerateUiString (StringBuilder sb)
  		{
  			sb.Append ('<').Append (type.ToString().ToLower());
  
  			string name = this.name;
  			if (String.IsNullOrEmpty (name) && action != null)
  				name = action.Name;
  
  			if (!String.IsNullOrEmpty (name))
  				sb.Append (" name='").Append (name).Append ("'");
  			if (action != null)
  
gt: 				sb.Append (" action='").Append (action.Name).Append ("'");