Hi,I have added a new feature to Gtk# which makes it possible to register managed properties within the GObject type system.
A scenario where this feature proves really useful is when developing custom cell renderers and you want to set an attribute mapping to make it display the data of a tree model. So after having the attached patch applied(with "patch -p0 > patchfile" from the gtk-sharp svn directory) it is possible to write
MyTreeView.AppendColumn("European article number", EANRenderer, "ean", 0); or MyColumn.AddAttribute(EANRenderer, "ean", 0); And in the cell renderer you could write: [GLib.RegisterPropertyAttribute("ean")] public System.Int64 EAN { get { // .... } set { // .... } }The patch was really a lot of work(although it is not as much new code as I have expected) and I would be great if you commit it to SVN or give me any feedback on the patch. I have also included a demo application with MonoDevelop which is attached, too.
Christian Hoff
CellRendererAttributeTest.tar.gz
Description: application/gzip
diff -Naur ../gtk-sharp-modified/glib/glue/object.c ./glib/glue/object.c --- ../gtk-sharp-modified/glib/glue/object.c 2008-05-25 12:09:44.000000000 +0200 +++ ./glib/glue/object.c 2008-05-25 19:31:00.000000000 +0200 @@ -18,12 +18,12 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ - #include <glib-object.h> - /* 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 type, const gchar *name, guint property_id, GType property_type, gboolean CanRead, gboolean CanWrite); /* */ int @@ -53,3 +53,105 @@ return result; } +// Override the default get_property and set_property accessors of the GObject class +void +gtksharp_override_property_handlers(GType type, gpointer get_property_cb, gpointer set_property_cb) { + // Peek the object's class structure + 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 type, const gchar *name, guint property_id, GType property_type, gboolean CanRead, gboolean CanWrite) { + // Peek the object's class structure + GObjectClass *type_class = g_type_class_peek (type); + if(!type_class) + type_class = g_type_class_ref (type); + // Create the param flags of the property + // Specifying G_PARAM_CONSTRUCT is neccessary; otherwise gobject would set the property to a default value + GParamFlags property_flags = 0; + if(CanRead) + property_flags |= G_PARAM_READABLE; + if(CanWrite) + property_flags |= G_PARAM_WRITABLE; + + + gchar* property_nick = g_strconcat ("Access ", name, NULL); + gchar* property_blurb = g_strconcat ("To cut it short: I don't know anything about ", name, NULL); + + /* 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 + */ + GParamSpec *param_spec; + + switch(property_type) { + case G_TYPE_CHAR: + param_spec = g_param_spec_char (name, property_nick, property_blurb, G_MININT8, G_MAXINT8, 0, property_flags); + break; + case G_TYPE_UCHAR: + param_spec = g_param_spec_uchar (name, property_nick, property_blurb, 0, G_MAXUINT8, 0, property_flags); + break; + case G_TYPE_BOOLEAN: + param_spec = g_param_spec_boolean (name, property_nick, property_blurb, FALSE, property_flags); + break; + case G_TYPE_INT: + param_spec = g_param_spec_int (name, property_nick, property_blurb, G_MININT, G_MAXINT, 0, property_flags); + break; + case G_TYPE_UINT: + param_spec = g_param_spec_uint (name, property_nick, property_blurb, 0, G_MAXUINT, 0, property_flags); + break; + case G_TYPE_LONG: + param_spec = g_param_spec_long (name, property_nick, property_blurb, G_MINLONG, G_MAXLONG, 0, property_flags); + break; + case G_TYPE_ULONG: + param_spec = g_param_spec_ulong (name, property_nick, property_blurb, 0, G_MAXULONG, 0, property_flags); + break; + case G_TYPE_INT64: + param_spec = g_param_spec_int64 (name, property_nick, property_blurb, G_MININT64, G_MAXINT64, 0, property_flags); + break; + case G_TYPE_UINT64: + param_spec = g_param_spec_uint64 (name, property_nick, property_blurb, 0, G_MAXUINT64, 0, property_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, property_nick, property_blurb, G_MINFLOAT, G_MAXFLOAT, 0, property_flags); + break; + case G_TYPE_DOUBLE: + param_spec = g_param_spec_double (name, property_nick, property_blurb, G_MINDOUBLE, G_MAXDOUBLE, 0, property_flags); + break; + case G_TYPE_STRING: + param_spec = g_param_spec_string (name, property_nick, property_blurb, NULL, property_flags); + break; + case G_TYPE_POINTER: + param_spec = g_param_spec_pointer (name, property_nick, property_blurb, property_flags); + break; + case G_TYPE_BOXED: + param_spec = g_param_spec_boxed (name, property_nick, property_blurb, property_type, property_flags); + break; + case G_TYPE_PARAM: + break; + case G_TYPE_OBJECT: + param_spec = g_param_spec_object (name, property_nick, property_blurb, property_type, property_flags); + break; + default: + // The type not supported + return NULL; + } + g_free (property_nick); + g_free (property_blurb); + + g_object_class_install_property (type_class, property_id, param_spec); + return param_spec; +} diff -Naur ../gtk-sharp-modified/glib/Makefile.am ./glib/Makefile.am --- ../gtk-sharp-modified/glib/Makefile.am 2008-05-25 12:09:45.000000000 +0200 +++ ./glib/Makefile.am 2008-05-25 19:17:42.000000000 +0200 @@ -55,6 +55,7 @@ ObjectManager.cs \ Opaque.cs \ PropertyAttribute.cs \ + RegisterPropertyAttribute.cs \ Signal.cs \ SignalArgs.cs \ SignalAttribute.cs \ diff -Naur ../gtk-sharp-modified/glib/Object.cs ./glib/Object.cs --- ../gtk-sharp-modified/glib/Object.cs 2008-05-25 12:09:45.000000000 +0200 +++ ./glib/Object.cs 2008-05-25 19:21:40.000000000 +0200 @@ -173,6 +173,70 @@ minfo.Invoke (null, parms); } } + + /* This hashtable holds all the properties that have been registered in the native vtable + * + * Key: The pointer to the ParamSpec of the property + * Value: The corresponding PropertyInfo object + */ + static Hashtable VTableProperties = new Hashtable(); + + [GLib.CDeclCallback] + delegate void GetPropertyDelegate (IntPtr GObject, uint property_id, ref GLib.Value value, IntPtr pspec); + + [GLib.CDeclCallback] + delegate void SetPropertyDelegate (IntPtr GObject, uint property_id, ref GLib.Value value, IntPtr pspec); + + [DllImport ("glibsharpglue-2")] + static extern void gtksharp_override_property_handlers(IntPtr type, GetPropertyDelegate get_property_cb, SetPropertyDelegate set_property_cb); + + [DllImport ("glibsharpglue-2")] + static extern IntPtr gtksharp_register_property(IntPtr type, IntPtr name, uint property_id, IntPtr property_type, bool CanRead, bool CanWrite); + + private static void RegisterVTableProperties(GType gtype, System.Type t) + { + if(!typeof(GLib.Object).IsAssignableFrom(t)) + // Class does not derive from GLib.Object -> No properties can be registered + return; + + // Range of allowed property IDs in Gtk starts with 1, not with zero! + uint property_index = 1; + + bool property_handlers_overridden = false; + foreach (PropertyInfo pinfo in t.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly)) { + foreach (object attr in pinfo.GetCustomAttributes (typeof (RegisterPropertyAttribute), false)) { + RegisterPropertyAttribute vtable_attr = attr as RegisterPropertyAttribute; + 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))); + + if(!property_handlers_overridden) { + gtksharp_override_property_handlers(gtype.Val, GetPropertyCallback, SetPropertyCallback); + property_handlers_overridden = true; + } + + IntPtr VTableNameNative = GLib.Marshaller.StringToPtrGStrdup(vtable_attr.VTableName); + IntPtr ParamSpec = gtksharp_register_property(gtype.Val, VTableNameNative, property_index, ((GType) pinfo.PropertyType).Val, pinfo.CanRead, pinfo.CanWrite); + GLib.Marshaller.Free(VTableNameNative); + if(ParamSpec == IntPtr.Zero) + // The GType of the property is not supported + throw(new InvalidOperationException(String.Format("GLib.RegisterPropertyAttribute cannot be applied to property {0} of type {1} because the return type of the property is not supported", pinfo.Name, t.FullName))); + + VTableProperties.Add(ParamSpec, pinfo); + property_index++; + break; + } + } + } + + private static void GetPropertyCallback(IntPtr GObject, uint property_id, ref GLib.Value value, IntPtr ParamSpec) { + GLib.Object obj = GLib.Object.GetObject(GObject, false); + value.Val = (VTableProperties[ParamSpec] as PropertyInfo).GetValue(obj, new System.Object[0]); + } + + private static void SetPropertyCallback(IntPtr GObject, uint property_id, ref GLib.Value value, IntPtr ParamSpec) { + GLib.Object obj = GLib.Object.GetObject(GObject, false); + (VTableProperties[ParamSpec] as PropertyInfo).SetValue(obj, value.Val, new System.Object[0]); + } [DllImport("libgobject-2.0-0.dll")] static extern void g_type_add_interface_static (IntPtr gtype, IntPtr iface_type, ref GInterfaceInfo info); @@ -227,6 +291,7 @@ GType gtype = new GType (gtksharp_register_type (native_name, parent_gtype.Val)); GLib.Marshaller.Free (native_name); GLib.GType.Register (gtype, t); + RegisterVTableProperties(gtype, t); ConnectDefaultHandlers (gtype, t); InvokeClassInitializers (gtype, t); AddInterfaces (gtype, t); diff -Naur ../gtk-sharp-modified/glib/RegisterPropertyAttribute.cs ./glib/RegisterPropertyAttribute.cs --- ../gtk-sharp-modified/glib/RegisterPropertyAttribute.cs 1970-01-01 01:00:00.000000000 +0100 +++ ./glib/RegisterPropertyAttribute.cs 2008-05-21 14:59:44.000000000 +0200 @@ -0,0 +1,42 @@ +// RegisterPropertyAttribute.cs - Attribute which can be applied to properties that are to be registered in the gtype's vtable +// +// Copyright (c) 2008 Christian Hoff +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of version 2 of the Lesser GNU General +// Public License as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place - Suite 330, +// Boston, MA 02111-1307, USA. + + +namespace GLib { + + using System; + + [AttributeUsage (AttributeTargets.Property)] + public sealed class RegisterPropertyAttribute : Attribute { + string vtable_name; + + public RegisterPropertyAttribute (string vtable_name) + { + this.VTableName = vtable_name; + } + + public string VTableName { + get { + return vtable_name; + } + set { + vtable_name = value; + } + } + } +}
_______________________________________________ Gtk-sharp-list maillist - Gtk-sharp-list@lists.ximian.com http://lists.ximian.com/mailman/listinfo/gtk-sharp-list