[gtk+] Added bindings support to GtkBuilder by introducing 3 new <property> attributes "bind-source" to spe



commit 887fc60ccee7b9719ebb47291682e48f00609b7b
Author: Juan Pablo Ugarte <juanpablougarte gmail com>
Date:   Fri Apr 11 16:24:04 2014 -0300

    Added bindings support to GtkBuilder by introducing 3 new <property> attributes "bind-source" to specify 
the source object of the binding "bind-property" to specify the source property and "bind-flags" to specify 
the binding flags (optional)
    
    Binding an object sensitive property with a check button active property will look like this:
    
    <object class="GtkButton" id="button">
      <property name="sensitive" bind-source="checkbutton" bind-property="active"/>
    </object>
    
    This is based on the original work done by Denis Washington for his GSoC project
    
    This closes Bug 654417 "[GSoC] Add <binding> element to GtkBuilder syntax"

 gtk/gtkbuilder.c        |   80 +++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkbuilder.rnc      |    3 ++
 gtk/gtkbuilder.rng      |   15 +++++++++
 gtk/gtkbuilderparser.c  |   51 ++++++++++++++++++++++++++---
 gtk/gtkbuilderprivate.h |   13 +++++++-
 testsuite/gtk/builder.c |   57 +++++++++++++++++++++++++++++++++
 6 files changed, 212 insertions(+), 7 deletions(-)
---
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index fe15875..d6a435f 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -134,6 +134,14 @@
  * object has to be constructed before it can be used as the value of
  * a construct-only property.
  *
+ * It is also possible to bind a property value to another object's
+ * property value using the attributes
+ * "bind-source" to specify the source object of the binding,
+ * "bind-property" to specify the source property and optionally
+ * "bind-flags" to specify the binding flags 
+ * Internally builder implement this using GBinding objects.
+ * For more information see g_object_bind_property()
+ * 
  * Signal handlers are set up with the <signal> 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
@@ -244,6 +252,7 @@ struct _GtkBuilderPrivate
   GHashTable *callbacks;
   GSList *delayed_properties;
   GSList *signals;
+  GSList *bindings;
   gchar *filename;
   gchar *resource_prefix;
   GType template_type;
@@ -499,6 +508,13 @@ gtk_builder_get_parameters (GtkBuilder  *builder,
               continue;
             }
         }
+      else if (prop->bound && (!prop->data || *prop->data == '\0'))
+        {
+          /* Ignore properties with a binding and no value since they are
+           * only there for to express the binding.
+           */
+          continue;
+        }
       else if (!gtk_builder_value_from_string (builder, pspec,
                                               prop->data, &parameter.value, &error))
         {
@@ -572,6 +588,15 @@ object_set_name (GObject *object, const gchar *name)
     g_object_set_data_full (object, "gtk-builder-name", g_strdup (name), g_free);
 }
 
