Compare commits

...

14 Commits

Author SHA1 Message Date
Denis Washington
cea5461353 Merge branch 'gtkbuilder-gbinding' into gtkbuilder-gbinding-transform 2011-08-30 08:38:03 +02:00
Denis Washington
143f943905 Merge branch 'master' into gtkbuilder-gbinding 2011-08-30 08:37:28 +02:00
Denis Washington
c2107aebe7 Moved transformation function support here from "gtkbuilder-gbinding" branch
This is not ready yet, but should still have a branch for improvements.
2011-08-21 21:56:17 +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
5 changed files with 466 additions and 26 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,10 @@
* last_modification_time #IMPLIED >
* <!ATTLIST child type #IMPLIED
* internal-child #IMPLIED >
* <!ATTLIST binding to #REQUIRED
* from #REQUIRED
* source #REQUIRED
* transform-func #IMPLIED>
* ]]></programlisting>
* <para>
* The toplevel element is &lt;interface&gt;. It optionally takes a "domain"
@@ -155,10 +160,23 @@
* 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. If present, "transform-func" specifies a
* transformation function that converts the source property value before
* it is passed to the target property. GTK+'s default method for finding
* such a function is using g_module_symbol(); to overridde this behavior,
* a custom #GtkBuilderBindingFunc can be passed to
* gtk_builder_create_bindings_full(). 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
* find the handler using g_module_symbol(), but this can be changed by passing
* specifies the function to connect to the signal. Like in the case of
* transformation functions, GTK+ tries to find the handler using
* g_module_symbol() by default, but this can be changed by passing
* a custom #GtkBuilderConnectFunc to gtk_builder_connect_signals_full(). The
* remaining attributes, "after", "swapped" and "object", have the same meaning
* as the corresponding parameters of the g_signal_connect_object() or
@@ -285,6 +303,7 @@ struct _GtkBuilderPrivate
GHashTable *objects;
GSList *delayed_properties;
GSList *signals;
GSList *bindings;
gchar *filename;
};
@@ -782,15 +801,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,35 +866,22 @@ 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);
}
@@ -1343,6 +1393,145 @@ gtk_builder_connect_signals_full (GtkBuilder *builder,
builder->priv->signals = NULL;
}
static void
gtk_builder_create_bindings_default (GtkBuilder *builder,
GObject *source,
const gchar *source_property,
GObject *target,
const gchar *target_property,
const gchar *transform_func,
GBindingFlags flags,
gpointer user_data)
{
GBindingTransformFunc func = NULL;
connect_args *args = (connect_args*)user_data;
if (transform_func &&
!g_module_symbol (args->module, transform_func, (gpointer)&func))
{
g_warning ("Could not find binding transformation function '%s'",
transform_func);
return;
}
g_object_bind_property_full (source, source_property,
target, target_property,
flags | G_BINDING_SYNC_CREATE,
func, NULL,
args->data, NULL);
}
/**
* gtk_builder_create_bindings:
* @builder: a #GtkBuilder
* @user_data: a pointer to a structure sent in as user data to all signals
*
* This function establishes all property bindings defined in the interface
* description. It uses g_object_bind_property_full() with the
* #G_BINDING_SYNC_CREATE flag flag for this purpose. Like
* gtk_builder_connect_signals(), #GModule is used to match any
* transformation function names given in the interface description with
* symbols exported through the application's symbol table. To overide
* this behavior with a different binding creation method, use
* gtk_builder_create_bindings_full() with a custom #GtkBuilderBindingFunc
* instead.
*
* Note that this function requires #GModule to be supported on the platform
* to work. It can only be called once, subsequent calls will do nothing.
*
* When compiling applications for Windows, you must declare transformation
* functions with #G_MODULE_EXPORT, or they will not be put in the symbol
* table. On Linux and Unices, this is not necessary; applications should
* instead be compiled with the -Wl,--export-dynamic CFLAGS, and linked
* against gmodule-export-2.0.
*/
void
gtk_builder_create_bindings (GtkBuilder *builder,
gpointer user_data)
{
/* Reuse connect_args from gtk_builder_connect_signals */
connect_args *args;
g_return_if_fail (GTK_IS_BUILDER (builder));
if (!g_module_supported ())
g_error ("gtk_builder_create_bindings() requires working GModule");
args = g_slice_new0 (connect_args);
args->module = g_module_open (NULL, G_MODULE_BIND_LAZY);
args->data = user_data;
gtk_builder_create_bindings_full (builder,
gtk_builder_create_bindings_default,
args);
g_module_close (args->module);
g_slice_free (connect_args, args);
}
/**
* GtkBuilderBindingFunc:
* @builder: a #GtkBuilder
* @source: the binding source object
* @source_property: the binding source property
* @target: the binding target object
* @target_property: the binding target property
* @flags: #GBindingFlags to use
* @user_data: user data
*
* The signature of a function used to create property bindings, used by the
* gtk_builder_create_bindings() and gtk_builder_create_bindings_full()
* methods. Like #GtkBuilderConnectFunc, its main use case is bindings for
* interpreted programming languages.
*
* Since: ?
*/
/**
* gtk_builder_create_bindings_full:
* @builder: a #GtkBuilder
* @func: (scope call): the function used to create the bindings
* @user_data: arbitrary data that will be passed to the binding function
*
* Calls @func for every property binding defined in the interface description
* in order to create it. Like gtk_builder_connect_signals_full(), this
* function is mainly thought as a version of gtk_builder_create_bindings()
* suitable for interpreted language bindings, but has other uses too.
*/
void
gtk_builder_create_bindings_full (GtkBuilder *builder,
GtkBuilderBindingFunc func,
gpointer user_data)
{
GSList *l;
builder->priv->bindings = g_slist_reverse (builder->priv->bindings);
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_warning ("Could not lookup source object %s for binding to "
"property %s of object %s",
binding->source, binding->to, binding->object_name);
continue;
}
func (builder, source, binding->from, target, binding->to,
binding->transform_func, 0, user_data);
}
g_slist_foreach (builder->priv->bindings, (GFunc)_free_binding_info, NULL);
g_slist_free (builder->priv->bindings);
builder->priv->bindings = NULL;
}
/**
* gtk_builder_value_from_string:
* @builder: a #GtkBuilder

View File

@@ -113,6 +113,15 @@ typedef void (*GtkBuilderConnectFunc) (GtkBuilder *builder,
GConnectFlags flags,
gpointer user_data);
typedef void (*GtkBuilderBindingFunc) (GtkBuilder *builder,
GObject *source,
const gchar *source_property,
GObject *target,
const gchar *target_property,
const gchar *transform_func,
GBindingFlags flags,
gpointer user_data);
GType gtk_builder_get_type (void) G_GNUC_CONST;
GtkBuilder* gtk_builder_new (void);
@@ -140,6 +149,12 @@ void gtk_builder_connect_signals (GtkBuilder *builder,
void gtk_builder_connect_signals_full (GtkBuilder *builder,
GtkBuilderConnectFunc func,
gpointer user_data);
void gtk_builder_create_bindings (GtkBuilder *builder,
gpointer user_data);
void gtk_builder_create_bindings_full (GtkBuilder *builder,
GtkBuilderBindingFunc func,
gpointer user_data);
void gtk_builder_set_translation_domain (GtkBuilder *builder,
const gchar *domain);
const gchar* gtk_builder_get_translation_domain (GtkBuilder *builder);

View File

@@ -533,6 +533,72 @@ 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;
const gchar *transform_func = 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 if (strcmp (names[i], "transform-func") == 0)
transform_func = 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);
if (transform_func)
info->transform_func = g_strdup (transform_func);
state_push (data, info);
info->tag.name = element_name;
}
static void
free_property_info (PropertyInfo *info)
{
@@ -634,6 +700,18 @@ _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_free (info->transform_func);
g_slice_free (BindingInfo, info);
}
void
_free_requires_info (RequiresInfo *info,
gpointer user_data)
@@ -879,6 +957,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 +1070,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 +1119,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))
{
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,15 @@ typedef struct {
gchar *context;
} PropertyInfo;
typedef struct {
TagInfo tag;
gchar *object_name;
gchar *to;
gchar *from;
gchar *source;
gchar *transform_func;
} BindingInfo;
typedef struct {
TagInfo tag;
gchar *object_name;
@@ -121,9 +131,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

@@ -2572,6 +2572,122 @@ 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);
gtk_builder_create_bindings (builder, 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);
}
gboolean
reverse_func (GBinding *binding,
const GValue *source_value,
GValue *target_value,
gpointer user_data)
{
gchar *tmp;
g_assert_cmpstr ((const gchar *)user_data, ==, "user_data");
tmp = g_strdup (g_value_get_string (source_value));
g_strreverse (tmp);
g_value_set_string (target_value, tmp);
g_free (tmp);
return TRUE;
}
static void
test_transform_funcs (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=\"GtkEntry\" id=\"entry\">"
" <property name=\"text\">GTK+</property>"
" </object>"
" </child>"
" <child>"
" <object class=\"GtkButton\" id=\"button\">"
" <binding to=\"label\" from=\"text\" source=\"entry\" transform-func=\"reverse_func\"/>"
" </object>"
" </child>"
" </object>"
" </child>"
" </object>"
"</interface>";
GtkBuilder *builder;
GObject *entry, *button, *window;
builder = builder_new_from_string (buffer, -1, NULL);
gtk_builder_create_bindings (builder, "user_data");
entry = gtk_builder_get_object (builder, "entry");
g_assert (entry != NULL);
g_assert (GTK_IS_ENTRY (entry));
g_assert (strcmp (gtk_entry_get_text (GTK_ENTRY (entry)), "GTK+") == 0);
button = gtk_builder_get_object (builder, "button");
g_assert (button != NULL);
g_assert (GTK_IS_BUTTON (button));
g_assert (strcmp (gtk_button_get_label (GTK_BUTTON (button)), "+KTG") == 0);
gtk_entry_set_text (GTK_ENTRY (entry), "rocks");
g_assert (strcmp (gtk_button_get_label (GTK_BUTTON (button)), "skcor") == 0);
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 +2734,8 @@ 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_bindings);
g_test_add_func ("/Builder/Transformation Functions", test_transform_funcs);
return g_test_run();
}