mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-06 00:30:08 +00:00
GtkBuilder: Add private _gtk_builder_extend_with_template()
This adds the definition of the <template> tag with some documentation on the variant of the format. _gtk_builder_extend_with_template() is to be used while GtkContainer builds from composite templates. A couple of error codes are also added to handle a few new possible failure cases. DTD Files gtkbuilder.rnc and gtkbuilder.rng have been updated to include the new <template> tag and it's attributes.
This commit is contained in:
parent
82583640a2
commit
b7da0d21f8
133
gtk/gtkbuilder.c
133
gtk/gtkbuilder.c
@ -275,6 +275,7 @@ struct _GtkBuilderPrivate
|
||||
GSList *signals;
|
||||
gchar *filename;
|
||||
gchar *resource_prefix;
|
||||
GType template_type;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (GtkBuilder, gtk_builder, G_TYPE_OBJECT)
|
||||
@ -459,8 +460,9 @@ gtk_builder_get_parameters (GtkBuilder *builder,
|
||||
GType object_type,
|
||||
const gchar *object_name,
|
||||
GSList *properties,
|
||||
GParamFlags filter_flags,
|
||||
GArray **parameters,
|
||||
GArray **construct_parameters)
|
||||
GArray **filtered_parameters)
|
||||
{
|
||||
GSList *l;
|
||||
GParamSpec *pspec;
|
||||
@ -471,8 +473,10 @@ gtk_builder_get_parameters (GtkBuilder *builder,
|
||||
oclass = g_type_class_ref (object_type);
|
||||
g_assert (oclass != NULL);
|
||||
|
||||
if (parameters)
|
||||
*parameters = g_array_new (FALSE, FALSE, sizeof (GParameter));
|
||||
*construct_parameters = g_array_new (FALSE, FALSE, sizeof (GParameter));
|
||||
if (filtered_parameters)
|
||||
*filtered_parameters = g_array_new (FALSE, FALSE, sizeof (GParameter));
|
||||
|
||||
for (l = properties; l; l = l->next)
|
||||
{
|
||||
@ -530,11 +534,17 @@ gtk_builder_get_parameters (GtkBuilder *builder,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY))
|
||||
g_array_append_val (*construct_parameters, parameter);
|
||||
if (pspec->flags & filter_flags)
|
||||
{
|
||||
if (filtered_parameters)
|
||||
g_array_append_val (*filtered_parameters, parameter);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parameters)
|
||||
g_array_append_val (*parameters, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
g_type_class_unref (oclass);
|
||||
}
|
||||
@ -619,10 +629,22 @@ _gtk_builder_construct (GtkBuilder *builder,
|
||||
info->class_name);
|
||||
return NULL;
|
||||
}
|
||||
else if (builder->priv->template_type != 0 &&
|
||||
g_type_is_a (object_type, builder->priv->template_type))
|
||||
{
|
||||
g_set_error (error,
|
||||
GTK_BUILDER_ERROR,
|
||||
GTK_BUILDER_ERROR_OBJECT_TYPE_REFUSED,
|
||||
"Refused to build object of type `%s' because it "
|
||||
"conforms to the template type `%s', avoiding infinite recursion.",
|
||||
info->class_name, g_type_name (builder->priv->template_type));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gtk_builder_get_parameters (builder, object_type,
|
||||
info->id,
|
||||
info->properties,
|
||||
(G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY),
|
||||
¶meters,
|
||||
&construct_parameters);
|
||||
|
||||
@ -734,6 +756,61 @@ _gtk_builder_construct (GtkBuilder *builder,
|
||||
return obj;
|
||||
}
|
||||
|
||||
void
|
||||
_gtk_builder_apply_properties (GtkBuilder *builder,
|
||||
ObjectInfo *info,
|
||||
GError **error)
|
||||
{
|
||||
GArray *parameters;
|
||||
GType object_type;
|
||||
GtkBuildableIface *iface;
|
||||
GtkBuildable *buildable;
|
||||
gboolean custom_set_property;
|
||||
gint i;
|
||||
|
||||
g_assert (info->object != NULL);
|
||||
g_assert (info->class_name != NULL);
|
||||
object_type = gtk_builder_get_type_from_name (builder, info->class_name);
|
||||
|
||||
/* Fetch all properties that are not construct-only */
|
||||
gtk_builder_get_parameters (builder, object_type,
|
||||
info->id,
|
||||
info->properties,
|
||||
G_PARAM_CONSTRUCT_ONLY,
|
||||
¶meters, NULL);
|
||||
|
||||
custom_set_property = FALSE;
|
||||
buildable = NULL;
|
||||
iface = NULL;
|
||||
if (GTK_IS_BUILDABLE (info->object))
|
||||
{
|
||||
buildable = GTK_BUILDABLE (info->object);
|
||||
iface = GTK_BUILDABLE_GET_IFACE (info->object);
|
||||
if (iface->set_buildable_property)
|
||||
custom_set_property = TRUE;
|
||||
}
|
||||
|
||||
for (i = 0; i < parameters->len; i++)
|
||||
{
|
||||
GParameter *param = &g_array_index (parameters, GParameter, i);
|
||||
if (custom_set_property)
|
||||
iface->set_buildable_property (buildable, builder, param->name, ¶m->value);
|
||||
else
|
||||
g_object_set_property (info->object, param->name, ¶m->value);
|
||||
|
||||
#if G_ENABLE_DEBUG
|
||||
if (gtk_get_debug_flags () & GTK_DEBUG_BUILDER)
|
||||
{
|
||||
gchar *str = g_strdup_value_contents ((const GValue*)¶m->value);
|
||||
g_print ("set %s: %s = %s\n", info->id, param->name, str);
|
||||
g_free (str);
|
||||
}
|
||||
#endif
|
||||
g_value_unset (¶m->value);
|
||||
}
|
||||
g_array_free (parameters, TRUE);
|
||||
}
|
||||
|
||||
void
|
||||
_gtk_builder_add (GtkBuilder *builder,
|
||||
ChildInfo *child_info)
|
||||
@ -987,6 +1064,48 @@ gtk_builder_add_objects_from_file (GtkBuilder *builder,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Main private entry point for building composite container
|
||||
* components from template XML
|
||||
*/
|
||||
guint
|
||||
_gtk_builder_extend_with_template (GtkBuilder *builder,
|
||||
GtkWidget *widget,
|
||||
GType template_type,
|
||||
const gchar *buffer,
|
||||
gsize length,
|
||||
GError **error)
|
||||
{
|
||||
GError *tmp_error;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
|
||||
g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
|
||||
g_return_val_if_fail (g_type_name (template_type) != NULL, 0);
|
||||
g_return_val_if_fail (g_type_is_a (G_OBJECT_TYPE (widget), template_type), 0);
|
||||
g_return_val_if_fail (buffer && buffer[0], 0);
|
||||
|
||||
tmp_error = NULL;
|
||||
|
||||
g_free (builder->priv->filename);
|
||||
g_free (builder->priv->resource_prefix);
|
||||
builder->priv->filename = g_strdup (".");
|
||||
builder->priv->resource_prefix = NULL;
|
||||
builder->priv->template_type = template_type;
|
||||
|
||||
gtk_builder_expose_object (builder, g_type_name (template_type), G_OBJECT (widget));
|
||||
_gtk_builder_parser_parse_buffer (builder, "<input>",
|
||||
buffer, length,
|
||||
NULL,
|
||||
&tmp_error);
|
||||
|
||||
if (tmp_error != NULL)
|
||||
{
|
||||
g_propagate_error (error, tmp_error);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_builder_add_from_resource:
|
||||
* @builder: a #GtkBuilder
|
||||
@ -2141,6 +2260,12 @@ _gtk_builder_get_absolute_filename (GtkBuilder *builder, const gchar *string)
|
||||
return filename;
|
||||
}
|
||||
|
||||
GType
|
||||
_gtk_builder_get_template_type (GtkBuilder *builder)
|
||||
{
|
||||
return builder->priv->template_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_builder_add_callback_symbol:
|
||||
* @builder: a #GtkBuilder
|
||||
|
@ -59,6 +59,9 @@ typedef struct _GtkBuilderPrivate GtkBuilderPrivate;
|
||||
* @GTK_BUILDER_ERROR_VERSION_MISMATCH: The input file requires a newer version
|
||||
* of GTK+.
|
||||
* @GTK_BUILDER_ERROR_DUPLICATE_ID: An object id occurred twice.
|
||||
* @GTK_BUILDER_ERROR_OBJECT_TYPE_REFUSED: A specified object type is of the same type or
|
||||
* derived from the type of the composite class being extended with builder XML.
|
||||
* @GTK_BUILDER_ERROR_TEMPLATE_MISMATCH: The wrong type was specified in a composite class's template XML
|
||||
*
|
||||
* Error codes that identify various errors that can occur while using
|
||||
* #GtkBuilder.
|
||||
@ -73,7 +76,9 @@ typedef enum
|
||||
GTK_BUILDER_ERROR_MISSING_PROPERTY_VALUE,
|
||||
GTK_BUILDER_ERROR_INVALID_VALUE,
|
||||
GTK_BUILDER_ERROR_VERSION_MISMATCH,
|
||||
GTK_BUILDER_ERROR_DUPLICATE_ID
|
||||
GTK_BUILDER_ERROR_DUPLICATE_ID,
|
||||
GTK_BUILDER_ERROR_OBJECT_TYPE_REFUSED,
|
||||
GTK_BUILDER_ERROR_TEMPLATE_MISMATCH
|
||||
} GtkBuilderError;
|
||||
|
||||
GQuark gtk_builder_error_quark (void);
|
||||
|
@ -1,6 +1,6 @@
|
||||
start = element interface {
|
||||
attribute domain { text } ?,
|
||||
( requires | object | menu ) *
|
||||
( requires | object | template | menu ) *
|
||||
}
|
||||
|
||||
requires = element requires {
|
||||
@ -16,6 +16,12 @@ object = element object {
|
||||
(property | signal | child | ANY) *
|
||||
}
|
||||
|
||||
template = element template {
|
||||
attribute class { text },
|
||||
attribute parent { text },
|
||||
(property | signal | child | ANY) *
|
||||
}
|
||||
|
||||
property = element property {
|
||||
attribute name { text },
|
||||
attribute translatable { "yes" | "no" } ?,
|
||||
@ -76,7 +82,7 @@ section = element section {
|
||||
(attribute_ | item | submenu | section) *
|
||||
}
|
||||
|
||||
ANY = element * - (interface | requires | object | property | signal | child | menu | item | attribute | link | submenu | section) {
|
||||
ANY = element * - (interface | requires | object | template | property | signal | child | menu | item | attribute | link | submenu | section) {
|
||||
attribute * { text } *,
|
||||
(ALL * & text ?)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
<choice>
|
||||
<ref name="requires"/>
|
||||
<ref name="object"/>
|
||||
<ref name="template"/>
|
||||
<ref name="menu"/>
|
||||
</choice>
|
||||
</zeroOrMore>
|
||||
@ -54,6 +55,24 @@
|
||||
</zeroOrMore>
|
||||
</element>
|
||||
</define>
|
||||
<define name="template">
|
||||
<element name="template">
|
||||
<attribute name="class">
|
||||
<text/>
|
||||
</attribute>
|
||||
<attribute name="parent">
|
||||
<text/>
|
||||
</attribute>
|
||||
<zeroOrMore>
|
||||
<choice>
|
||||
<ref name="property"/>
|
||||
<ref name="signal"/>
|
||||
<ref name="child"/>
|
||||
<ref name="ANY"/>
|
||||
</choice>
|
||||
</zeroOrMore>
|
||||
</element>
|
||||
</define>
|
||||
<define name="property">
|
||||
<element name="property">
|
||||
<attribute name="name">
|
||||
|
@ -187,14 +187,27 @@ builder_construct (ParserData *data,
|
||||
|
||||
g_assert (object_info != NULL);
|
||||
|
||||
if (object_info->object)
|
||||
if (object_info->object && object_info->applied_properties)
|
||||
return object_info->object;
|
||||
|
||||
object_info->properties = g_slist_reverse (object_info->properties);
|
||||
|
||||
if (object_info->object == NULL)
|
||||
{
|
||||
object = _gtk_builder_construct (data->builder, object_info, error);
|
||||
if (!object)
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We're building a template, the object is already set and
|
||||
* we just want to resolve the properties at the right time
|
||||
*/
|
||||
object = object_info->object;
|
||||
_gtk_builder_apply_properties (data->builder, object_info, error);
|
||||
}
|
||||
|
||||
object_info->applied_properties = TRUE;
|
||||
|
||||
g_assert (G_IS_OBJECT (object));
|
||||
|
||||
@ -411,6 +424,92 @@ parse_object (GMarkupParseContext *context,
|
||||
g_hash_table_insert (data->object_ids, g_strdup (object_id), GINT_TO_POINTER (line));
|
||||
}
|
||||
|
||||
static void
|
||||
parse_template (GMarkupParseContext *context,
|
||||
ParserData *data,
|
||||
const gchar *element_name,
|
||||
const gchar **names,
|
||||
const gchar **values,
|
||||
GError **error)
|
||||
{
|
||||
ObjectInfo *object_info;
|
||||
int i;
|
||||
gchar *object_class = NULL;
|
||||
gint line, line2;
|
||||
GType template_type = _gtk_builder_get_template_type (data->builder);
|
||||
GType parsed_type;
|
||||
|
||||
if (template_type == 0)
|
||||
{
|
||||
g_set_error (error,
|
||||
GTK_BUILDER_ERROR,
|
||||
GTK_BUILDER_ERROR_UNHANDLED_TAG,
|
||||
"Encountered template definition but not parsing a template.");
|
||||
return;
|
||||
}
|
||||
else if (state_peek (data) != NULL)
|
||||
{
|
||||
g_set_error (error,
|
||||
GTK_BUILDER_ERROR,
|
||||
GTK_BUILDER_ERROR_UNHANDLED_TAG,
|
||||
"Encountered template definition that is not at the top level.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; names[i] != NULL; i++)
|
||||
{
|
||||
if (strcmp (names[i], "class") == 0)
|
||||
object_class = g_strdup (values[i]);
|
||||
else if (strcmp (names[i], "parent") == 0)
|
||||
/* Ignore 'parent' attribute, however it's needed by Glade */;
|
||||
else
|
||||
{
|
||||
error_invalid_attribute (data, element_name, names[i], error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!object_class)
|
||||
{
|
||||
error_missing_attribute (data, element_name, "class", error);
|
||||
return;
|
||||
}
|
||||
|
||||
parsed_type = g_type_from_name (object_class);
|
||||
if (template_type != parsed_type)
|
||||
{
|
||||
g_set_error (error,
|
||||
GTK_BUILDER_ERROR,
|
||||
GTK_BUILDER_ERROR_TEMPLATE_MISMATCH,
|
||||
"Parsed template definition for type `%s', expected type `%s'.",
|
||||
object_class, g_type_name (template_type));
|
||||
return;
|
||||
}
|
||||
|
||||
++data->cur_object_level;
|
||||
|
||||
object_info = g_slice_new0 (ObjectInfo);
|
||||
object_info->class_name = object_class;
|
||||
object_info->id = g_strdup (object_class);
|
||||
object_info->object = gtk_builder_get_object (data->builder, object_class);
|
||||
state_push (data, object_info);
|
||||
object_info->tag.name = element_name;
|
||||
|
||||
g_markup_parse_context_get_position (context, &line, NULL);
|
||||
line2 = GPOINTER_TO_INT (g_hash_table_lookup (data->object_ids, object_class));
|
||||
if (line2 != 0)
|
||||
{
|
||||
g_set_error (error, GTK_BUILDER_ERROR,
|
||||
GTK_BUILDER_ERROR_DUPLICATE_ID,
|
||||
_("Duplicate object ID '%s' on line %d (previously on line %d)"),
|
||||
object_class, line, line2);
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_insert (data->object_ids, g_strdup (object_class), GINT_TO_POINTER (line));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
free_object_info (ObjectInfo *info)
|
||||
{
|
||||
@ -446,7 +545,9 @@ parse_child (ParserData *data,
|
||||
guint i;
|
||||
|
||||
object_info = state_peek_info (data, ObjectInfo);
|
||||
if (!object_info || strcmp (object_info->tag.name, "object") != 0)
|
||||
if (!object_info ||
|
||||
!(strcmp (object_info->tag.name, "object") == 0 ||
|
||||
strcmp (object_info->tag.name, "template") == 0))
|
||||
{
|
||||
error_invalid_tag (data, element_name, NULL, error);
|
||||
return;
|
||||
@ -493,7 +594,9 @@ parse_property (ParserData *data,
|
||||
int i;
|
||||
|
||||
object_info = state_peek_info (data, ObjectInfo);
|
||||
if (!object_info || strcmp (object_info->tag.name, "object") != 0)
|
||||
if (!object_info ||
|
||||
!(strcmp (object_info->tag.name, "object") == 0 ||
|
||||
strcmp (object_info->tag.name, "template") == 0))
|
||||
{
|
||||
error_invalid_tag (data, element_name, NULL, error);
|
||||
return;
|
||||
@ -566,7 +669,9 @@ parse_signal (ParserData *data,
|
||||
int i;
|
||||
|
||||
object_info = state_peek_info (data, ObjectInfo);
|
||||
if (!object_info || strcmp (object_info->tag.name, "object") != 0)
|
||||
if (!object_info ||
|
||||
!(strcmp (object_info->tag.name, "object") == 0 ||
|
||||
strcmp (object_info->tag.name, "template") == 0))
|
||||
{
|
||||
error_invalid_tag (data, element_name, NULL, error);
|
||||
return;
|
||||
@ -784,7 +889,8 @@ parse_custom (GMarkupParseContext *context,
|
||||
if (!parent_info)
|
||||
return FALSE;
|
||||
|
||||
if (strcmp (parent_info->tag.name, "object") == 0)
|
||||
if (strcmp (parent_info->tag.name, "object") == 0 ||
|
||||
strcmp (parent_info->tag.name, "template") == 0)
|
||||
{
|
||||
ObjectInfo* object_info = (ObjectInfo*)parent_info;
|
||||
if (!object_info->object)
|
||||
@ -877,6 +983,8 @@ start_element (GMarkupParseContext *context,
|
||||
parse_requires (data, element_name, names, values, error);
|
||||
else if (strcmp (element_name, "object") == 0)
|
||||
parse_object (context, data, element_name, names, values, error);
|
||||
else if (strcmp (element_name, "template") == 0)
|
||||
parse_template (context, data, element_name, names, values, error);
|
||||
else if (data->requested_objects && !data->inside_requested_object)
|
||||
{
|
||||
/* If outside a requested object, simply ignore this tag */
|
||||
@ -972,7 +1080,8 @@ end_element (GMarkupParseContext *context,
|
||||
{
|
||||
_gtk_builder_menu_end (data);
|
||||
}
|
||||
else if (strcmp (element_name, "object") == 0)
|
||||
else if (strcmp (element_name, "object") == 0 ||
|
||||
strcmp (element_name, "template") == 0)
|
||||
{
|
||||
ObjectInfo *object_info = state_pop_info (data, ObjectInfo);
|
||||
ChildInfo* child_info = state_peek_info (data, ChildInfo);
|
||||
@ -1012,7 +1121,8 @@ end_element (GMarkupParseContext *context,
|
||||
CommonInfo *info = state_peek_info (data, CommonInfo);
|
||||
|
||||
/* Normal properties */
|
||||
if (strcmp (info->tag.name, "object") == 0)
|
||||
if (strcmp (info->tag.name, "object") == 0 ||
|
||||
strcmp (info->tag.name, "template") == 0)
|
||||
{
|
||||
ObjectInfo *object_info = (ObjectInfo*)info;
|
||||
|
||||
@ -1101,7 +1211,8 @@ text (GMarkupParseContext *context,
|
||||
static void
|
||||
free_info (CommonInfo *info)
|
||||
{
|
||||
if (strcmp (info->tag.name, "object") == 0)
|
||||
if (strcmp (info->tag.name, "object") == 0 ||
|
||||
strcmp (info->tag.name, "template") == 0)
|
||||
free_object_info ((ObjectInfo *)info);
|
||||
else if (strcmp (info->tag.name, "child") == 0)
|
||||
free_child_info ((ChildInfo *)info);
|
||||
|
@ -38,6 +38,7 @@ typedef struct {
|
||||
GSList *signals;
|
||||
GObject *object;
|
||||
CommonInfo *parent;
|
||||
gboolean applied_properties;
|
||||
} ObjectInfo;
|
||||
|
||||
typedef struct {
|
||||
@ -121,6 +122,9 @@ void _gtk_builder_parser_parse_buffer (GtkBuilder *builder,
|
||||
GObject * _gtk_builder_construct (GtkBuilder *builder,
|
||||
ObjectInfo *info,
|
||||
GError **error);
|
||||
void _gtk_builder_apply_properties (GtkBuilder *builder,
|
||||
ObjectInfo *info,
|
||||
GError **error);
|
||||
void _gtk_builder_add_object (GtkBuilder *builder,
|
||||
const gchar *id,
|
||||
GObject *object);
|
||||
@ -159,5 +163,12 @@ void _gtk_builder_menu_start (ParserData *parser_data,
|
||||
GError **error);
|
||||
void _gtk_builder_menu_end (ParserData *parser_data);
|
||||
|
||||
GType _gtk_builder_get_template_type (GtkBuilder *builder);
|
||||
guint _gtk_builder_extend_with_template (GtkBuilder *builder,
|
||||
GtkWidget *widget,
|
||||
GType template_type,
|
||||
const gchar *buffer,
|
||||
gsize length,
|
||||
GError **error);
|
||||
|
||||
#endif /* __GTK_BUILDER_PRIVATE_H__ */
|
||||
|
Loading…
Reference in New Issue
Block a user