Compare commits

...

17 Commits

Author SHA1 Message Date
Denis Washington
c3fe8359dd Merge branch 'master' into gtkbuilder-gbinding 2012-02-15 22:46:26 +01:00
Denis Washington
b45a20d4e4 Modified GtkBuilder schemas to include <binding> 2012-02-15 22:44:36 +01:00
Denis Washington
3dd2c6f4e3 Check for conflicting <binding>/<property> elements 2012-02-15 22:36:01 +01:00
Denis Washington
d698b5dcef Remove accidently commited conflict section in the GtkBuilder doc comment 2012-02-14 21:34:49 +01:00
Denis Washington
3e0ae4bf9a Merge branch 'master' into gtkbuilder-gbinding
Conflicts:
	gtk/gtkbuilder.c
	gtk/tests/builder.c
2012-02-14 21:10:05 +01:00
Denis Washington
143f943905 Merge branch 'master' into gtkbuilder-gbinding 2011-08-30 08:37:28 +02:00
Denis Washington
41ca2069c0 Remove support for transformation functions in GtkBuilder property binding code for now
The added API would be a burden for language binding authors and requires
another call after loading a UI file (like gtk_builder_connect_signals()).
This still needs to be fleshed out more. The version with transformation
functions were moved to a new "gtkbuilder-gbinding-transform" branch for
reference.
2011-08-21 21:51:21 +02:00
Denis Washington
4405feaf59 Merge branch 'master' into gtkbuilder-gbinding 2011-08-21 21:36:22 +02:00
Denis Washington
09f188d2dd Merge branch 'master' into gtkbuilder-gbinding 2011-08-19 12:43:57 +02:00
Denis Washington
173bd1618d Introduce gtk_builder_create_bindings[_full](), add transformation function support
Property bindings are not created implicitly by gtk_builder_add*()
anymore but by explicitly calling gtk_builder_create_bindings() or
gtk_builder_create_bindings_full(), which are analog to the
gtk_builder_signals*() functions. This change is needed for the
new support for transformation functions, which need to be located
like signal handlers (via GModule or a custom function). A transormation
function can be specified by adding a "transform-func" attribute to
a <binding> tag with the function name.

Also, updated the GtkBuilder documentation to reflect the changes
and adjusted the test case (there is also a new test for transformation
functions).
2011-07-23 10:56:22 +02:00
Denis Washington
65a846b64c Merge branch 'master' into gtkbuilder-gbinding 2011-07-23 10:55:38 +02:00
Denis Washington
e24b186276 Merge branch 'master' into gtkbuilder-gbinding 2011-07-20 16:22:11 +02:00
Denis Washington
321961699b Memory leak fix 2011-07-11 20:48:14 +02:00
Denis Washington
af67944475 Merge branch 'master' into gtkbuilder-gbinding 2011-07-09 12:37:06 +02:00
Denis Washington
21708559d0 Fix wrong comparison, makes multiple bindings per object work 2011-06-10 11:03:56 +02:00
Denis Washington
0df0306116 Fix typo in test file 2011-06-10 11:02:44 +02:00
Denis Washington
fe50aa8d04 (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.)
2011-05-25 23:59:17 +02:00
6 changed files with 300 additions and 24 deletions

View File

@@ -132,6 +132,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 a &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. For more information about the property
* binding mechanism, see #GBinding (which GTK+ uses internally).
*
* 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
@@ -272,6 +279,7 @@ struct _GtkBuilderPrivate
GHashTable *objects;
GSList *delayed_properties;
GSList *signals;
GSList *bindings;
gchar *filename;
gchar *resource_prefix;
};
@@ -778,15 +786,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.
*
@@ -799,43 +851,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

