// This is a completely pointless widget, but it shows how to subclass container... using System; using System.Collections.Generic; using Gtk; using Gdk; class PolarFixed : Container { IList children; public PolarFixed () { children = new List (); HasWindow = false; } // The child properties object public class PolarFixedChild : Container.ContainerChild { double theta; uint r; public PolarFixedChild (PolarFixed parent, Widget child, double theta, uint r) : base (parent, child) { this.theta = theta; this.r = r; } // We call parent.QueueResize() from the property setters here so that you // can move the widget around just by changing its child properties (just // like with a native container class). public double Theta { get { return theta; } set { theta = value; parent.QueueResize (); } } public uint R { get { return r; } set { r = value; parent.QueueResize (); } } } // Override the child properties accessor to return the right object from // "children". public override ContainerChild this [Widget w] { get { foreach (PolarFixedChild pfc in children) { if (pfc.Child == w) return pfc; } return null; } } // Indicate the kind of children the container will accept. Most containers // will accept any kind of child, so they should return Gtk.Widget.GType. // The default is "GLib.GType.None", which technically means that no (new) // children can be added to the container, though Container.Add does not // enforce this. protected override GLib.GType OnChildType () { return Gtk.Widget.GType; } // Implement gtk_container_forall(), which is also used by // Gtk.Container.Children and Gtk.Container.AllChildren. protected override void ForAll (bool include_internals, Callback callback) { foreach (PolarFixedChild pfc in children) callback (pfc.Child); } // Invoked by Container.Add (w). It's good practice to have this do *something*, // even if it's not something terribly useful. protected override void OnAdded (Widget w) { Put (w, 0.0, 0); } // our own adder method public void Put (Widget w, double theta, uint r) { children.Add (new PolarFixedChild (this, w, theta, r)); w.Parent = this; QueueResize (); } public void Move (Widget w, double theta, uint r) { PolarFixedChild pfc = (PolarFixedChild)this[w]; if (pfc != null) { pfc.Theta = theta; pfc.R = r; } } // invoked by Container.Remove (w) protected override void OnRemoved (Widget w) { PolarFixedChild pfc = (PolarFixedChild)this[w]; if (pfc != null) { pfc.Child.Unparent (); children.Remove (pfc); QueueResize (); } } // Handle size request protected override void OnGetPreferredHeight (out int minimal_height, out int natural_height) { Requisition req = new Requisition (); OnSizeRequested (ref req); minimal_height = natural_height = req.Height; } protected override void OnGetPreferredWidth (out int minimal_width, out int natural_width) { Requisition req = new Requisition (); OnSizeRequested (ref req); minimal_width = natural_width = req.Width; } void OnSizeRequested (ref Requisition req) { int child_width, child_minwidth, child_height, child_minheight; int x, y; req.Width = req.Height = 0; foreach (PolarFixedChild pfc in children) { // Recursively request the size of each child pfc.Child.GetPreferredWidth (out child_minwidth, out child_width); pfc.Child.GetPreferredHeight (out child_minheight, out child_height); // Figure out where we're going to put it x = (int)(Math.Cos (pfc.Theta) * pfc.R) + child_width / 2; y = (int)(Math.Sin (pfc.Theta) * pfc.R) + child_height / 2; // Update our own size request to fit it if (req.Width < 2 * x) req.Width = 2 * x; if (req.Height < 2 * y) req.Height = 2 * y; } // Take Container.BorderWidth into account req.Width += (int)(2 * BorderWidth); req.Height += (int)(2 * BorderWidth); } // Size allocation. Note that the allocation received may be smaller than what we // requested. Some containers will take that into account by giving some or all // of their children a smaller allocation than they requested. Other containers // (like this one) just let their children get placed partly out-of-bounds if they // aren't allocated enough room. protected override void OnSizeAllocated (Rectangle allocation) { Requisition childReq, childMinReq; int cx, cy, x, y; // This sets the "Allocation" property. For widgets that // have a GdkWindow, it also calls GdkWindow.MoveResize() base.OnSizeAllocated (allocation); // Figure out where the center of the grid will be cx = allocation.X + (allocation.Width / 2); cy = allocation.Y + (allocation.Height / 2); foreach (PolarFixedChild pfc in children) { pfc.Child.GetPreferredSize (out childMinReq, out childReq); x = (int)(Math.Cos (pfc.Theta) * pfc.R) - childReq.Width / 2; y = (int)(Math.Sin (pfc.Theta) * pfc.R) + childReq.Height / 2; allocation.X = cx + x; allocation.Width = childReq.Width; allocation.Y = cy - y; allocation.Height = childReq.Height; pfc.Child.SizeAllocate (allocation); } } } class Test { public static void Main () { uint r; double theta; Application.Init (); Gtk.Window win = new Gtk.Window ("Polar Coordinate Container"); win.DeleteEvent += new DeleteEventHandler (Window_Delete); Notebook notebook = new Notebook (); win.Add (notebook); // Clock PolarFixed pf = new PolarFixed (); notebook.AppendPage (pf, new Label ("Clock")); for (int hour = 1; hour <= 12; hour ++) { theta = (Math.PI / 2) - hour * (Math.PI / 6); if (theta < 0) theta += 2 * Math.PI; Label l = new Label ("" + hour.ToString () + ""); l.UseMarkup = true; pf.Put (l, theta, 200); } // Spiral pf = new PolarFixed (); notebook.AppendPage (pf, new Label ("Spiral")); r = 0; theta = 0.0; foreach (string id in Gtk.Stock.ListIds ()) { StockItem item = Gtk.Stock.Lookup (id); if (item.Label == null) continue; pf.Put (new Gtk.Button (id), theta, r); // Logarithmic spiral: r = a*e^(b*theta) r += 5; theta = 10 * Math.Log (10 * r); } win.ShowAll (); Application.Run (); } static void Window_Delete (object obj, DeleteEventArgs args) { Application.Quit (); args.RetVal = true; } }