builder: Add support for parsing expressions

This commit is contained in:
Benjamin Otte
2019-11-18 04:35:36 +01:00
parent b4b6be9f6e
commit b603f472ab
5 changed files with 533 additions and 2 deletions

View File

@@ -520,7 +520,18 @@ gtk_builder_get_parameters (GtkBuilder *builder,
const char *property_name = g_intern_string (prop->pspec->name);
GValue property_value = G_VALUE_INIT;
if (G_IS_PARAM_SPEC_OBJECT (prop->pspec) &&
if (prop->value)
{
g_value_init (&property_value, G_PARAM_SPEC_VALUE_TYPE (prop->pspec));
if (G_PARAM_SPEC_VALUE_TYPE (prop->pspec) == GTK_TYPE_EXPRESSION)
g_value_set_boxed (&property_value, prop->value);
else
{
g_assert_not_reached();
}
}
else if (G_IS_PARAM_SPEC_OBJECT (prop->pspec) &&
(G_PARAM_SPEC_VALUE_TYPE (prop->pspec) != GDK_TYPE_PIXBUF) &&
(G_PARAM_SPEC_VALUE_TYPE (prop->pspec) != GDK_TYPE_TEXTURE) &&
(G_PARAM_SPEC_VALUE_TYPE (prop->pspec) != GDK_TYPE_PAINTABLE) &&

View File

@@ -932,7 +932,7 @@ parse_property (ParserData *data,
return;
}
info = g_slice_new (PropertyInfo);
info = g_slice_new0 (PropertyInfo);
info->tag_type = TAG_PROPERTY;
info->pspec = pspec;
info->text = g_string_new ("");
@@ -948,11 +948,349 @@ parse_property (ParserData *data,
static void
free_property_info (PropertyInfo *info)
{
if (info->value)
{
if (G_PARAM_SPEC_VALUE_TYPE (info->pspec) == GTK_TYPE_EXPRESSION)
gtk_expression_unref (info->value);
else
g_assert_not_reached();
}
g_string_free (info->text, TRUE);
g_free (info->context);
g_slice_free (PropertyInfo, info);
}
static void
free_expression_info (ExpressionInfo *info)
{
switch (info->expression_type)
{
case EXPRESSION_EXPRESSION:
gtk_expression_unref (info->expression);
break;
case EXPRESSION_CONSTANT:
g_string_free (info->constant.text, TRUE);
break;
case EXPRESSION_CLOSURE:
g_free (info->closure.function_name);
g_free (info->closure.object_name);
g_slist_free_full (info->closure.params, (GDestroyNotify) free_expression_info);
break;
case EXPRESSION_PROPERTY:
g_clear_pointer (&info->property.expression, free_expression_info);
g_free (info->property.property_name);
break;
default:
g_assert_not_reached ();
break;
}
g_slice_free (ExpressionInfo, info);
}
static gboolean
check_expression_parent (ParserData *data)
{
CommonInfo *common_info = state_peek_info (data, CommonInfo);
if (common_info == NULL)
return FALSE;
if (common_info->tag_type == TAG_PROPERTY)
{
PropertyInfo *prop_info = (PropertyInfo *) common_info;
return G_PARAM_SPEC_VALUE_TYPE (prop_info->pspec) == GTK_TYPE_EXPRESSION;
}
if (common_info->tag_type == TAG_EXPRESSION)
{
ExpressionInfo *expr_info = (ExpressionInfo *) common_info;
switch (expr_info->expression_type)
{
case EXPRESSION_CLOSURE:
return TRUE;
case EXPRESSION_CONSTANT:
return FALSE;
case EXPRESSION_PROPERTY:
return expr_info->property.expression == NULL;
case EXPRESSION_EXPRESSION:
default:
g_assert_not_reached ();
return FALSE;
}
}
return FALSE;
}
static void
parse_constant_expression (ParserData *data,
const gchar *element_name,
const gchar **names,
const gchar **values,
GError **error)
{
ExpressionInfo *info;
const char *type_name;
GType type;
if (!check_expression_parent (data))
{
error_invalid_tag (data, element_name, NULL, error);
return;
}
if (!g_markup_collect_attributes (element_name, names, values, error,
G_MARKUP_COLLECT_STRING, "type", &type_name,
G_MARKUP_COLLECT_INVALID))
{
_gtk_builder_prefix_error (data->builder, &data->ctx, error);
return;
}
type = gtk_builder_get_type_from_name (data->builder, type_name);
if (type == G_TYPE_INVALID)
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Invalid type '%s'", type_name);
_gtk_builder_prefix_error (data->builder, &data->ctx, error);
return;
}
info = g_slice_new0 (ExpressionInfo);
info->tag_type = TAG_EXPRESSION;
info->expression_type = EXPRESSION_CONSTANT;
info->constant.type = type;
info->constant.text = g_string_new (NULL);
state_push (data, info);
}
static void
parse_closure_expression (ParserData *data,
const gchar *element_name,
const gchar **names,
const gchar **values,
GError **error)
{
ExpressionInfo *info;
const char *type_name;
const char *function_name;
const char *object_name = NULL;
gboolean swapped = -1;
GType type;
if (!check_expression_parent (data))
{
error_invalid_tag (data, element_name, NULL, error);
return;
}
if (!g_markup_collect_attributes (element_name, names, values, error,
G_MARKUP_COLLECT_STRING, "type", &type_name,
G_MARKUP_COLLECT_STRING, "function", &function_name,
G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "object", &object_name,
G_MARKUP_COLLECT_TRISTATE|G_MARKUP_COLLECT_OPTIONAL, "swapped", &swapped,
G_MARKUP_COLLECT_INVALID))
{
_gtk_builder_prefix_error (data->builder, &data->ctx, error);
return;
}
type = gtk_builder_get_type_from_name (data->builder, type_name);
if (type == G_TYPE_INVALID)
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Invalid type '%s'", type_name);
_gtk_builder_prefix_error (data->builder, &data->ctx, error);
return;
}
/* Swapped defaults to FALSE except when object is set */
if (swapped == -1)
{
if (object_name)
swapped = TRUE;
else
swapped = FALSE;
}
info = g_slice_new0 (ExpressionInfo);
info->tag_type = TAG_EXPRESSION;
info->expression_type = EXPRESSION_CLOSURE;
info->closure.type = type;
info->closure.swapped = swapped;
info->closure.function_name = g_strdup (function_name);
info->closure.object_name = g_strdup (object_name);
state_push (data, info);
}
static void
parse_lookup_expression (ParserData *data,
const gchar *element_name,
const gchar **names,
const gchar **values,
GError **error)
{
ExpressionInfo *info;
const char *property_name;
const char *type_name;
GType type;
if (!check_expression_parent (data))
{
error_invalid_tag (data, element_name, NULL, error);
return;
}
if (!g_markup_collect_attributes (element_name, names, values, error,
G_MARKUP_COLLECT_STRING, "type", &type_name,
G_MARKUP_COLLECT_STRING, "name", &property_name,
G_MARKUP_COLLECT_INVALID))
{
_gtk_builder_prefix_error (data->builder, &data->ctx, error);
return;
}
type = gtk_builder_get_type_from_name (data->builder, type_name);
if (type == G_TYPE_INVALID)
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Invalid type '%s'", type_name);
_gtk_builder_prefix_error (data->builder, &data->ctx, error);
return;
}
info = g_slice_new0 (ExpressionInfo);
info->tag_type = TAG_EXPRESSION;
info->expression_type = EXPRESSION_PROPERTY;
info->property.this_type = type;
info->property.property_name = g_strdup (property_name);
state_push (data, info);
}
static GtkExpression *
expression_info_construct (GtkBuilder *builder,
ExpressionInfo *info,
GError **error)
{
switch (info->expression_type)
{
case EXPRESSION_EXPRESSION:
break;
case EXPRESSION_CONSTANT:
{
GtkExpression *expr;
GValue value = G_VALUE_INIT;
if (!gtk_builder_value_from_string_type (builder,
info->constant.type,
info->constant.text->str,
&value,
error))
return NULL;
if (G_VALUE_HOLDS_OBJECT (&value))
expr = gtk_object_expression_new (g_value_get_object (&value));
else
expr = gtk_constant_expression_new_for_value (&value);
g_value_unset (&value);
g_string_free (info->constant.text, TRUE);
info->expression_type = EXPRESSION_EXPRESSION;
info->expression = expr;
}
break;
case EXPRESSION_CLOSURE:
{
GObject *object;
GClosure *closure;
guint i, n_params;
GtkExpression **params;
GtkExpression *expression;
GSList *l;
if (info->closure.object_name)
{
object = gtk_builder_lookup_object (builder, info->closure.object_name, 0, 0, error);
if (object == NULL)
return NULL;
}
else
{
object = NULL;
}
closure = gtk_builder_create_closure (builder,
info->closure.function_name,
info->closure.swapped,
object,
error);
if (closure == NULL)
return NULL;
n_params = g_slist_length (info->closure.params);
params = g_newa (GtkExpression *, n_params);
i = n_params;
for (l = info->closure.params; l; l = l->next)
{
params[--i] = expression_info_construct (builder, l->data, error);
if (params[i] == NULL)
return NULL;
}
expression = gtk_closure_expression_new (info->closure.type, closure, n_params, params);
g_free (info->closure.function_name);
g_free (info->closure.object_name);
g_slist_free_full (info->closure.params, (GDestroyNotify) free_expression_info);
info->expression_type = EXPRESSION_EXPRESSION;
info->expression = expression;
}
break;
case EXPRESSION_PROPERTY:
{
GtkExpression *expression;
if (info->property.expression)
{
expression = expression_info_construct (builder, info->property.expression, error);
if (expression == NULL)
return NULL;
free_expression_info (info->property.expression);
}
else
expression = NULL;
expression = gtk_property_expression_new (info->property.this_type,
expression,
info->property.property_name);
g_free (info->property.property_name);
info->expression_type = EXPRESSION_EXPRESSION;
info->expression = expression;
}
break;
default:
g_return_val_if_reached (NULL);
}
return gtk_expression_ref (info->expression);
}
static void
parse_signal (ParserData *data,
const gchar *element_name,
@@ -1287,6 +1625,12 @@ start_element (GtkBuildableParseContext *context,
parse_requires (data, element_name, names, values, error);
else if (strcmp (element_name, "interface") == 0)
parse_interface (data, element_name, names, values, error);
else if (strcmp (element_name, "constant") == 0)
parse_constant_expression (data, element_name, names, values, error);
else if (strcmp (element_name, "closure") == 0)
parse_closure_expression (data, element_name, names, values, error);
else if (strcmp (element_name, "lookup") == 0)
parse_lookup_expression (data, element_name, names, values, error);
else if (strcmp (element_name, "menu") == 0)
_gtk_builder_menu_start (data, element_name, names, values, error);
else if (strcmp (element_name, "placeholder") == 0)
@@ -1422,6 +1766,43 @@ end_element (GtkBuildableParseContext *context,
signal_info->object_name = g_strdup (object_info->id);
object_info->signals = g_slist_prepend (object_info->signals, signal_info);
}
else if (strcmp (element_name, "constant") == 0 ||
strcmp (element_name, "closure") == 0 ||
strcmp (element_name, "lookup") == 0)
{
ExpressionInfo *expression_info = state_pop_info (data, ExpressionInfo);
CommonInfo *parent_info = state_peek_info (data, CommonInfo);
if (parent_info->tag_type == TAG_PROPERTY)
{
PropertyInfo *prop_info = (PropertyInfo *) parent_info;
prop_info->value = expression_info_construct (data->builder, expression_info, error);
}
else if (parent_info->tag_type == TAG_EXPRESSION)
{
ExpressionInfo *expr_info = (ExpressionInfo *) parent_info;
switch (expr_info->expression_type)
{
case EXPRESSION_CLOSURE:
expr_info->closure.params = g_slist_prepend (expr_info->closure.params, expression_info);
break;
case EXPRESSION_PROPERTY:
expr_info->property.expression = expression_info;
break;
case EXPRESSION_EXPRESSION:
case EXPRESSION_CONSTANT:
default:
g_assert_not_reached ();
break;
}
}
else
{
g_assert_not_reached ();
}
}
else if (strcmp (element_name, "requires") == 0)
{
RequiresInfo *req_info = state_pop_info (data, RequiresInfo);
@@ -1502,6 +1883,12 @@ text (GtkBuildableParseContext *context,
g_string_append_len (prop_info->text, text, text_len);
}
else if (strcmp (gtk_buildable_parse_context_get_element (context), "constant") == 0)
{
ExpressionInfo *expr_info = (ExpressionInfo *) info;
g_string_append_len (expr_info->constant.text, text, text_len);
}
}
static void
@@ -1525,6 +1912,9 @@ free_info (CommonInfo *info)
case TAG_REQUIRES:
free_requires_info ((RequiresInfo *)info, NULL);
break;
case TAG_EXPRESSION:
free_expression_info ((ExpressionInfo *)info);
break;
default:
g_assert_not_reached ();
}

View File

@@ -21,6 +21,7 @@
#include "gtkbuilder.h"
#include "gtkbuildable.h"
#include "gtkexpression.h"
enum {
TAG_PROPERTY,
@@ -31,6 +32,7 @@ enum {
TAG_SIGNAL,
TAG_INTERFACE,
TAG_TEMPLATE,
TAG_EXPRESSION,
};
typedef struct {
@@ -64,6 +66,7 @@ typedef struct {
typedef struct {
guint tag_type;
GParamSpec *pspec;
gpointer value;
GString *text;
gboolean translatable:1;
gboolean bound:1;
@@ -72,6 +75,36 @@ typedef struct {
gint col;
} PropertyInfo;
typedef struct _ExpressionInfo ExpressionInfo;
struct _ExpressionInfo {
guint tag_type;
enum {
EXPRESSION_EXPRESSION,
EXPRESSION_CONSTANT,
EXPRESSION_CLOSURE,
EXPRESSION_PROPERTY
} expression_type;
union {
GtkExpression *expression;
struct {
GType type;
GString *text;
} constant;
struct {
GType type;
char *function_name;
char *object_name;
gboolean swapped;
GSList *params;
} closure;
struct {
GType this_type;
char *property_name;
ExpressionInfo *expression;
} property;
};
};
typedef struct {
guint tag_type;
gchar *object_name;

View File

@@ -50,6 +50,44 @@
*
* Watches can be created for automatically updating the propery of an object,
* similar to GObject's #GBinding mechanism, by using gtk_expression_bind().
*
* #GtkExpression in ui files
*
* GtkBuilder has support for creating expressions. The syntax here can be used where
* a #GtkExpression object is needed like in a <property> tag for an expression
* property, or in a <binding> tag to bind a property to an expression.
*
* To create an property expression, use the <lookup> element. It can have a `type`
* attribute to specify the object type, and a `name` attribute to specify the property
* to look up. The content of <lookup> can either be an element specfiying the expression
* to use the object, or a string that specifies the name of the object to use.
*
* Example:
* |[
* <lookup name='search'>string_filter</lookup>
* |]
*
* To create a constant expression, use the <constant> element. If the type attribute
* is specified, the element content is interpreted as a value of that type. Otherwise,
* it is assumed to be an object.
*
* Example:
* |[
* <constant>string_filter</constant>
* <constant type='gchararray'>Hello, world</constant>
* ]|
*
* To create a closure expression, use the <closure> element. The `type` and `function`
* attributes specify what function to use for the closure, the content of the element
* contains the expressions for the parameters.
*
* Example:
* |[
* <closure type='gchararray' function='combine_args_somehow'>
* <constant type='gchararray'>File size:</constant>
* <lookup type='gint64' name='size'>myfile</lookup>
* </closure>
* ]|
*/
typedef struct _GtkExpressionClass GtkExpressionClass;

View File

@@ -2643,6 +2643,64 @@ test_file_filter (void)
g_object_unref (builder);
}
char *
builder_get_search (gpointer item)
{
return g_strdup (gtk_string_filter_get_search (item));
}
char *
builder_copy_arg (gpointer item, const char *arg)
{
return g_strdup (arg);
}
static void
test_expressions (void)
{
const char *tests[] = {
"<interface>"
" <object class='GtkStringFilter' id='filter'>"
" <property name='search'>Hello World</property>"
" <property name='expression'><constant type='gchararray'>Hello World</constant></property>"
" </object>"
"</interface>",
"<interface>"
" <object class='GtkStringFilter' id='filter'>"
" <property name='search'>Hello World</property>"
" <property name='expression'><closure type='gchararray' function='builder_get_search'></closure></property>"
" </object>"
"</interface>",
"<interface>"
" <object class='GtkStringFilter' id='filter'>"
" <property name='search'>Hello World</property>"
" <property name='expression'><lookup type='GtkStringFilter' name='search'></lookup></property>"
" </object>"
"</interface>",
"<interface>"
" <object class='GtkStringFilter' id='filter'>"
" <property name='search'>Hello World</property>"
" <property name='expression'><closure type='gchararray' function='builder_copy_arg'>"
" <constant type='gchararray'>Hello World</constant>"
" </closure></property>"
" </object>"
"</interface>",
};
GtkBuilder *builder;
GObject *obj;
guint i;
for (i = 0; i < G_N_ELEMENTS (tests); i++)
{
builder = builder_new_from_string (tests[i], -1, NULL);
obj = gtk_builder_get_object (builder, "filter");
g_assert (GTK_IS_FILTER (obj));
g_assert (gtk_filter_match (GTK_FILTER (obj), obj));
g_object_unref (builder);
}
}
int
main (int argc, char **argv)
{
@@ -2695,6 +2753,7 @@ main (int argc, char **argv)
g_test_add_func ("/Builder/Property Bindings", test_property_bindings);
g_test_add_func ("/Builder/anaconda-signal", test_anaconda_signal);
g_test_add_func ("/Builder/FileFilter", test_file_filter);
g_test_add_func ("/Builder/Expressions", test_expressions);
return g_test_run();
}