(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.)
This commit is contained in:
Denis Washington
2011-05-25 23:59:17 +02:00
parent a21042c156
commit fe50aa8d04
4 changed files with 239 additions and 22 deletions

View File

@@ -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 &lt;interface&gt;. 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 &lt;binding&gt; 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 &lt;signal&gt; 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);
}
/**

View File

@@ -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)
{
}

View File

@@ -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);

View File

@@ -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();
}