[gtk/listview-for-merge: 6/163] builder: Add support for parsing expressions
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/listview-for-merge: 6/163] builder: Add support for parsing expressions
- Date: Sat, 30 May 2020 21:59:37 +0000 (UTC)
commit fde75aa9f68530b693f47a4ef327dc29a3070ee5
Author: Benjamin Otte <otte redhat com>
Date: Mon Nov 18 04:35:36 2019 +0100
builder: Add support for parsing expressions
gtk/gtkbuilder.c | 13 +-
gtk/gtkbuilderparser.c | 439 +++++++++++++++++++++++++++++++++++++++++++++++-
gtk/gtkbuilderprivate.h | 33 ++++
gtk/gtkexpression.c | 38 +++++
testsuite/gtk/builder.c | 59 +++++++
5 files changed, 580 insertions(+), 2 deletions(-)
---
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index 3f8ba5329e..244f8e8a9a 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -527,7 +527,18 @@ gtk_builder_get_parameters (GtkBuilder *builder,
const char *property_name = g_intern_string (prop->pspec->name);
GValue property_value = G_VALUE_INIT;
- if (prop->bound && (!prop->text || prop->text->len == 0))
+ 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 (prop->bound && (!prop->text || prop->text->len == 0))
{
/* Ignore properties with a binding and no value since they are
* only there for to express the binding.
diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c
index fd2d6d91f7..00e856f05a 100644
--- a/gtk/gtkbuilderparser.c
+++ b/gtk/gtkbuilderparser.c
@@ -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,395 @@ 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:
+ g_clear_pointer (&info->expression, gtk_expression_unref);
+ 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;
+ GType type;
+ GParamSpec *pspec;
+
+ if (info->property.expression)
+ {
+ expression = expression_info_construct (builder, info->property.expression, error);
+ if (expression == NULL)
+ return NULL;
+ g_clear_pointer (&info->property.expression, free_expression_info);
+ }
+ else
+ expression = NULL;
+
+ if (info->property.this_type != G_TYPE_INVALID)
+ type = info->property.this_type;
+ else if (expression != NULL)
+ type = gtk_expression_get_value_type (expression);
+ else
+ {
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
+ "Lookups require a type attribute if they don't have an expression.");
+ return NULL;
+ }
+
+ if (g_type_is_a (type, G_TYPE_OBJECT))
+ {
+ GObjectClass *class = g_type_class_ref (type);
+ pspec = g_object_class_find_property (class, info->property.property_name);
+ g_type_class_unref (class);
+ }
+ else if (g_type_is_a (type, G_TYPE_INTERFACE))
+ {
+ GTypeInterface *iface = g_type_default_interface_ref (type);
+ pspec = g_object_interface_find_property (iface, info->property.property_name);
+ g_type_default_interface_unref (iface);
+ }
+ else
+ {
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
+ "Type `%s` does not support properties",
+ g_type_name (type));
+ return NULL;
+ }
+
+ if (pspec == NULL)
+ {
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
+ "Type `%s` does not have a property name `%s`",
+ g_type_name (type), info->property.property_name);
+ return NULL;
+ }
+
+ expression = gtk_property_expression_new_for_pspec (expression, pspec);
+
+ 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 +1671,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 +1812,44 @@ 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);
+ g_assert (parent_info != NULL);
+
+ 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 +1930,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 +1959,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 ();
}
diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h
index 9ddf24b402..32227c10f1 100644
--- a/gtk/gtkbuilderprivate.h
+++ b/gtk/gtkbuilderprivate.h
@@ -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;
diff --git a/gtk/gtkexpression.c b/gtk/gtkexpression.c
index d6b0c78206..0014a06b69 100644
--- a/gtk/gtkexpression.c
+++ b/gtk/gtkexpression.c
@@ -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='GFile' name='size'>myfile</lookup>
+ * </closure>
+ * ]|
*/
typedef struct _GtkExpressionClass GtkExpressionClass;
diff --git a/testsuite/gtk/builder.c b/testsuite/gtk/builder.c
index d545b3a667..db1eb2dd04 100644
--- a/testsuite/gtk/builder.c
+++ b/testsuite/gtk/builder.c
@@ -2492,6 +2492,64 @@ test_transforms (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)
{
@@ -2538,6 +2596,7 @@ main (int argc, char **argv)
g_test_add_func ("/Builder/FileFilter", test_file_filter);
g_test_add_func ("/Builder/Shortcuts", test_shortcuts);
g_test_add_func ("/Builder/Transforms", test_transforms);
+ g_test_add_func ("/Builder/Expressions", test_expressions);
return g_test_run();
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]