[gtk/wip/otte/listview: 47/199] builder: Add support for parsing expressions



commit 3446c5b04a9c9623c85ce58d06ea2eaef61a63d1
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  | 392 +++++++++++++++++++++++++++++++++++++++++++++++-
 gtk/gtkbuilderprivate.h |  33 ++++
 gtk/gtkexpression.c     |  38 +++++
 testsuite/gtk/builder.c |  59 ++++++++
 5 files changed, 533 insertions(+), 2 deletions(-)
---
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index d1c944844b..e436a3b351 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -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 (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 7b781c17d1..f1f6834c5d 100644
--- a/gtk/gtkbuilderparser.c
+++ b/gtk/gtkbuilderparser.c
@@ -933,7 +933,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 ("");
@@ -949,11 +949,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,
@@ -1288,6 +1626,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)
@@ -1423,6 +1767,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);
@@ -1503,6 +1884,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
@@ -1526,6 +1913,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 49833ae9d5..0e9f73b3fd 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 a4e8a725e1..634cc15207 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 645f86bb03..55aaaf9d37 100644
--- a/testsuite/gtk/builder.c
+++ b/testsuite/gtk/builder.c
@@ -2513,6 +2513,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)
 {
@@ -2564,6 +2622,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();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]