diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c index 30fed9087f..e3526138b9 100644 --- a/gtk/gtkbuilder.c +++ b/gtk/gtkbuilder.c @@ -65,9 +65,10 @@ * * - * + * * * + * * * * @@ -90,6 +91,9 @@ * last_modification_time #IMPLIED > * + * * ]]> * * The toplevel element is <interface>. 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 <binding> 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 <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 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 5ea15e3c60..dc8d7738fd 100644 --- a/gtk/gtkbuilderparser.c +++ b/gtk/gtkbuilderparser.c @@ -533,6 +533,67 @@ parse_property (ParserData *data, info->tag.name = element_name; } +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) { @@ -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 e633a28b5d..cb75c94571 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; @@ -61,6 +62,14 @@ typedef struct { gchar *context; } PropertyInfo; +typedef struct { + TagInfo tag; + gchar *object_name; + gchar *to; + gchar *from; + gchar *source; +} BindingInfo; + typedef struct { TagInfo tag; gchar *object_name; @@ -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 6da3279dfe..b5c9c87386 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 = + "" + " " + " " + " " + " True" + " vertical" + " " + " " + " false" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + 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(); }