[gtk+/gtkbuilder-gbinding] (GSoC 2011) Add simple property binding syntax to GtkBuilder



commit fe50aa8d048b0207cd80b5ba6f3510bde96911d7
Author: Denis Washington <denisw online de>
Date:   Wed May 25 23:59:17 2011 +0200

    (GSoC 2011) Add simple property binding syntax to GtkBuilder
    
    This is for my Google Summer of Code project, "GObject property binding
    support in GtkBuilder and Glade".
    (http://live.gnome.org/DenisWashington_GtkBuilder).
    
    Add the possibility to add <binding> tags to GtkBuilder object definitions
    which allow you to specify bindings between properties, i.e. that the value
    of one object property is always automatically updated to mirror the value of
    another (usually of another object) - a.k.a. GBinding.
    (http://developer.gnome.org/gobject/unstable/GBinding.html#GBindingFlags)
    
    So far only the simplest kinds of property bindings are implemented -
    no special GBinding flags or transformation functions are supported.
    (G_BINDING_SYNC_CREATE is always passed, however.)

 gtk/gtkbuilder.c        |  114 ++++++++++++++++++++++++++++++++++++++---------
 gtk/gtkbuilderparser.c  |   87 ++++++++++++++++++++++++++++++++++++
 gtk/gtkbuilderprivate.h |   11 +++++
 gtk/tests/builder.c     |   49 ++++++++++++++++++++
 4 files changed, 239 insertions(+), 22 deletions(-)
---
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index 30fed90..e352613 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -65,9 +65,10 @@
  * </para>
  * <programlisting><![CDATA[
  * <!ELEMENT interface (requires|object)* >
- * <!ELEMENT object    (property|signal|child|ANY)* >
+ * <!ELEMENT object    (property|signal|child|binding|ANY)* >
  * <!ELEMENT property  PCDATA >
  * <!ELEMENT signal    EMPTY >
+ * <!ELEMENT binding   EMPTY >
  * <!ELEMENT requires  EMPTY >
  * <!ELEMENT child     (object|ANY*) >
  *
@@ -90,6 +91,9 @@
  *                      last_modification_time #IMPLIED >
  * <!ATTLIST child      type           	    #IMPLIED
  *                      internal-child 	    #IMPLIED >
+ * <!ATTLIST binding    to                  #REQUIRED
+ *                      from                #REQUIRED
+ *                      source              #REQUIRED >
  * ]]></programlisting>
  * <para>
  * The toplevel element is &lt;interface&gt;. It optionally takes a "domain"
@@ -155,6 +159,13 @@
  * an object has to be constructed before it can be used as the value of a
  * construct-only property.
  *
+ * It is also possible to define the value of a property by binding it to
+ * another property with the &lt;binding&gt; element. This causes the value
+ * of the property specified with the "to" attribute to be whatever the
+ * property "from" of object "source" is set to, even if that property
+ * is later set to another value. See the documentation of GLib's GBinding
+ * documentation for more details about the property binding mechanism.
+ *
  * Signal handlers are set up with the &lt;signal&gt; element. The "name"
  * attribute specifies the name of the signal, and the "handler" attribute
  * specifies the function to connect to the signal. By default, GTK+ tries to
@@ -285,6 +296,7 @@ struct _GtkBuilderPrivate
   GHashTable *objects;
   GSList *delayed_properties;
   GSList *signals;
+  GSList *bindings;
   gchar *filename;
 };
 
@@ -782,15 +794,59 @@ _gtk_builder_add_signals (GtkBuilder *builder,
                                            g_slist_copy (signals));
 }
 
