[gtk/wip/otte/listview: 9/138] builder: Add support for parsing expressions



commit 3b08e865dae1bca5a87efac64e872df231f71e7a
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  | 340 +++++++++++++++++++++++++++++++++++++++++++++++-
 gtk/gtkbuilderprivate.h |  26 ++++
 testsuite/gtk/builder.c |  59 +++++++++
 4 files changed, 436 insertions(+), 2 deletions(-)
---
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index 8bcf58134e..b605b22759 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -565,7 +565,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) &&
diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c
index a853151806..b3e6a59e70 100644
--- a/gtk/gtkbuilderparser.c
+++ b/gtk/gtkbuilderparser.c
@@ -947,7 +947,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 ("");
@@ -963,11 +963,300 @@ 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;
+
+    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;
+
+      return expr_info->expression_type = EXPRESSION_CLOSURE;
+    }
+
+  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_EXPRESSION;
+  info->expression = gtk_property_expression_new (type, 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:
+      {
+        GValue value = G_VALUE_INIT;
+
+        if (!gtk_builder_value_from_string_type (builder,
+                                                 info->constant.type,
+                                                 info->constant.text->str,
+                                                 &value,
+                                                 error))
+          return  NULL;
+
+        g_string_free (info->constant.text, TRUE);
+        info->expression_type = EXPRESSION_EXPRESSION;
+        info->expression = gtk_constant_expression_new_for_value (&value);
+        g_value_unset (&value);
+      }
+      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);
+        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;
+
+    default:
+      g_return_val_if_reached (NULL);
+    }
+
+  return gtk_expression_ref (info->expression);
+}
+
 static void
 parse_signal (ParserData   *data,
               const gchar  *element_name,
@@ -1302,6 +1591,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)
@@ -1437,6 +1732,40 @@ 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_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);
@@ -1517,6 +1846,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
@@ -1540,6 +1875,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 0e72efe273..5160ab535d 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,29 @@ typedef struct {
   gint col;
 } PropertyInfo;
 
+typedef struct {
+  guint tag_type;
+  enum {
+    EXPRESSION_EXPRESSION,
+    EXPRESSION_CONSTANT,
+    EXPRESSION_CLOSURE
+  } 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;
+  };
+} ExpressionInfo;
+
 typedef struct {
   guint tag_type;
   gchar *object_name;
diff --git a/testsuite/gtk/builder.c b/testsuite/gtk/builder.c
index 5513834256..a5c733c413 100644
--- a/testsuite/gtk/builder.c
+++ b/testsuite/gtk/builder.c
@@ -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_filter (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();
 }


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