From 9864a0960d1b806b4d9dc8743e7258a0720b894d Mon Sep 17 00:00:00 2001 From: Mike Kestner Date: Fri, 6 Jun 2008 16:55:00 +0000 Subject: [PATCH] 2008-06-06 Mike Kestner Initial Patch submitted by Christian Hoff with some small style alterations and a round trip sample by me. Supports the registration of managed properties with the GType system, so that things like custom cell renderers can be accessed via the native property system from treeview. * glib/glue/object.c : property registration related glue. * glib/Object.cs: implement managed property registration. * glib/PropertyAttribute.cs: add new props and ctor for managed property registration. * sample/PropertyRegistration.cs: little test app to test round- tripping of registered properties. * sample/Makefile.am: add new sample. svn path=/trunk/gtk-sharp/; revision=105177 --- ChangeLog | 16 ++++++ glib/Object.cs | 90 ++++++++++++++++++++++++++++++++ glib/PropertyAttribute.cs | 28 ++++++++++ glib/glue/object.c | 93 ++++++++++++++++++++++++++++++++++ sample/Makefile.am | 6 ++- sample/PropertyRegistration.cs | 44 ++++++++++++++++ 6 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 sample/PropertyRegistration.cs diff --git a/ChangeLog b/ChangeLog index df88a5fe3..c7ad50f9a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2008-06-06 Mike Kestner + + Initial Patch submitted by Christian Hoff with some small + style alterations and a round trip sample by me. Supports the + registration of managed properties with the GType system, so + that things like custom cell renderers can be accessed via the + native property system from treeview. + + * glib/glue/object.c : property registration related glue. + * glib/Object.cs: implement managed property registration. + * glib/PropertyAttribute.cs: add new props and ctor for managed + property registration. + * sample/PropertyRegistration.cs: little test app to test round- + tripping of registered properties. + * sample/Makefile.am: add new sample. + 2008-06-06 Mike Kestner * atk/Object.custom: use 'as StateSet' instead of cast to avoid diff --git a/glib/Object.cs b/glib/Object.cs index e22453376..4fc91683f 100644 --- a/glib/Object.cs +++ b/glib/Object.cs @@ -173,6 +173,95 @@ namespace GLib { minfo.Invoke (null, parms); } } + + // Key: The pointer to the ParamSpec of the property + // Value: The corresponding PropertyInfo object + static Hashtable properties; + static Hashtable Properties { + get { + if (properties == null) + properties = new Hashtable (); + return properties; + } + } + + [DllImport ("glibsharpglue-2")] + static extern void gtksharp_override_property_handlers (IntPtr type, GetPropertyDelegate get_cb, SetPropertyDelegate set_cb); + + [DllImport ("glibsharpglue-2")] + static extern IntPtr gtksharp_register_property (IntPtr type, IntPtr name, IntPtr nick, IntPtr blurb, uint property_id, IntPtr property_type, bool can_read, bool can_write); + + static void AddProperties (GType gtype, System.Type t) + { + uint idx = 1; + + bool handlers_overridden = false; + foreach (PropertyInfo pinfo in t.GetProperties (BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)) { + foreach (object attr in pinfo.GetCustomAttributes (typeof (PropertyAttribute), false)) { + if(pinfo.GetIndexParameters().Length > 0) + throw(new InvalidOperationException(String.Format("GLib.RegisterPropertyAttribute cannot be applied to property {0} of type {1} because the property expects one or more indexed parameters", pinfo.Name, t.FullName))); + + PropertyAttribute property_attr = attr as PropertyAttribute; + if (!handlers_overridden) { + gtksharp_override_property_handlers (gtype.Val, GetPropertyHandler, SetPropertyHandler); + handlers_overridden = true; + } + + IntPtr native_name = GLib.Marshaller.StringToPtrGStrdup (property_attr.Name); + IntPtr native_nick = GLib.Marshaller.StringToPtrGStrdup (property_attr.Nickname); + IntPtr native_blurb = GLib.Marshaller.StringToPtrGStrdup (property_attr.Blurb); + + IntPtr param_spec = gtksharp_register_property (gtype.Val, native_name, native_nick, native_blurb, idx, ((GType) pinfo.PropertyType).Val, pinfo.CanRead, pinfo.CanWrite); + + GLib.Marshaller.Free (native_name); + GLib.Marshaller.Free (native_nick); + GLib.Marshaller.Free (native_blurb); + + if (param_spec == IntPtr.Zero) + // The GType of the property is not supported + throw new InvalidOperationException (String.Format ("GLib.PropertyAttribute cannot be applied to property {0} of type {1} because the return type of the property is not supported", pinfo.Name, t.FullName)); + + Properties.Add (param_spec, pinfo); + idx++; + } + } + } + + [GLib.CDeclCallback] + delegate void GetPropertyDelegate (IntPtr GObject, uint property_id, ref GLib.Value value, IntPtr pspec); + + static void GetPropertyCallback (IntPtr handle, uint property_id, ref GLib.Value value, IntPtr param_spec) + { + GLib.Object obj = GLib.Object.GetObject (handle, false); + value.Val = (Properties [param_spec] as PropertyInfo).GetValue (obj, new object [0]); + } + + static GetPropertyDelegate get_property_handler; + static GetPropertyDelegate GetPropertyHandler { + get { + if (get_property_handler == null) + get_property_handler = new GetPropertyDelegate (GetPropertyCallback); + return get_property_handler; + } + } + + [GLib.CDeclCallback] + delegate void SetPropertyDelegate (IntPtr GObject, uint property_id, ref GLib.Value value, IntPtr pspec); + + static void SetPropertyCallback(IntPtr handle, uint property_id, ref GLib.Value value, IntPtr param_spec) + { + GLib.Object obj = GLib.Object.GetObject (handle, false); + (Properties [param_spec] as PropertyInfo).SetValue (obj, value.Val, new object [0]); + } + + static SetPropertyDelegate set_property_handler; + static SetPropertyDelegate SetPropertyHandler { + get { + if (set_property_handler == null) + set_property_handler = new SetPropertyDelegate (SetPropertyCallback); + return set_property_handler; + } + } [DllImport("libgobject-2.0-0.dll")] static extern void g_type_add_interface_static (IntPtr gtype, IntPtr iface_type, ref GInterfaceInfo info); @@ -227,6 +316,7 @@ namespace GLib { GType gtype = new GType (gtksharp_register_type (native_name, parent_gtype.Val)); GLib.Marshaller.Free (native_name); GLib.GType.Register (gtype, t); + AddProperties (gtype, t); ConnectDefaultHandlers (gtype, t); InvokeClassInitializers (gtype, t); AddInterfaces (gtype, t); diff --git a/glib/PropertyAttribute.cs b/glib/PropertyAttribute.cs index b3dbab073..f2f0b3ab4 100644 --- a/glib/PropertyAttribute.cs +++ b/glib/PropertyAttribute.cs @@ -22,6 +22,9 @@ namespace GLib { using System; public sealed class PropertyAttribute : Attribute { + + string blurb; + string nickname; string name; public PropertyAttribute (string name) @@ -29,6 +32,22 @@ namespace GLib { this.name = name; } + public PropertyAttribute (string name, string nickname, string blurb) + { + this.name = name; + this.nickname = nickname; + this.blurb = blurb; + } + + public string Blurb { + get { + return blurb; + } + set { + blurb = value; + } + } + public string Name { get { return name; @@ -37,5 +56,14 @@ namespace GLib { name = value; } } + + public string Nickname { + get { + return nickname; + } + set { + nickname = value; + } + } } } diff --git a/glib/glue/object.c b/glib/glue/object.c index 01dc56226..484585fc0 100644 --- a/glib/glue/object.c +++ b/glib/glue/object.c @@ -24,6 +24,8 @@ /* Forward declarations */ int gtksharp_object_get_ref_count (GObject *obj); GObject *gtksharp_object_newv (GType type, gint cnt, gchar **names, GValue *vals); +void gtksharp_override_property_handlers(GType type, gpointer get_property_cb, gpointer set_property_cb); +GParamSpec *gtksharp_register_property(GType declaring_type, const gchar *name, const gchar *nick, const gchar *blurb, guint id, GType return_type, gboolean can_read, gboolean can_write); /* */ int @@ -53,3 +55,94 @@ gtksharp_object_newv (GType type, gint cnt, gchar **names, GValue *vals) return result; } +void +gtksharp_override_property_handlers(GType type, gpointer get_property_cb, gpointer set_property_cb) +{ + GObjectClass *type_class = g_type_class_peek (type); + if(!type_class) + type_class = g_type_class_ref (type); + + type_class->get_property = get_property_cb; + type_class->set_property = set_property_cb; +} + +GParamSpec * +gtksharp_register_property(GType declaring_type, const gchar *name, const gchar *nick, const gchar *blurb, guint id, GType return_type, gboolean can_read, gboolean can_write) +{ + GParamSpec *param_spec; + GParamFlags flags = 0; + GObjectClass *declaring_class = g_type_class_peek (declaring_type); + if (!declaring_class) + declaring_class = g_type_class_ref (declaring_type); + if (can_read) + flags |= G_PARAM_READABLE; + if (can_write) + flags |= G_PARAM_WRITABLE; + + /* Create the ParamSpec for the property + * These are used to hold default values and to validate values + * Both is not needed since the check for invalid values takes place in the managed set accessor of the property and properties do not + * contain default values. Therefore the ParamSpecs must allow every value that can be assigned to the property type. + * Furthermore the default value that is specified in the constructors will never be used and assigned to the property; + * they are not relevant, but have to be passed + */ + + switch (return_type) { + case G_TYPE_CHAR: + param_spec = g_param_spec_char (name, nick, blurb, G_MININT8, G_MAXINT8, 0, flags); + break; + case G_TYPE_UCHAR: + param_spec = g_param_spec_uchar (name, nick, blurb, 0, G_MAXUINT8, 0, flags); + break; + case G_TYPE_BOOLEAN: + param_spec = g_param_spec_boolean (name, nick, blurb, FALSE, flags); + break; + case G_TYPE_INT: + param_spec = g_param_spec_int (name, nick, blurb, G_MININT, G_MAXINT, 0, flags); + break; + case G_TYPE_UINT: + param_spec = g_param_spec_uint (name, nick, blurb, 0, G_MAXUINT, 0, flags); + break; + case G_TYPE_LONG: + param_spec = g_param_spec_long (name, nick, blurb, G_MINLONG, G_MAXLONG, 0, flags); + break; + case G_TYPE_ULONG: + param_spec = g_param_spec_ulong (name, nick, blurb, 0, G_MAXULONG, 0, flags); + break; + case G_TYPE_INT64: + param_spec = g_param_spec_int64 (name, nick, blurb, G_MININT64, G_MAXINT64, 0, flags); + break; + case G_TYPE_UINT64: + param_spec = g_param_spec_uint64 (name, nick, blurb, 0, G_MAXUINT64, 0, flags); + break; + /* case G_TYPE_ENUM: + * case G_TYPE_FLAGS: + * TODO: Implement both G_TYPE_ENUM and G_TYPE_FLAGS + * My problem: Both g_param_spec_enum and g_param_spec_flags expect default property values and the members of the enum seemingly cannot be enumerated + */ + case G_TYPE_FLOAT: + param_spec = g_param_spec_float (name, nick, blurb, G_MINFLOAT, G_MAXFLOAT, 0, flags); + break; + case G_TYPE_DOUBLE: + param_spec = g_param_spec_double (name, nick, blurb, G_MINDOUBLE, G_MAXDOUBLE, 0, flags); + break; + case G_TYPE_STRING: + param_spec = g_param_spec_string (name, nick, blurb, NULL, flags); + break; + case G_TYPE_POINTER: + param_spec = g_param_spec_pointer (name, nick, blurb, flags); + break; + case G_TYPE_BOXED: + param_spec = g_param_spec_boxed (name, nick, blurb, return_type, flags); + break; + case G_TYPE_OBJECT: + param_spec = g_param_spec_object (name, nick, blurb, return_type, flags); + break; + default: + // The property's return type is not supported + return NULL; + } + + g_object_class_install_property (declaring_class, id, param_spec); + return param_spec; +} diff --git a/sample/Makefile.am b/sample/Makefile.am index a01c23b43..a786ca453 100755 --- a/sample/Makefile.am +++ b/sample/Makefile.am @@ -16,7 +16,7 @@ DOTNET_TARGETS= DOTNET_ASSEMBLY= endif -TARGETS = polarfixed.exe custom-widget.exe custom-cellrenderer.exe gtk-hello-world.exe button.exe calendar.exe subclass.exe menu.exe size.exe scribble.exe scribble-xinput.exe treeviewdemo.exe managedtreeviewdemo.exe nodeviewdemo.exe treemodeldemo.exe testdnd.exe actions.exe spawn.exe assistant.exe $(GLADE_TARGETS) $(DOTNET_TARGETS) +TARGETS = polarfixed.exe custom-widget.exe custom-cellrenderer.exe gtk-hello-world.exe button.exe calendar.exe subclass.exe menu.exe size.exe scribble.exe scribble-xinput.exe treeviewdemo.exe managedtreeviewdemo.exe nodeviewdemo.exe treemodeldemo.exe testdnd.exe actions.exe spawn.exe assistant.exe registerprop.exe $(GLADE_TARGETS) $(DOTNET_TARGETS) DEBUGS = $(addsuffix .mdb, $(TARGETS)) @@ -97,6 +97,9 @@ spawn.exe: $(srcdir)/SpawnTests.cs $(assemblies) assistant.exe: $(srcdir)/Assistant.cs $(assemblies) $(CSC) /out:assistant.exe $(references) $(srcdir)/Assistant.cs +registerprop.exe: $(srcdir)/PropertyRegistration.cs $(assemblies) + $(CSC) /out:registerprop.exe $(references) $(srcdir)/PropertyRegistration.cs + EXTRA_DIST = \ HelloWorld.cs \ Assistant.cs \ @@ -122,5 +125,6 @@ EXTRA_DIST = \ cairo-sample.exe.config \ CustomWidget.cs \ Actions.cs \ + PropertyRegistration.cs \ PolarFixed.cs diff --git a/sample/PropertyRegistration.cs b/sample/PropertyRegistration.cs new file mode 100644 index 000000000..e609b23b9 --- /dev/null +++ b/sample/PropertyRegistration.cs @@ -0,0 +1,44 @@ +// PropertyRegistration.cs - GObject property registration sample +// +// Author: Mike Kestner +// +// Copyright (c) 2008 Novell, Inc. + +namespace GtkSamples { + + using System; + + public class TestObject : GLib.Object { + + public static int Main (string[] args) + { + GLib.GType.Init (); + TestObject obj = new TestObject (); + GLib.Value val = new GLib.Value (42); + obj.SetProperty ("my_prop", val); + val.Dispose (); + if (obj.MyProp != 42) { + Console.Error.WriteLine ("Property setter did not run."); + return 1; + } + GLib.Value val2 = obj.GetProperty ("my_prop"); + if ((int)val2.Val != 42) { + Console.Error.WriteLine ("Property set/get roundtrip failed."); + return 1; + } + Console.WriteLine ("Round trip succeeded."); + return 0; + } + + int my_prop; + + [GLib.Property ("my_prop")] + public int MyProp { + get { return my_prop; } + set { + my_prop = value; + Console.WriteLine ("Property setter invoked."); + } + } + } +}