GProperty — GObject properties made easy

Implementing GObject properties may be considered cumbersome to the uninitiated, but once you’ve got a few implementations under your belt it’s actually quite repetitive boilerplate code. On top of that, if you want to provide explicit property accessors in addition to the g_object_get/set API, they need to be implemented as well, which means more of the same. It would be nice to implement the boilerplate code once for all, so here comes …

GProperty is a subclass of GParamSpec┬╣ that provides common ways of implementing properties. And by being based on GParamSpec it retains compatibility and works exactly like expected when looking at a GObject from the outside. The proof of concept implementation also provides egg_object_get_property() and egg_object_set_property() GObject methods, that dispatch property access to the respective GProperty.

Field-based properties

Probably the most common way is to store the value of a property in a field of the object’s private data blob. GProperty provides a ready-made implementation that manages access to that field. If you need to react on property changes you’d just hook up to the property-changed notification.

The following snippet implements an int and a string property backed by the respective field in the private struct. No need for a property-id enum or implementation of g_object_get/set_property().

typedef struct
{
  gint   number;
  gchar *text;
} EggTestOffsetPrivate;

static void
egg_test_offset_class_init (EggTestOffsetClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GParamSpec  *pspec;
  guint        id = 0;

  g_type_class_add_private (klass, sizeof (EggTestOffsetPrivate));

  object_class->get_property = egg_object_get_property;
  object_class->set_property = egg_object_set_property;

  pspec = egg_int_property_for_field ("number",
                                      G_STRUCT_OFFSET (EggTestOffsetPrivate, number),
                                      G_MININT32, G_MAXINT32, 1,
                                      G_PARAM_READWRITE);
  g_object_class_install_property (object_class, ++id, pspec);

  pspec = egg_string_property_for_field ("text",
                                         G_STRUCT_OFFSET (EggTestOffsetPrivate, text),
                                         NULL,
                                         G_PARAM_READWRITE);
  g_object_class_install_property (object_class, ++id, pspec);
}

Accessor-based properties

Another way of using GProperty is with accessor functions that are explicitly managing a property. The following snippet creates GProperties that are dispatching access from g_object_get/set() to the custom accessors, again saving custom g_object_get/set_property() overrides.

typedef struct
{
  gint   number;
  gchar *text;
} EggTestAccessorsPrivate;

static void
egg_test_accessors_class_init (EggTestAccessorsClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GParamSpec  *pspec;
  guint        id = 0;

  g_type_class_add_private (klass, sizeof (EggTestAccessorsPrivate));

  object_class->get_property = egg_object_get_property;
  object_class->set_property = egg_object_set_property;

  pspec = egg_int_property_for_accessors (
            "number",
            (EggIntPropertyGetterFunc) egg_test_accessors_get_number,
            (EggIntPropertySetterFunc) egg_test_accessors_set_number,
            G_MININT32, G_MAXINT32, 1,
            G_PARAM_READWRITE);
  g_object_class_install_property (object_class, ++id, pspec);

  pspec = egg_string_property_for_accessors (
            "text",
            (EggStringPropertyGetterFunc) egg_test_accessors_get_text,
            (EggStringPropertySetterFunc) egg_test_accessors_set_text,
            NULL,
            G_PARAM_READWRITE);
  g_object_class_install_property (object_class, ++id, pspec);
}

/* standard getter and setter functions omitted */

Property proxies

Maybe slightly more obscure than the above, but hopefully still useful are proxied properties. The goal is to transparently export properties of an aggregate object to the outside.

The following snippet implements a custom gtk widget using a button and spinner inside a hbox, and exports the button’s label and the spinner’s value as properties of the toplevel hbox subclass.

typedef struct
{
  GtkWidget *button;
  GtkWidget *spinner;
} EggTestProxyPrivate;

static void
egg_test_proxy_class_init (EggTestProxyClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GParamSpec  *pspec;
  guint        id = 0;

  g_type_class_add_private (klass, sizeof (EggTestProxyPrivate));

  object_class->get_property = egg_object_get_property;
  object_class->set_property = egg_object_set_property;

  pspec = egg_string_property_proxy_for_object_offset (
                      "label",
                      G_STRUCT_OFFSET (EggTestProxyPrivate, button),
                      "label",
                      NULL,
                      G_PARAM_READWRITE);
  g_object_class_install_property (object_class, ++id, pspec);

  pspec = egg_double_property_proxy_for_object_offset (
                      "value",
                      G_STRUCT_OFFSET (EggTestProxyPrivate, spinner),
                      "value",
                      0.0, 10.0, 1.0,
                      G_PARAM_READWRITE);
  g_object_class_install_property (object_class, ++id, pspec);
}

The code with full examples can be found at http://gitorious.org/egg-gobject At the moment only properties of type double, int and string are implemented, but chances for broader support are not all that bad. If feedback is positive I’d like to consider migration to libegg in gnome’s git.

┬╣ actually G<Foo>Property is a subclass of GParamSpec<Foo>

2 Responses to “GProperty — GObject properties made easy”

  1. This is great! I hope this makes it into GLib itself. I’m so bored of writing out completely standard get_property and set_property implementations.

  2. Chris Lord says:

    Sounds really cool, nice work :)