diff --git a/Source/Libs/GtkSharp/Container.Forall.cs b/Source/Libs/GtkSharp/Container.Forall.cs new file mode 100644 index 000000000..ba3fabe47 --- /dev/null +++ b/Source/Libs/GtkSharp/Container.Forall.cs @@ -0,0 +1,156 @@ +using GtkSharp; + +namespace Gtk +{ + using System; + using System.Runtime.InteropServices; + + public partial class Container + { + internal static D TryGetDelegate(IntPtr _cb) + { + try { + var d = Marshal.GetDelegateForFunctionPointer(_cb); + return d; + } catch { + return default; + } + } + + static ForAllNativeDelegate ForAll_cb_delegate; + + static ForAllNativeDelegate ForAllVMCallback { + get { + if (ForAll_cb_delegate == null) + ForAll_cb_delegate = new ForAllNativeDelegate(ForAll_cb); + return ForAll_cb_delegate; + } + } + + static void OverrideForAll(GLib.GType gtype) + { + OverrideForAll(gtype, ForAllVMCallback); + } + + /// + /// sets callback forall in GtkContainerClass-Struct + /// + /// + /// + static void OverrideForAll(GLib.GType gtype, ForAllNativeDelegate callback) + { + unsafe { + IntPtr* raw_ptr = (IntPtr*) (((long) gtype.GetClassPtr()) + (long) class_abi.GetFieldOffset("forall")); + *raw_ptr = Marshal.GetFunctionPointerForDelegate((Delegate) callback); + } + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void ForAllNativeDelegate(IntPtr inst, bool include_internals, IntPtr cb, IntPtr data); + + internal class ForAllCallbackHandler + { + internal IntPtr cb { get; } + internal IntPtr data { get; } + internal bool include_internals { get; } + + internal ForAllCallbackHandler(IntPtr cb, IntPtr data, bool include_internals) + { + this.cb = cb; + this.data = data; + this.include_internals = include_internals; + } + + internal void Invoke(Widget w) + { + var nativeCb = TryGetDelegate(cb); + var inv = new GtkSharp.CallbackInvoker(nativeCb, data); + inv.Handler?.Invoke(w); + } + } + + static void ForAll_cb(IntPtr inst, bool include_internals, IntPtr cb, IntPtr data) + { + try { + //GtkContainer's unmanaged dispose calls forall, but by that time the managed object is gone + //so it couldn't do anything useful, and resurrecting it would cause a resurrection cycle. + //In that case, just chain to the native base in case it can do something. + + var fcb = new ForAllCallbackHandler(cb, data, include_internals); + if (GLib.Object.TryGetObject(inst) is Container container) { + container.ForAll(include_internals, fcb.Invoke); + } else { + gtksharp_container_base_forall(inst, include_internals, fcb.Invoke, data); + } + } catch (Exception e) { + GLib.ExceptionManager.RaiseUnhandledException(e, false); + } + } + + [GLib.DefaultSignalHandler(Type = typeof(Gtk.Container), ConnectionMethod = nameof(OverrideForAll))] + protected virtual void ForAll(bool include_internals, Callback callback) + { + InternalForAll(include_internals, callback); + } + + private void InternalForAll(bool include_internals, Callback callback) + { + if (!(callback.Target is ForAllCallbackHandler)) { + throw new ApplicationException( + $"{nameof(ForAll)} can only be called as \"base.{nameof(ForAll)}()\". Use {nameof(Forall)}() or {nameof(Foreach)}()."); + } + + gtksharp_container_base_forall(Handle, include_internals, callback, IntPtr.Zero); + } + + static ForAllNativeDelegate InternalForAllNativeDelegate(GLib.GType gtype) + { + ForAllNativeDelegate unmanaged = null; + unsafe { + IntPtr* raw_ptr = (IntPtr*) (((long) gtype.GetClassPtr()) + (long) class_abi.GetFieldOffset("forall")); + if (*raw_ptr == IntPtr.Zero) + return default; + + unmanaged = Marshal.GetDelegateForFunctionPointer(*raw_ptr); + } + + return unmanaged; + } + + + static void gtksharp_container_base_forall(IntPtr container, bool include_internals, Callback cb, IntPtr data) + { + // gtksharp_container_base_forall from gtkglue: + // Find and call the first base callback that's not the GTK# callback. The GTK# callback calls down the whole + // managed override chain, so calling it on a subclass-of-a-managed-container-subclass causes a stack overflow. + + // GtkContainerClass *parent = (GtkContainerClass *) G_OBJECT_GET_CLASS (container); + // while ((parent = g_type_class_peek_parent (parent))) { + // if (strncmp (G_OBJECT_CLASS_NAME (parent), "__gtksharp_", 11) != 0) { + // if (parent->forall) { + // (*parent->forall) (container, include_internals, cb, data); + // } + // return; + // } + // } + if (container == IntPtr.Zero) + return; + + var obj = TryGetObject(container); + var parent = obj.NativeType; + while ((parent = parent.GetBaseType()) != GLib.GType.None) { + if (parent.ToString().StartsWith("__gtksharp_")) { + continue; + } + + var forAll = InternalForAllNativeDelegate(parent); + if (forAll != default && cb.Target is ForAllCallbackHandler cbh) { + GtkSharp.CallbackWrapper cb_wrapper = new GtkSharp.CallbackWrapper(cb); + forAll(container, include_internals, cbh.cb, data); + } + + return; + } + } + } +} \ No newline at end of file diff --git a/Source/Libs/GtkSharp/Container.cs b/Source/Libs/GtkSharp/Container.cs index b63fbc691..05a72b261 100644 --- a/Source/Libs/GtkSharp/Container.cs +++ b/Source/Libs/GtkSharp/Container.cs @@ -123,40 +123,6 @@ namespace Gtk } - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate void ForallDelegate(IntPtr container, bool include_internals, IntPtr cb, IntPtr data); - - static ForallDelegate ForallOldCallback; - static ForallDelegate ForallCallback; - - public struct CallbackInvoker - { - IntPtr cb; - IntPtr data; - - internal CallbackInvoker(IntPtr cb, IntPtr data) - { - this.cb = cb; - this.data = data; - } - - internal IntPtr Data - { - get - { - return data; - } - } - - internal IntPtr Callback - { - get - { - return cb; - } - } - } - // Compatibility code for old ChildType() virtual method static IntPtr ObsoleteChildType_cb(IntPtr raw) { diff --git a/Source/Samples/Sections/Widgets/CustomWidgets/PolarFixed.cs b/Source/Samples/Sections/Widgets/CustomWidgets/PolarFixed.cs new file mode 100644 index 000000000..6a09ae770 --- /dev/null +++ b/Source/Samples/Sections/Widgets/CustomWidgets/PolarFixed.cs @@ -0,0 +1,196 @@ +// adopted from: https://github.com/mono/gtk-sharp/commits/2.99.3/sample/PolarFixed.cs +// This is a completely pointless widget, but it shows how to subclass container... + +using System; +using System.Collections.Generic; +using Gtk; +using Gdk; + +namespace Samples +{ + 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) + { + base.ForAll(include_internals, 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); + } + } + } +} \ No newline at end of file diff --git a/Source/Samples/Sections/Widgets/PolarFixedSection.cs b/Source/Samples/Sections/Widgets/PolarFixedSection.cs new file mode 100644 index 000000000..668cfafe1 --- /dev/null +++ b/Source/Samples/Sections/Widgets/PolarFixedSection.cs @@ -0,0 +1,64 @@ +using System; +using Gtk; + +namespace Samples +{ + [Section(ContentType = typeof(PolarFixed), Category = Category.Widgets)] + class PolarFixedSection : ListSection + { + public PolarFixedSection() + { + AddItem(CreateClock()); + AddItem(CreateSpiral()); + } + + public (string, Widget) CreateClock() + { + uint r; + double theta; + + + // Clock + PolarFixed pf = new PolarFixed(); + + 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, 50); + } + + return ("Clock", pf); + } + + public (string, Widget) CreateSpiral() + { + uint r; + double theta; + + var pf = new PolarFixed(); + + + r = 0; + theta = 0.0; + + foreach (string id in Gtk.Stock.ListIds()) { + StockItem item = Gtk.Stock.Lookup(id); + if (item.Label == null) + continue; + var icon = Gtk.Image.NewFromIconName(item.StockId, IconSize.SmallToolbar); + + pf.Put(icon, theta, r); + + // Logarithmic spiral: r = a*e^(b*theta) + r += 1; + theta = 10 * Math.Log(10 * r); + } + + return ("Spiral", pf); + } + } +} \ No newline at end of file