(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:
114
gtk/gtkbuilder.c
114
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user