Merge pull request #150 from lytico/lytico_gtkcontainer_forall
Gtk.Container: implement missing ForAll(bool include_internals, Callback callback)
This commit is contained in:
commit
f53304cd7f
4 changed files with 416 additions and 34 deletions
156
Source/Libs/GtkSharp/Container.Forall.cs
Normal file
156
Source/Libs/GtkSharp/Container.Forall.cs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
using GtkSharp;
|
||||||
|
|
||||||
|
namespace Gtk
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
public partial class Container
|
||||||
|
{
|
||||||
|
internal static D TryGetDelegate<D>(IntPtr _cb)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
var d = Marshal.GetDelegateForFunctionPointer<D>(_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// sets callback forall in GtkContainerClass-Struct
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gtype"></param>
|
||||||
|
/// <param name="callback"></param>
|
||||||
|
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<CallbackNative>(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<ForAllNativeDelegate>(*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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
// Compatibility code for old ChildType() virtual method
|
||||||
static IntPtr ObsoleteChildType_cb(IntPtr raw)
|
static IntPtr ObsoleteChildType_cb(IntPtr raw)
|
||||||
{
|
{
|
||||||
|
|
196
Source/Samples/Sections/Widgets/CustomWidgets/PolarFixed.cs
Normal file
196
Source/Samples/Sections/Widgets/CustomWidgets/PolarFixed.cs
Normal file
|
@ -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<PolarFixedChild> children;
|
||||||
|
|
||||||
|
public PolarFixed()
|
||||||
|
{
|
||||||
|
children = new List<PolarFixedChild>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
Source/Samples/Sections/Widgets/PolarFixedSection.cs
Normal file
64
Source/Samples/Sections/Widgets/PolarFixedSection.cs
Normal file
|
@ -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("<big><b>" + hour.ToString() + "</b></big>");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue