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 =
+ ""
+ " "
+ "";
+
+ 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();
}