@@ -13,7 +13,7 @@ object = element object {
attribute class { text },
attribute type-func { text } ?,
attribute constructor { text } ?,
(property | signal | child | ANY) *
(property | signal | binding | child | ANY) *
}
property = element property {
@@ -34,6 +34,12 @@ signal = element signal {
empty
}
binding = element binding {
attribute to { text },
attribute from { text },
attribute source { text }
}
child = element child {
attribute type { text } ?,
attribute internal-child { text } ?,

View File

@@ -48,6 +48,7 @@
<choice>
<ref name="property"/>
<ref name="signal"/>
<ref name="binding"/>
<ref name="child"/>
<ref name="ANY"/>
</choice>
@@ -113,6 +114,20 @@
<empty/>
</element>
</define>
<define name="binding">
<element name="binding">
<attribute name="to">
<text/>
</attribute>
<attribute name="from">
<text/>
</attribute>
<attribute name="source">
<text/>
</attribute>
<empty/>
</element>
</define>
<define name="child">
<element name="child">
<optional>

View File

@@ -542,6 +542,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;
const gchar *to = NULL;
const gchar *from = NULL;
const 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 = values[i];
else if (strcmp (names[i], "from") == 0)
from = values[i];
else if (strcmp (names[i], "source") == 0)
source = 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 = g_strdup (to);
info->from = g_strdup (from);
info->source = g_strdup (source);
state_push (data, info);
info->tag.name = element_name;
}
static void
free_property_info (PropertyInfo *info)
{
@@ -643,6 +704,17 @@ _free_signal_info (SignalInfo *info,
g_slice_free (SignalInfo, info);
}
void
_free_binding_info (BindingInfo *info,
gpointer user_data)
{
g_free (info->object_name);
g_free (info->to);
g_free (info->from);
g_free (info->source);
g_slice_free (BindingInfo, info);
}
void
_free_requires_info (RequiresInfo *info,
gpointer user_data)
@@ -888,6 +960,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)
@@ -978,6 +1052,49 @@ end_element (GMarkupParseContext *context,
{
ObjectInfo *object_info = state_pop_info (data, ObjectInfo);
ChildInfo* child_info = state_peek_info (data, ChildInfo);
GSList *l;
/* Check for conflicting property binding definitions and
* conflicts between property binding and value definitions.
*/
for (l = object_info->bindings; l; l = l->next)
{
BindingInfo *b = (BindingInfo*)l->data;
GSList *l2;
for (l2 = object_info->bindings; l2; l2 = l2->next)
{
BindingInfo *b2 = (BindingInfo*)l2->data;
if (b != b2 && !strcmp (b->to, b2->to))
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Duplicate binding for property: `%s'",
b->to);
break;
}
}
/* If we broke out of the last loop, we already found an error */
if (l2)
break;
for (l2 = object_info->properties; l2; l2 = l2->next)
{
PropertyInfo *p = (PropertyInfo*)l2->data;
if (!strcmp (b->to, p->name))
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Value supplied for property defined as "
"bound: `%s'",
b->to);
break;
}
}
}
if (data->requested_objects && data->inside_requested_object &&
(data->cur_object_level == data->requested_object_level))
@@ -1005,6 +1122,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);
}
@@ -1053,6 +1171,15 @@ 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);
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;
@@ -67,6 +68,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;
@@ -130,9 +139,13 @@ 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);
void _free_signal_info (SignalInfo *info,
gpointer user_data);
void _free_binding_info (BindingInfo *info,
gpointer user_data);
/* Internal API which might be made public at some point */
gboolean _gtk_builder_boolean_from_string (const gchar *string,

View File

@@ -2645,6 +2645,54 @@ test_gmenu (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)
{
@@ -2692,6 +2740,7 @@ main (int argc, char **argv)
g_test_add_func ("/Builder/MessageArea", test_message_area);
g_test_add_func ("/Builder/MessageDialog", test_message_dialog);
g_test_add_func ("/Builder/GMenu", test_gmenu);
g_test_add_func ("/Builder/Property Bindings", test_property_bindings);
return g_test_run();
}