Compare commits
17 Commits
matthiasc/
...
gtkbuilder
Author | SHA1 | Date | |
---|---|---|---|
|
c3fe8359dd | ||
|
b45a20d4e4 | ||
|
3dd2c6f4e3 | ||
|
d698b5dcef | ||
|
3e0ae4bf9a | ||
|
143f943905 | ||
|
41ca2069c0 | ||
|
4405feaf59 | ||
|
09f188d2dd | ||
|
173bd1618d | ||
|
65a846b64c | ||
|
e24b186276 | ||
|
321961699b | ||
|
af67944475 | ||
|
21708559d0 | ||
|
0df0306116 | ||
|
fe50aa8d04 |
108
gtk/gtkbuilder.c
108
gtk/gtkbuilder.c
@@ -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 <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. For more information about the property
|
||||
* binding mechanism, see #GBinding (which GTK+ uses internally).
|
||||
*
|
||||
* 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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 } ?,
|
||||
|
@@ -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>
|
||||
|
@@ -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)
|
||||
{
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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();
|
||||
}
|
||||
|
Reference in New Issue
Block a user