diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c index fe15875d17..d6a435f16b 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 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, ¶meter.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 6e3aea3469..8b2182d801 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 341d19f3ea..032d84d142 100644 --- a/gtk/gtkbuilder.rng +++ b/gtk/gtkbuilder.rng @@ -98,6 +98,21 @@ + + + + + + + + + + + + + + + diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c index cab2176916..b56adc8010 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 b653373be7..9941121c84 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 43a0441606..c6a974f95b 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 = + "" + " " + " " + " " + " True" + " vertical" + " " + " " + " false" + " " + " " + " " + " " + " false" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + 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(); }