+static inline const gchar *
+object_get_name (GObject *object)
+{
+  if (GTK_IS_BUILDABLE (object))
+    return gtk_buildable_get_name (GTK_BUILDABLE (object));
+  else
+    return g_object_get_data (object, "gtk-builder-name");
+}
+
 void
 _gtk_builder_add_object (GtkBuilder  *builder,
                          const gchar *id,
@@ -581,6 +606,22 @@ _gtk_builder_add_object (GtkBuilder  *builder,
   g_hash_table_insert (builder->priv->objects, g_strdup (id), g_object_ref (object));
 }
 
+static inline void
+gtk_builder_take_bindings (GtkBuilder *builder,
+                           GObject    *target,
+                           GSList     *bindings)
+{
+  GSList *l;
+
+  for (l = bindings; l; l = g_slist_next (l))
+    {
+      BindingInfo *info = l->data;
+      info->target = target;
+    }
+
+  builder->priv->bindings = g_slist_concat (builder->priv->bindings, bindings);
+}
+
 GObject *
 _gtk_builder_construct (GtkBuilder *builder,
                         ObjectInfo *info,
@@ -741,6 +782,9 @@ _gtk_builder_construct (GtkBuilder *builder,
     }
   g_array_free (parameters, TRUE);
 
+  if (info->bindings)
+    gtk_builder_take_bindings (builder, obj, info->bindings);
+
   /* put it in the hash table. */
   _gtk_builder_add_object (builder, info->id, obj);
   
@@ -909,10 +953,46 @@ gtk_builder_apply_delayed_properties (GtkBuilder *builder)
   g_slist_free (props);
 }
 
+static inline void
+free_binding_info (gpointer data, gpointer user)
+{
+  BindingInfo *info = data;
+  g_free (info->target_property);
+  g_free (info->source);
+  g_free (info->source_property);
+  g_slice_free (BindingInfo, data);
+}
+
+static inline void
+gtk_builder_create_bindings (GtkBuilder *builder)
+{
+  GSList *l;
+
+  for (l = builder->priv->bindings; l; l = g_slist_next (l))
+    {
+      BindingInfo *info = l->data;
+      GObject *source;
+
+      if ((source = gtk_builder_get_object (builder, info->source)))
+        g_object_bind_property (source, info->source_property,
+                                info->target, info->target_property,
+                                info->flags);
+      else
+        g_warning ("Could not find source object '%s' to bind property '%s'",
+                   info->source, info->source_property);
+
+      free_binding_info (info, NULL);
+    }
+
+  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/gtkbuilder.rnc b/gtk/gtkbuilder.rnc
index 6e3aea3..8b2182d 100644
--- a/gtk/gtkbuilder.rnc
+++ b/gtk/gtkbuilder.rnc
@@ -27,6 +27,9 @@ property = element property {
   attribute translatable { "yes" | "no" } ?,
   attribute comments { text } ?,
   attribute context { text } ?,
+  (attribute bind-source { text },
+   attribute bind-property { text },
+   attribute bind-flags { text } ?) ?,
   text ?
 }
 
diff --git a/gtk/gtkbuilder.rng b/gtk/gtkbuilder.rng
index 341d19f..032d84d 100644
--- a/gtk/gtkbuilder.rng
+++ b/gtk/gtkbuilder.rng
@@ -99,6 +99,21 @@
         </attribute>
       </optional>
       <optional>
+        <group>
+          <attribute name="bind-source">
+            <text/>
+          </attribute>
+          <attribute name="bind-property">
+            <text/>
+          </attribute>
+          <optional>
+            <attribute name="bind-flags">
+              <text/>
+            </attribute>
+          </optional>
+        </group>
+      </optional>
+      <optional>
         <text/>
       </optional>
     </element>
diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c
index cab2176..b56adc8 100644
--- a/gtk/gtkbuilderparser.c
+++ b/gtk/gtkbuilderparser.c
@@ -587,8 +587,11 @@ parse_property (ParserData   *data,
                 GError      **error)
 {
   PropertyInfo *info;
-  gchar *name = NULL;
-  gchar *context = NULL;
+  const gchar *name = NULL;
+  const gchar *context = NULL;
+  const gchar *bind_source = NULL;
+  const gchar *bind_property = NULL;
+  GBindingFlags bind_flags = G_BINDING_DEFAULT;
   gboolean translatable = FALSE;
   ObjectInfo *object_info;
   int i;
@@ -605,7 +608,7 @@ parse_property (ParserData   *data,
   for (i = 0; names[i] != NULL; i++)
     {
       if (strcmp (names[i], "name") == 0)
-        name = g_strdelimit (g_strdup (values[i]), "_", '-');
+        name = values[i];
       else if (strcmp (names[i], "translatable") == 0)
        {
          if (!_gtk_builder_boolean_from_string (values[i], &translatable,
@@ -618,7 +621,21 @@ parse_property (ParserData   *data,
         }
       else if (strcmp (names[i], "context") == 0) 
         {
-          context = g_strdup (values[i]);
+          context = values[i];
+        }
+      else if (strcmp (names[i], "bind-source") == 0) 
+        {
+          bind_source = values[i];
+        }
+      else if (strcmp (names[i], "bind-property") == 0) 
+        {
+          bind_property = values[i];
+        }
+      else if (strcmp (names[i], "bind-flags") == 0) 
+        {
+          if (!_gtk_builder_flags_from_string (G_TYPE_BINDING_FLAGS, values[i],
+                                               &bind_flags, error))
+            return;
         }
       else
        {
@@ -633,10 +650,30 @@ parse_property (ParserData   *data,
       return;
     }
 
+  if (bind_source && bind_property)
+    {
+      BindingInfo *binfo = g_slice_new0 (BindingInfo);
+
+      binfo->target_property = g_strdup (name);
+      binfo->source = g_strdup (bind_source);
+      binfo->source_property = g_strdup (bind_property);
+      binfo->flags = bind_flags;
+
+      object_info->bindings = g_slist_prepend (object_info->bindings, binfo);
+    }
+  else if (bind_source || bind_property)
+    {
+      error_missing_attribute (data, element_name,
+                               (bind_source) ? "bind-property" : "bind-source",
+                               error);
+      return;
+    }
+
   info = g_slice_new0 (PropertyInfo);
-  info->name = name;
+  info->name = g_strdelimit (g_strdup (name), "_", '-');
   info->translatable = translatable;
-  info->context = context;
+  info->bound = (bind_source != NULL && bind_property != NULL);
+  info->context = g_strdup (context);
   info->text = g_string_new ("");
   state_push (data, info);
 
@@ -648,6 +685,8 @@ free_property_info (PropertyInfo *info)
 {
   g_free (info->data);
   g_free (info->name);
+  g_free (info->context);
+  /* info->text is already freed */
   g_slice_free (PropertyInfo, info);
 }
 
diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h
index b653373..9941121 100644
--- a/gtk/gtkbuilderprivate.h
+++ b/gtk/gtkbuilderprivate.h
@@ -36,6 +36,7 @@ typedef struct {
   gchar *constructor;
   GSList *properties;
   GSList *signals;
+  GSList *bindings;
   GObject *object;
   CommonInfo *parent;
   gboolean applied_properties;
@@ -62,7 +63,8 @@ typedef struct {
   gchar *name;
   GString *text;
   gchar *data;
-  gboolean translatable;
+  gboolean translatable:1;
+  gboolean bound:1;
   gchar *context;
 } PropertyInfo;
 
@@ -75,6 +77,15 @@ typedef struct {
   gchar *connect_object_name;
 } SignalInfo;
 
+typedef struct
+{
+  GObject *target;
+  gchar *target_property;
+  gchar *source;
+  gchar *source_property;
+  GBindingFlags flags;
+} BindingInfo;
+
 typedef struct {
   TagInfo  tag;
   gchar   *library;
diff --git a/testsuite/gtk/builder.c b/testsuite/gtk/builder.c
index 43a0441..c6a974f 100644
--- a/testsuite/gtk/builder.c
+++ b/testsuite/gtk/builder.c
@@ -2777,6 +2777,62 @@ test_no_ids (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\">"
+    "            <property name=\"sensitive\" bind-source=\"checkbutton\" bind-property=\"active\" 
bind-flags=\"sync-create\">false</property>"
+    "          </object>"
+    "        </child>"
+    "        <child>"
+    "          <object class=\"GtkButton\" id=\"button2\">"
+    "            <property name=\"sensitive\" bind-source=\"checkbutton\" bind-property=\"active\" />"
+    "          </object>"
+    "        </child>"
+    "      </object>"
+    "    </child>"
+    "  </object>"
+    "</interface>";
+
+  GtkBuilder *builder;
+  GObject *checkbutton, *button, *button2, *window;
+  
+  builder = builder_new_from_string (buffer, -1, NULL);
+  
+  checkbutton = gtk_builder_get_object (builder, "checkbutton");
+  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 (GTK_IS_BUTTON (button));
+  g_assert (!gtk_widget_get_sensitive (GTK_WIDGET (button)));
+
+  button2 = gtk_builder_get_object (builder, "button2");
+  g_assert (GTK_IS_BUTTON (button2));
+  g_assert (gtk_widget_get_sensitive (GTK_WIDGET (button2)));
+  
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton), TRUE);
+  g_assert (gtk_widget_get_sensitive (GTK_WIDGET (button)));
+  g_assert (gtk_widget_get_sensitive (GTK_WIDGET (button2)));
+  
+  window = gtk_builder_get_object (builder, "window");
+  gtk_widget_destroy (GTK_WIDGET (window));
+  g_object_unref (builder);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -2827,6 +2883,7 @@ main (int argc, char **argv)
   g_test_add_func ("/Builder/LevelBar", test_level_bar);
   g_test_add_func ("/Builder/Expose Object", test_expose_object);
   g_test_add_func ("/Builder/No IDs", test_no_ids);
+  g_test_add_func ("/Builder/Property Bindings", test_property_bindings);
 
   return g_test_run();
 }


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