context: using Gtk;
  using System;
  using System.Collections;
  
  namespace Stetic {
  
  	internal class Grid : Gtk.Container {
  
  		public Grid () : base ()
  		{
  			Stetic.Editor.GtkWorkarounds.FixContainerLeak (this);
  			
  			BorderWidth = 2;
  			WidgetFlags |= WidgetFlags.NoWindow;
  			lines = new ArrayList ();
  			group = null;
  		}
  
  		// Padding constants
  		const int groupPad = 6;
  		const int hPad = 6;
  		const int linePad = 3;
  
  		// Theme-based sizes; computed at first SizeRequest
  		static int indent = -1;
  		static int lineHeight = -1;
  
  		Grid[] group;
  		public static void Connect (params Grid[] grids)
  		{
  			for (int i = 0; i < grids.Length; i++) {
  				grids[i].group = new Grid[grids.Length - 1];
  				Array.Copy (grids, 0, grids[i].group, 0, i);
  				Array.Copy (grids, i + 1, grids[i].group, i, grids.Length - i - 1);
  			}
  		}
  
  		class Pair {
  			Gtk.Widget label;
  			Gtk.Widget editor;
  
  			public Pair (Grid grid, string name, Widget editor) : this (grid, name, editor, null) {}
  
  			public Pair (Grid grid, string name, Widget editor, string description)
  			{
  				Gtk.Label l = new Label (name);
  				l.UseMarkup = true;
  				l.Justify = Justification.Left;
  				l.Xalign = 0;
  				l.Show ();
  
  				if (description == null)
  					label = l;
  				else {
  					Gtk.EventBox ebox = new Gtk.EventBox ();
  					ebox.Add (l);
  					ebox.Show ();
  					ebox.TooltipText = description;
  					label = ebox;
  				}
  				label.Parent = grid;
  
  				this.editor = editor;
  				editor.Parent = grid;
  				editor.Show ();
  			}
  
  			public Widget Label {
  				get {
  					return label;
  				}
  			}
  
  			public Widget Editor {
  				get {
  					return editor;
  				}
  			}
  		}
  
  		// list of widgets and Stetic.Grid.Pairs
  		ArrayList lines;
  
  		public void Append (Widget w)
  		{
  			w.Parent = this;
  			w.Show ();
  
  			lines.Add (w);
  			QueueDraw ();
  		}
  
  		public void Append (Widget w, string description)
  		{
  			if ((w.WidgetFlags & WidgetFlags.NoWindow) != 0) {
  				Gtk.EventBox ebox = new Gtk.EventBox ();
  				ebox.Add (w);
  				ebox.Show ();
  				w = ebox;
  			}
  			w.Parent = this;
  			w.Show ();
  			w.TooltipText = description;
  			lines.Add (w);
  			QueueDraw ();
  		}
  
  		public void AppendLabel (string text)
  		{
  			Gtk.Label label = new Label (text);
  			label.UseMarkup = true;
  			label.Justify = Justification.Left;
  			label.Xalign = 0;
  			Append (label);
  		}
  
  		public void AppendGroup (string name, bool expanded)
  		{
  			Gtk.Expander exp = new Expander ("<b>" + name + "</b>");
  			exp.UseMarkup = true;
  			exp.Expanded = expanded;
  			exp.AddNotification ("expanded", ExpansionChanged);
  			Append (exp);
  		}
  
  		public void AppendPair (string label, Widget editor, string description)
  		{
  			Stetic.Grid.Pair pair = new Pair (this, label, editor, description);
  			lines.Add (pair);
  			QueueDraw ();
  		}
  
  		protected override void OnRemoved (Widget w)
  		{
  			w.Unparent ();
  		}
  
  		void ExpansionChanged (object obj, GLib.NotifyArgs args)
  		{
  			Gtk.Expander exp = obj as Gtk.Expander;
  
  			int ind = lines.IndexOf (exp);
  			if (ind == -1)
  				return;
  
  			ind++;
  			while (ind < lines.Count && !(lines[ind] is Gtk.Expander)) {
  				if (lines[ind] is Widget) {
  					Widget w = (Widget)lines[ind];
  					if (exp.Expanded)
  						w.Show ();
  					else
  						w.Hide ();
  				} else if (lines[ind] is Pair) {
  					Pair p = (Pair)lines[ind];
  					if (exp.Expanded) {
  						p.Label.Show ();
  						p.Editor.Show ();
  					} else {
  						p.Label.Hide ();
  						p.Editor.Hide ();
  					}
  				}
  				ind++;
  			}
  
  			QueueDraw ();
  		}
  
  		protected void Clear ()
  		{
  			foreach (object obj in lines) {
  				if (obj is Widget)
  					((Widget)obj).Destroy ();
  				else if (obj is Pair) {
  					Pair p = (Pair)obj;
  					p.Label.Destroy ();
  					p.Editor.Destroy ();
  				}
  			}
  
  			lines.Clear ();
  		}
  
  		protected override void ForAll (bool include_internals, Gtk.Callback callback)
  		{
  			if (!include_internals)
  				return;
  
  			foreach (object obj in lines) {
  				if (obj is Widget)
  					callback ((Widget)obj);
  				else if (obj is Pair) {
  					Pair p = (Pair)obj;
  					callback (p.Label);
  					callback (p.Editor);
  				}
  			}
  		}
  
  		// These are figured out at requisition time and used again at
  		// allocation time.
  		int lwidth, ewidth;
  
  		void SizeRequestGrid (Grid grid, ref Gtk.Requisition req)
  		{
  			bool visible = true;
  
  			req.Width = req.Height = 0;
  			foreach (object obj in grid.lines) {
  				if (obj is Expander) {
  					Gtk.Widget w = (Gtk.Widget)obj;
  					Gtk.Requisition childreq;
  
  					childreq = w.SizeRequest ();
  					if (req.Width < childreq.Width)
  						req.Width = childreq.Width;
  					req.Height += groupPad + childreq.Height;
  
  					visible = ((Gtk.Expander)obj).Expanded;
  
  					if (indent == -1) {
  						// Seems like there should be an easier way...
  						int focusWidth = (int)w.StyleGetProperty ("focus-line-width");
  						int focusPad = (int)w.StyleGetProperty ("focus-padding");
  						int expanderSize = (int)w.StyleGetProperty ("expander-size");
  						int expanderSpacing = (int)w.StyleGetProperty ("expander-spacing");
  						indent = focusWidth + focusPad + expanderSize + 2 * expanderSpacing;
  					}
  				} else if (obj is Widget) {
  					Gtk.Widget w = (Gtk.Widget)obj;
  					Gtk.Requisition childreq;
  
  					childreq = w.SizeRequest ();
  					if (lwidth < childreq.Width)
  						lwidth = childreq.Width;
  					if (visible)
  						req.Height += linePad + childreq.Height;
  				} else if (obj is Pair) {
  					Pair p = (Pair)obj;
  					Gtk.Requisition lreq, ereq;
  
  					lreq = p.Label.SizeRequest ();
  					ereq = p.Editor.SizeRequest ();
  
  					if (lineHeight == -1)
  						lineHeight = (int)(1.5 * lreq.Height);
  
  					if (lreq.Width > lwidth)
  						lwidth = lreq.Width;
  					if (ereq.Width > ewidth)
  						ewidth = ereq.Width;
  
  					if (visible)
  						req.Height += Math.Max (lineHeight, ereq.Height) + linePad;
  				}
  			}
  
  			req.Width = Math.Max (req.Width, indent + lwidth + hPad + ewidth);
  			req.Height += 2 * (int)BorderWidth;
  			req.Width += 2 * (int)BorderWidth;
  		}
  
  		protected override void OnSizeRequested (ref Gtk.Requisition req)
  		{
  			lwidth = ewidth = 0;
  
  			if (group != null) {
  				foreach (Grid grid in group)
  					SizeRequestGrid (grid, ref req);
  			}
  
  			SizeRequestGrid (this, ref req);
  		}
  
  		protected override void OnSizeAllocated (Gdk.Rectangle alloc)
  		{
  			int xbase = alloc.X + (int)BorderWidth;
  			int ybase = alloc.Y + (int)BorderWidth;
  
  			base.OnSizeAllocated (alloc);
  
  			int y = ybase;
  			bool visible = true;
  
  			foreach (object obj in lines) {
  				if (!visible && !(obj is Expander))
  					continue;
  
  				if (obj is Widget) {
  					Gtk.Widget w = (Gtk.Widget)obj;
  					if (!w.Visible)
  						continue;
  
  					Gdk.Rectangle childalloc;
  					Gtk.Requisition childreq;
  
  					childreq = w.ChildRequisition;
  
  					if (obj is Expander) {
  						childalloc.X = xbase;
  						childalloc.Width = alloc.Width - 2 * (int)BorderWidth;
  						visible = ((Gtk.Expander)obj).Expanded;
  
gt: 						y += groupPad;