[gtk+/gtkbuilder-gbinding] (GSoC 2011) Add simple property binding syntax to GtkBuilder
- From: Denis Washington <denisw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/gtkbuilder-gbinding] (GSoC 2011) Add simple property binding syntax to GtkBuilder
- Date: Wed, 25 May 2011 22:14:08 +0000 (UTC)
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 <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 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]