+void
+_gtk_builder_add_bindings (GtkBuilder *builder,
+			   GSList     *bindings)
+{
+  builder->priv->bindings = g_slist_concat (builder->priv->bindings,
+					    g_slist_copy (bindings));
+}
+
+static GObject *
+gtk_builder_lookup_object (GtkBuilder *builder,
+			   gchar      *object_name)
+{
+  GObject *object;
+
+  object = g_hash_table_lookup (builder->priv->objects, object_name);
+  if (!object)
+    g_warning ("No object called: %s", object_name);
+  
+  return object;
+}
+
+static GParamSpec *
+gtk_builder_lookup_property (GtkBuilder *builder,
+			     GObject    *object,
+			     gchar      *property_name)
+{
+  GType object_type;
+  GObjectClass *oclass;
+  GParamSpec *pspec;
+  
+  object_type = G_OBJECT_TYPE (object);
+  g_assert (object_type != G_TYPE_INVALID);
+
+  oclass = g_type_class_ref (object_type);
+  g_assert (oclass != NULL);
+
+  pspec = g_object_class_find_property (G_OBJECT_CLASS (oclass),
+					property_name);
+  if (!pspec)
+    g_warning ("Unknown property: %s.%s",
+	       g_type_name (object_type),
+	       property_name);
+  
+  g_type_class_unref (oclass);
+  return pspec;
+}
+
 static void
 gtk_builder_apply_delayed_properties (GtkBuilder *builder)
 {
   GSList *l, *props;
   DelayedProperty *property;
   GObject *object;
-  GType object_type;
-  GObjectClass *oclass;
-  GParamSpec *pspec;
 
   /* take the list over from the builder->priv.
    *
@@ -803,43 +859,57 @@ gtk_builder_apply_delayed_properties (GtkBuilder *builder)
   for (l = props; l; l = l->next)
     {
       property = (DelayedProperty*)l->data;
-      object = g_hash_table_lookup (builder->priv->objects, property->object);
+      object = gtk_builder_lookup_object (builder, property->object);
       g_assert (object != NULL);
 
-      object_type = G_OBJECT_TYPE (object);
-      g_assert (object_type != G_TYPE_INVALID);
-
-      oclass = g_type_class_ref (object_type);
-      g_assert (oclass != NULL);
-
-      pspec = g_object_class_find_property (G_OBJECT_CLASS (oclass),
-                                            property->name);
-      if (!pspec)
-        g_warning ("Unknown property: %s.%s", g_type_name (object_type),
-                   property->name);
-      else
+      if (gtk_builder_lookup_property (builder, object, property->name))
         {
           GObject *obj;
 
-          obj = g_hash_table_lookup (builder->priv->objects, property->value);
-          if (!obj)
-            g_warning ("No object called: %s", property->value);
-          else
+          obj = gtk_builder_lookup_object (builder, property->value);
+          if (obj)
             g_object_set (object, property->name, obj, NULL);
         }
+      
       g_free (property->value);
       g_free (property->object);
       g_free (property->name);
       g_slice_free (DelayedProperty, property);
-      g_type_class_unref (oclass);
     }
   g_slist_free (props);
 }
 
+static void
+gtk_builder_create_bindings (GtkBuilder *builder)
+{
+  GSList *l;
+
+  for (l = builder->priv->bindings; l; l = l->next)
+    {
+      BindingInfo *binding = (BindingInfo*)l->data;
+      GObject *target, *source;
+
+      target = gtk_builder_lookup_object (builder, binding->object_name);
+      g_assert (target != NULL);
+
+      source = gtk_builder_lookup_object (builder, binding->source);
+      if (source)
+	{
+	  g_object_bind_property (source, binding->from,
+				  target, binding->to,
+				  G_BINDING_SYNC_CREATE);
+	}
+    }
+
+  g_slist_free (builder->priv->bindings);
+  builder->priv->bindings = NULL;
+}
+
 void
 _gtk_builder_finish (GtkBuilder *builder)
 {
   gtk_builder_apply_delayed_properties (builder);
+  gtk_builder_create_bindings (builder);
 }
 
 /**
diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c
index 5ea15e3..dc8d773 100644
--- a/gtk/gtkbuilderparser.c
+++ b/gtk/gtkbuilderparser.c
@@ -534,6 +534,67 @@ parse_property (ParserData   *data,
 }
 
 static void
+parse_binding (ParserData   *data,
+               const gchar  *element_name,
+               const gchar **names,
+               const gchar **values,
+               GError      **error)
+{
+  BindingInfo *info;
+  gchar *to = NULL;
+  gchar *from = NULL;
+  gchar *source = NULL;
+  ObjectInfo *object_info;
+  int i;
+  
+  object_info = state_peek_info (data, ObjectInfo);
+  if (!object_info || strcmp (object_info->tag.name, "object") != 0)
+    {
+      error_invalid_tag (data, element_name, NULL, error);
+      return;
+    }
+
+  for (i = 0; names[i] != NULL; i++)
+    {
+      if (strcmp (names[i], "to") == 0)
+	to = g_strdup (values[i]);
+      else if (strcmp (names[i], "from") == 0)
+	from = g_strdup (values[i]);
+      else if (strcmp (names[i], "source") == 0)
+	source = g_strdup (values[i]);
+      else
+	{
+	  error_invalid_attribute (data, element_name, names[i], error);
+	  return;
+	}
+    }
+
+  if (!to)
+    {
+      error_missing_attribute (data, element_name, "to", error);
+      return;
+    }
+  if (!from)
+    {
+      error_missing_attribute (data, element_name, "from", error);
+      return;
+    }
+  if (!source)
+    {
+      error_missing_attribute (data, element_name, "source", error);
+      return;
+    }
+
+  info = g_slice_new0 (BindingInfo);
+  info->to = to;
+  info->from = from;
+  info->source = source;
+  state_push (data, info);
+
+  info->tag.name = element_name;
+}
+
+static void
 free_property_info (PropertyInfo *info)
 {
   g_free (info->data);
@@ -879,6 +940,8 @@ start_element (GMarkupParseContext *context,
     parse_child (data, element_name, names, values, error);
   else if (strcmp (element_name, "property") == 0)
     parse_property (data, element_name, names, values, error);
+  else if (strcmp (element_name, "binding") == 0)
+    parse_binding (data, element_name, names, values, error);
   else if (strcmp (element_name, "signal") == 0)
     parse_signal (data, element_name, names, values, error);
   else if (strcmp (element_name, "interface") == 0)
@@ -990,6 +1053,7 @@ end_element (GMarkupParseContext *context,
           GTK_BUILDABLE_GET_IFACE (object_info->object)->parser_finished)
         data->finalizers = g_slist_prepend (data->finalizers, object_info->object);
       _gtk_builder_add_signals (data->builder, object_info->signals);
+      _gtk_builder_add_bindings (data->builder, object_info->bindings);
 
       free_object_info (object_info);
     }
@@ -1038,6 +1102,29 @@ end_element (GMarkupParseContext *context,
       object_info->signals =
         g_slist_prepend (object_info->signals, signal_info);
     }
+  else if (strcmp (element_name, "binding") == 0)
+    {
+      BindingInfo *binding_info = state_pop_info (data, BindingInfo);
+      ObjectInfo *object_info = (ObjectInfo*)state_peek_info (data, CommonInfo);
+      GSList *l;
+      
+      for (l = object_info->bindings; l; l = l->next)
+        {
+          BindingInfo *b = (BindingInfo*)l->data;
+          if (strcmp (b->to, binding_info->to) != 0)
+            {
+              g_set_error (error,
+                           GTK_BUILDER_ERROR,
+                           GTK_BUILDER_ERROR_INVALID_VALUE,
+                           "Duplicate binding for property: `%s'",
+                           b->to);
+            }
+        }
+      
+      binding_info->object_name = g_strdup (object_info->id);
+      object_info->bindings =
+        g_slist_prepend (object_info->bindings, binding_info);
+    }
   else if (strcmp (element_name, "placeholder") == 0)
     {
     }
diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h
index e633a28..cb75c94 100644
--- a/gtk/gtkbuilderprivate.h
+++ b/gtk/gtkbuilderprivate.h
@@ -38,6 +38,7 @@ typedef struct {
   gchar *constructor;
   GSList *properties;
   GSList *signals;
+  GSList *bindings;
   GObject *object;
   CommonInfo *parent;
 } ObjectInfo;
@@ -64,6 +65,14 @@ typedef struct {
 typedef struct {
   TagInfo tag;
   gchar *object_name;
+  gchar *to;
+  gchar *from;
+  gchar *source;
+} BindingInfo;
+
+typedef struct {
+  TagInfo tag;
+  gchar *object_name;
   gchar *name;
   gchar *handler;
   GConnectFlags flags;
@@ -121,6 +130,8 @@ void      _gtk_builder_add (GtkBuilder *builder,
                             ChildInfo *child_info);
 void      _gtk_builder_add_signals (GtkBuilder *builder,
 				    GSList     *signals);
+void      _gtk_builder_add_bindings (GtkBuilder  *builder,
+				     GSList      *bindings);
 void      _gtk_builder_finish (GtkBuilder *builder);
 void _free_signal_info (SignalInfo *info,
                         gpointer user_data);
diff --git a/gtk/tests/builder.c b/gtk/tests/builder.c
index 6da3279..b5c9c87 100644
--- a/gtk/tests/builder.c
+++ b/gtk/tests/builder.c
@@ -2572,6 +2572,54 @@ test_message_area (void)
   g_object_unref (builder);
 }
 
+static void
+test_property_bindings (void)
+{
+  const gchar *buffer =
+    "<interface>"
+    "  <object class=\"GtkWindow\" id=\"window\">"
+    "    <child>"
+    "      <object class=\"GtkVBox\" id=\"vbox\">"
+    "        <property name=\"visible\">True</property>"
+    "        <property name=\"orientation\">vertical</property>"
+    "        <child>"
+    "          <object class=\"GtkCheckButton\" id=\"checkbutton\">"
+    "            <property name=\"active\">false</property>"
+    "          </object>"
+    "        </child>"
+    "        <child>"
+    "          <object class=\"GtkButton\" id=\"button\">"
+    "            <binding to=\"sensitive\" from=\"active\" source=\"checkbutton\"/>"
+    "          </object>"
+    "        </child>"
+    "      </object>"
+    "    </child>"
+    "  </object>"
+    "</interface>";
+
+  GtkBuilder *builder;
+  GObject *checkbutton, *button, *window;
+  
+  builder = builder_new_from_string (buffer, -1, NULL);
+  
+  checkbutton = gtk_builder_get_object (builder, "checkbutton");
+  g_assert (checkbutton != NULL);
+  g_assert (GTK_IS_CHECK_BUTTON (checkbutton));
+  g_assert (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbutton)));
+
+  button = gtk_builder_get_object (builder, "button");
+  g_assert (button != NULL);
+  g_assert (GTK_IS_BUTTON (button));
+  g_assert (!gtk_widget_get_sensitive (GTK_WIDGET (button)));
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton), TRUE);
+  g_assert (gtk_widget_get_sensitive (GTK_WIDGET (button)));
+  
+  window = gtk_builder_get_object (builder, "window");
+  gtk_widget_destroy (GTK_WIDGET (window));
+  g_object_unref (builder);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -2618,6 +2666,7 @@ main (int argc, char **argv)
   g_test_add_func ("/Builder/Menus", test_menus);
   g_test_add_func ("/Builder/MessageArea", test_message_area);
   g_test_add_func ("/Builder/MessageDialog", test_message_dialog);
+  g_test_add_func ("/Builder/Property Bindings", test_property_binding);
 
   return g_test_run();
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]