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."); + } + } + } +}