[gtk/wip/otte/listview: 69/113] Add GtkExpression
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/listview: 69/113] Add GtkExpression
- Date: Tue, 5 Nov 2019 17:38:07 +0000 (UTC)
commit e2abf0c3e59049ba43b099705afe75b64f9eae1a
Author: Benjamin Otte <otte redhat com>
Date: Thu Oct 17 05:21:31 2019 +0200
Add GtkExpression
This is the new replacement for bindings, though I'm not entirely sure
what I'm doing.
demos/gtk-demo/fishbowl.ui | 17 +-
gtk/gtkbuilder.c | 62 +-
gtk/gtkbuilderparser.c | 136 +++-
gtk/gtkbuilderprivate.h | 9 +-
gtk/gtkexpression.c | 1699 ++++++++++++++++++++++++++++++++++++++++++++
gtk/gtkexpressionprivate.h | 54 ++
gtk/meson.build | 1 +
7 files changed, 1936 insertions(+), 42 deletions(-)
---
diff --git a/demos/gtk-demo/fishbowl.ui b/demos/gtk-demo/fishbowl.ui
index 5d09b3477a..ab8201f6d7 100644
--- a/demos/gtk-demo/fishbowl.ui
+++ b/demos/gtk-demo/fishbowl.ui
@@ -28,22 +28,7 @@
</child>
<child type="end">
<object class="GtkLabel">
- <property name="label">fps</property>
- </object>
- </child>
- <child type="end">
- <object class="GtkLabel">
- <property name="label" bind-source="bowl" bind-property="framerate-string"/>
- </object>
- </child>
- <child type="end">
- <object class="GtkLabel">
- <property name="label">Icons, </property>
- </object>
- </child>
- <child type="end">
- <object class="GtkLabel">
- <property name="label" bind-source="bowl" bind-property="count"/>
+ <binding name="label">bowl.count + " Icons, " + bowl.framerate + "fps"</binding>
</object>
</child>
<child type="end">
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index 78f3fee3f8..376fc6eace 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -209,6 +209,7 @@
#include "gtkbuildable.h"
#include "gtkbuilderprivate.h"
#include "gtkdebug.h"
+#include "gtkexpressionprivate.h"
#include "gtkmain.h"
#include "gtkintl.h"
#include "gtkprivate.h"
@@ -992,19 +993,9 @@ gtk_builder_apply_delayed_properties (GtkBuilder *builder)
g_slist_free (props);
}
-static inline void
-free_binding_info (gpointer data,
- gpointer user)
-{
- BindingInfo *info = data;
-
- g_free (info->source);
- g_free (info->source_property);
- g_slice_free (BindingInfo, data);
-}
-
-static inline void
-gtk_builder_create_bindings (GtkBuilder *builder)
+static inline gboolean
+gtk_builder_create_bindings (GtkBuilder *builder,
+ GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GSList *l;
@@ -1014,24 +1005,51 @@ gtk_builder_create_bindings (GtkBuilder *builder)
BindingInfo *info = l->data;
GObject *source;
- source = _gtk_builder_lookup_object (builder, info->source, info->line, info->col);
- if (source)
- g_object_bind_property (source, info->source_property,
- info->target, info->target_pspec->name,
- info->flags);
+ if (info->tag_type == TAG_BINDING)
+ {
+ source = _gtk_builder_lookup_object (builder, info->source, info->line, info->col);
+ if (source)
+ g_object_bind_property (source, info->source_property,
+ info->target, info->target_pspec->name,
+ info->flags);
+ }
+ else
+ {
+ GtkExpression *expression, *assign;
+ GValue value = G_VALUE_INIT;
+
+ expression = gtk_expression_parse (builder, info->source, error);
+ if (expression == NULL)
+ {
+ g_prefix_error (error, "%s:%d:%d: ", priv->filename, info->line, info->col);
+ goto fail;
+ }
+ assign = gtk_expression_new_assign (info->target, info->target_pspec->name, expression);
+ if (gtk_expression_evaluate (assign, &value))
+ g_value_unset (&value);
+ gtk_expression_unref (assign);
+ }
+
+ _free_binding_info (info, NULL);
+ }
- free_binding_info (info, NULL);
+fail:
+ for (; l; l = l->next)
+ {
+ _free_binding_info (l->data, NULL);
}
g_slist_free (priv->bindings);
priv->bindings = NULL;
+ return TRUE;
}
-void
-_gtk_builder_finish (GtkBuilder *builder)
+gboolean
+_gtk_builder_finish (GtkBuilder *builder,
+ GError **error)
{
gtk_builder_apply_delayed_properties (builder);
- gtk_builder_create_bindings (builder);
+ return gtk_builder_create_bindings (builder, error);
}
/**
diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c
index 954a06b772..20e3ab245e 100644
--- a/gtk/gtkbuilderparser.c
+++ b/gtk/gtkbuilderparser.c
@@ -928,7 +928,8 @@ parse_property (ParserData *data,
{
BindingInfo *binfo;
- binfo = g_slice_new (BindingInfo);
+ binfo = g_slice_new0 (BindingInfo);
+ binfo->tag_type = TAG_BINDING;
binfo->target = NULL;
binfo->target_pspec = pspec;
binfo->source = g_strdup (bind_source);
@@ -960,6 +961,87 @@ parse_property (ParserData *data,
state_push (data, info);
}
+static void
+parse_binding (ParserData *data,
+ const gchar *element_name,
+ const gchar **names,
+ const gchar **values,
+ GError **error)
+{
+ BindingInfo *info;
+ const gchar *name = NULL;
+ const gchar *context = NULL;
+ gboolean translatable = FALSE;
+ ObjectInfo *object_info;
+ GParamSpec *pspec = NULL;
+ gint line, col;
+
+ object_info = state_peek_info (data, ObjectInfo);
+ if (!object_info ||
+ !(object_info->tag_type == TAG_OBJECT ||
+ object_info->tag_type == TAG_TEMPLATE))
+ {
+ error_invalid_tag (data, element_name, NULL, error);
+ return;
+ }
+
+ if (!g_markup_collect_attributes (element_name, names, values, error,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "translatable",
&translatable,
+ G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "context", &context,
+ G_MARKUP_COLLECT_INVALID))
+ {
+ _gtk_builder_prefix_error (data->builder, &data->ctx, error);
+ return;
+ }
+
+ pspec = g_object_class_find_property (object_info->oclass, name);
+
+ if (!pspec)
+ {
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_PROPERTY,
+ "Invalid property: %s.%s",
+ g_type_name (object_info->type), name);
+ _gtk_builder_prefix_error (data->builder, &data->ctx, error);
+ return;
+ }
+ else if (pspec->flags & G_PARAM_CONSTRUCT_ONLY)
+ {
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_PROPERTY,
+ "%s.%s is a construct-only property",
+ g_type_name (object_info->type), name);
+ _gtk_builder_prefix_error (data->builder, &data->ctx, error);
+ return;
+ }
+ else if (!(pspec->flags & G_PARAM_WRITABLE))
+ {
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_PROPERTY,
+ "%s.%s is a non-writable property",
+ g_type_name (object_info->type), name);
+ _gtk_builder_prefix_error (data->builder, &data->ctx, error);
+ return;
+ }
+
+ gtk_buildable_parse_context_get_position (&data->ctx, &line, &col);
+
+ info = g_slice_new0 (BindingInfo);
+ info->tag_type = TAG_EXPRESSION;
+ info->target = NULL;
+ info->target_pspec = pspec;
+ info->source = NULL;
+ info->flags = 0;
+ info->line = line;
+ info->col = col;
+
+ state_push (data, info);
+}
+
static void
free_property_info (PropertyInfo *info)
{
@@ -1052,6 +1134,16 @@ _free_signal_info (SignalInfo *info,
g_slice_free (SignalInfo, info);
}
+void
+_free_binding_info (BindingInfo *info,
+ gpointer user)
+{
+ g_free (info->source);
+ g_free (info->source_property);
+ g_slice_free (BindingInfo, info);
+}
+
+
static void
free_requires_info (RequiresInfo *info,
gpointer user_data)
@@ -1292,6 +1384,8 @@ start_element (GtkBuildableParseContext *context,
}
else if (strcmp (element_name, "property") == 0)
parse_property (data, element_name, names, values, error);
+ else if (strcmp (element_name, "binding") == 0)
+ parse_binding (data, element_name, names, values, error);
else if (strcmp (element_name, "child") == 0)
parse_child (data, element_name, names, values, error);
else if (strcmp (element_name, "signal") == 0)
@@ -1377,6 +1471,23 @@ end_element (GtkBuildableParseContext *context,
else
g_assert_not_reached ();
}
+ else if (strcmp (element_name, "binding") == 0)
+ {
+ BindingInfo *binfo = state_pop_info (data, BindingInfo);
+ CommonInfo *info = state_peek_info (data, CommonInfo);
+
+ g_assert (info != NULL);
+
+ /* Normal properties */
+ if (info->tag_type == TAG_OBJECT ||
+ info->tag_type == TAG_TEMPLATE)
+ {
+ ObjectInfo *object_info = (ObjectInfo*)info;
+ object_info->bindings = g_slist_prepend (object_info->bindings, binfo);
+ }
+ else
+ g_assert_not_reached ();
+ }
else if (strcmp (element_name, "object") == 0 ||
strcmp (element_name, "template") == 0)
{
@@ -1517,6 +1628,22 @@ text (GtkBuildableParseContext *context,
g_string_append_len (prop_info->text, text, text_len);
}
+ else if (strcmp (gtk_buildable_parse_context_get_element (context), "binding") == 0)
+ {
+ BindingInfo *binfo = (BindingInfo *) info;
+
+ if (binfo->source == NULL)
+ {
+ binfo->source = g_strndup (text, text_len);
+ }
+ else
+ {
+ char *s;
+ s = g_strdup_printf ("%s%*s", binfo->source, (guint) text_len, text);
+ g_free (binfo->source);
+ binfo->source = s;
+ }
+ }
}
static void
@@ -1531,6 +1658,10 @@ free_info (CommonInfo *info)
case TAG_CHILD:
free_child_info ((ChildInfo *)info);
break;
+ case TAG_BINDING:
+ case TAG_EXPRESSION:
+ _free_binding_info ((BindingInfo *)info, NULL);
+ break;
case TAG_PROPERTY:
free_property_info ((PropertyInfo *)info);
break;
@@ -1594,7 +1725,8 @@ _gtk_builder_parser_parse_buffer (GtkBuilder *builder,
if (!gtk_buildable_parse_context_parse (&data.ctx, buffer, length, error))
goto out;
- _gtk_builder_finish (builder);
+ if (!_gtk_builder_finish (builder, error))
+ goto out;
if (_gtk_builder_lookup_failed (builder, error))
goto out;
diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h
index 0846efa378..b9ed6d58d6 100644
--- a/gtk/gtkbuilderprivate.h
+++ b/gtk/gtkbuilderprivate.h
@@ -24,7 +24,8 @@
enum {
TAG_PROPERTY,
- TAG_MENU,
+ TAG_BINDING,
+ TAG_EXPRESSION,
TAG_REQUIRES,
TAG_OBJECT,
TAG_CHILD,
@@ -84,6 +85,7 @@ typedef struct {
typedef struct
{
+ guint tag_type;
GObject *target;
GParamSpec *target_pspec;
gchar *source;
@@ -175,9 +177,12 @@ void _gtk_builder_add (GtkBuilder *builder,
ChildInfo *child_info);
void _gtk_builder_add_signals (GtkBuilder *builder,
GSList *signals);
-void _gtk_builder_finish (GtkBuilder *builder);
+gboolean _gtk_builder_finish (GtkBuilder *builder,
+ GError **error);
void _free_signal_info (SignalInfo *info,
gpointer user_data);
+void _free_binding_info (BindingInfo *info,
+ gpointer user_data);
/* Internal API which might be made public at some point */
gboolean _gtk_builder_boolean_from_string (const gchar *string,
diff --git a/gtk/gtkexpression.c b/gtk/gtkexpression.c
new file mode 100644
index 0000000000..4a5b483aa3
--- /dev/null
+++ b/gtk/gtkexpression.c
@@ -0,0 +1,1699 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "gtkexpressionprivate.h"
+
+#include <gtk/css/gtkcss.h>
+#include <gtk/css/gtkcssparserprivate.h>
+#include "gtk/gtkbuildable.h"
+
+typedef struct _GtkExpressionClass GtkExpressionClass;
+
+struct _GtkExpression
+{
+ const GtkExpressionClass *expression_class;
+ GType value_type;
+
+ GtkExpression *owner;
+};
+
+struct _GtkExpressionClass
+{
+ gsize struct_size;
+ const char *type_name;
+
+ void (* finalize) (GtkExpression *expr);
+ void (* print) (GtkExpression *expr,
+ GString *string);
+ void (* notify) (GtkExpression *expr,
+ GtkExpression *source);
+ gboolean (* evaluate) (GtkExpression *expr,
+ GValue *value);
+};
+
+/**
+ * GtkExpression: (ref-func gtk_expression_ref) (unref-func gtk_expression_unref)
+ *
+ * The `GtkExpression` structure contains only private data.
+ */
+
+/*< private >
+ * gtk_expression_alloc:
+ * @expression_class: class structure for this expression
+ * @value_type: the type of the value returned by this expression
+ *
+ * Returns: (transfer full): the newly created #GtkExpression
+ */
+static gpointer
+gtk_expression_alloc (const GtkExpressionClass *expression_class,
+ GType value_type)
+{
+ GtkExpression *self;
+
+ g_return_val_if_fail (expression_class != NULL, NULL);
+
+ self = g_atomic_rc_box_alloc0 (expression_class->struct_size);
+
+ self->expression_class = expression_class;
+ self->value_type = value_type;
+
+ return self;
+}
+
+static void
+gtk_expression_notify (GtkExpression *self)
+{
+ if (self->owner == NULL)
+ return;
+
+ self->owner->expression_class->notify (self->owner, self);
+}
+
+static void
+gtk_expression_set_owner (GtkExpression *self,
+ GtkExpression *owner)
+{
+ g_assert (self->owner == NULL);
+ self->owner = owner;
+}
+
+/*** LITERAL ***/
+
+typedef struct _GtkLiteralExpression GtkLiteralExpression;
+
+struct _GtkLiteralExpression
+{
+ GtkExpression parent;
+
+ GValue value;
+};
+
+static void
+gtk_literal_expression_finalize (GtkExpression *expr)
+{
+ GtkLiteralExpression *self = (GtkLiteralExpression *) expr;
+
+ g_value_unset (&self->value);
+}
+
+static void
+gtk_literal_expression_print (GtkExpression *expr,
+ GString *string)
+{
+ GtkLiteralExpression *self = (GtkLiteralExpression *) expr;
+ char *s;
+
+ s = g_strdup_value_contents (&self->value);
+ g_string_append (string, s);
+ g_free (s);
+}
+
+static gboolean
+gtk_literal_expression_evaluate (GtkExpression *expr,
+ GValue *value)
+{
+ GtkLiteralExpression *self = (GtkLiteralExpression *) expr;
+
+ g_value_init (value, G_VALUE_TYPE (&self->value));
+ g_value_copy (&self->value, value);
+ return TRUE;
+}
+
+static const GtkExpressionClass GTK_LITERAL_EXPRESSION_CLASS =
+{
+ sizeof (GtkLiteralExpression),
+ "GtkLiteralExpression",
+ gtk_literal_expression_finalize,
+ gtk_literal_expression_print,
+ NULL,
+ gtk_literal_expression_evaluate,
+};
+
+/*** OBJECT ***/
+
+typedef struct _GtkObjectExpression GtkObjectExpression;
+
+struct _GtkObjectExpression
+{
+ GtkExpression parent;
+
+ GObject *object;
+};
+
+static void
+gtk_object_expression_weak_cb (gpointer expr,
+ GObject *unused)
+{
+ GtkObjectExpression *self = (GtkObjectExpression *) expr;
+
+ self->object = NULL;
+ gtk_expression_notify (expr);
+}
+
+static void
+gtk_object_expression_finalize (GtkExpression *expr)
+{
+ GtkObjectExpression *self = (GtkObjectExpression *) expr;
+
+ if (self->object)
+ g_object_weak_unref (self->object, gtk_object_expression_weak_cb, self);
+}
+
+static void
+print_object (GObject *object,
+ GString *string)
+{
+ if (GTK_IS_BUILDABLE (object) && gtk_buildable_get_name (GTK_BUILDABLE (object)))
+ {
+ g_string_append (string, gtk_buildable_get_name (GTK_BUILDABLE (object)));
+ }
+ else if (object == NULL)
+ {
+ g_string_append (string, "NULL");
+ }
+ else
+ {
+ g_string_append_printf (string, "0x%p:%s", object, G_OBJECT_TYPE_NAME (object));
+ }
+}
+
+static void
+gtk_object_expression_print (GtkExpression *expr,
+ GString *string)
+{
+ GtkObjectExpression *self = (GtkObjectExpression *) expr;
+
+ print_object (self->object, string);
+}
+
+static gboolean
+gtk_object_expression_evaluate (GtkExpression *expr,
+ GValue *value)
+{
+ GtkObjectExpression *self = (GtkObjectExpression *) expr;
+
+ if (self->object == NULL)
+ return FALSE;
+
+ g_value_init (value, G_OBJECT_TYPE (self->object));
+ g_value_set_object (value, self->object);
+
+ return TRUE;
+}
+
+static const GtkExpressionClass GTK_OBJECT_EXPRESSION_CLASS =
+{
+ sizeof (GtkObjectExpression),
+ "GtkObjectExpression",
+ gtk_object_expression_finalize,
+ gtk_object_expression_print,
+ NULL,
+ gtk_object_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_object_expression_new (GObject *object)
+{
+ GtkObjectExpression *result;
+
+ result = gtk_expression_alloc (>K_OBJECT_EXPRESSION_CLASS, G_OBJECT_TYPE (object));
+ result->object = object;
+ g_object_weak_ref (result->object, gtk_object_expression_weak_cb, result);
+
+ return (GtkExpression *) result;
+}
+
+/*** PROPERTY ***/
+
+typedef struct _GtkPropertyExpression GtkPropertyExpression;
+
+struct _GtkPropertyExpression
+{
+ GtkExpression parent;
+
+ GtkExpression *expr;
+ GParamSpec *pspec;
+
+ GClosure *closure;
+};
+
+static void
+gtk_property_expression_finalize (GtkExpression *expr)
+{
+ GtkPropertyExpression *self = (GtkPropertyExpression *) expr;
+
+ gtk_expression_unref (self->expr);
+ if (self->closure)
+ {
+ g_closure_invalidate (self->closure);
+ g_closure_unref (self->closure);
+ }
+}
+
+static void
+gtk_property_expression_print (GtkExpression *expr,
+ GString *string)
+{
+ GtkPropertyExpression *self = (GtkPropertyExpression *) expr;
+
+ gtk_expression_print (self->expr, string);
+ g_string_append (string, ".");
+ g_string_append (string, self->pspec->name);
+}
+
+static void
+gtk_property_expression_notify_cb (GObject *object,
+ GParamSpec *pspec,
+ GtkExpression *expr)
+{
+ gtk_expression_notify (expr);
+}
+
+static void
+gtk_property_expression_connect (GtkPropertyExpression *self)
+{
+ GValue value = G_VALUE_INIT;
+
+ if (gtk_expression_evaluate (self->expr, &value))
+ {
+ GObject *object = g_value_get_object (&value);
+ if (object)
+ {
+ self->closure = g_cclosure_new (G_CALLBACK (gtk_property_expression_notify_cb), self, NULL);
+ if (!g_signal_connect_closure_by_id (object,
+ g_signal_lookup ("notify", G_OBJECT_TYPE (object)),
+ g_quark_from_string (self->pspec->name),
+ g_closure_ref (self->closure),
+ FALSE))
+ {
+ g_assert_not_reached ();
+ }
+ }
+ g_value_unset (&value);
+ }
+}
+
+static void
+gtk_property_expression_notify (GtkExpression *expr,
+ GtkExpression *source)
+{
+ GtkPropertyExpression *self = (GtkPropertyExpression *) expr;
+
+ if (self->closure)
+ {
+ g_closure_invalidate (self->closure);
+ g_clear_pointer (&self->closure, g_closure_unref);
+ }
+
+ gtk_property_expression_connect (self);
+
+ gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_property_expression_evaluate (GtkExpression *expr,
+ GValue *value)
+{
+ GtkPropertyExpression *self = (GtkPropertyExpression *) expr;
+ GValue object_value = G_VALUE_INIT;
+ GObject *object;
+
+ if (!gtk_expression_evaluate (self->expr, &object_value))
+ return FALSE;
+
+ object = g_value_get_object (&object_value);
+ if (object == NULL)
+ {
+ g_value_unset (&object_value);
+ return FALSE;
+ }
+
+ g_object_get_property (object, self->pspec->name, value);
+ g_value_unset (&object_value);
+ return TRUE;
+}
+
+static const GtkExpressionClass GTK_PROPERTY_EXPRESSION_CLASS =
+{
+ sizeof (GtkPropertyExpression),
+ "GtkPropertyExpression",
+ gtk_property_expression_finalize,
+ gtk_property_expression_print,
+ gtk_property_expression_notify,
+ gtk_property_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_property (GtkExpression *expr,
+ GParamSpec *pspec)
+{
+ GtkPropertyExpression *result;
+
+ g_assert (g_type_is_a (gtk_expression_get_value_type (expr), pspec->owner_type));
+
+ result = gtk_expression_alloc (>K_PROPERTY_EXPRESSION_CLASS, pspec->value_type);
+
+ gtk_expression_set_owner (expr, (GtkExpression *) result);
+ result->expr = expr;
+ result->pspec = pspec;
+
+ gtk_property_expression_connect (result);
+
+ return (GtkExpression *) result;
+}
+
+/*** CAST ***/
+
+typedef struct _GtkCastExpression GtkCastExpression;
+
+typedef gboolean (* GtkCastFunc) (GValue *to,
+ const GValue *from);
+
+struct _GtkCastExpression
+{
+ GtkExpression parent;
+
+ GtkExpression *expr;
+ GtkCastFunc cast_func;
+};
+
+static gboolean
+gtk_cast_copy (GValue *to,
+ const GValue *from)
+{
+ g_value_copy (from, to);
+ return TRUE;
+}
+
+static gboolean
+gtk_cast_transform (GValue *to,
+ const GValue *from)
+{
+ g_value_transform (from, to);
+ return TRUE;
+}
+
+static gboolean
+gtk_cast_upcast (GValue *to,
+ const GValue *from)
+{
+ GObject *o = g_value_get_object (from);
+
+ if (o && !g_type_is_a (G_OBJECT_TYPE (o), G_VALUE_TYPE (to)))
+ {
+ g_value_unset (to);
+ return FALSE;
+ }
+
+ g_value_set_object (to, o);
+ return TRUE;
+}
+
+static gboolean
+gtk_cast_impossible (GValue *to,
+ const GValue *from)
+{
+ g_value_unset (to);
+ return FALSE;
+}
+
+static GtkCastFunc
+gtk_cast_expression_find_cast_func (GType from,
+ GType to)
+{
+ if (g_value_type_compatible (from, to))
+ return gtk_cast_copy;
+ else if (g_type_is_a (from, G_TYPE_OBJECT) && g_type_is_a (to, G_TYPE_OBJECT))
+ return gtk_cast_upcast;
+ else if (g_value_type_transformable (from, to))
+ return gtk_cast_transform;
+ else
+ return gtk_cast_impossible;
+}
+
+static void
+gtk_cast_expression_finalize (GtkExpression *expr)
+{
+ GtkCastExpression *self = (GtkCastExpression *) expr;
+
+ gtk_expression_unref (self->expr);
+}
+
+static void
+gtk_cast_expression_print (GtkExpression *expr,
+ GString *string)
+{
+ GtkCastExpression *self = (GtkCastExpression *) expr;
+
+ gtk_expression_print (self->expr, string);
+ g_string_append (string, ":");
+ g_string_append (string, g_type_name (gtk_expression_get_value_type (expr)));
+}
+
+static void
+gtk_cast_expression_notify (GtkExpression *expr,
+ GtkExpression *source)
+{
+ gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_cast_expression_evaluate (GtkExpression *expr,
+ GValue *value)
+{
+ GtkCastExpression *self = (GtkCastExpression *) expr;
+ GValue expr_value = G_VALUE_INIT;
+ gboolean result;
+
+ if (!gtk_expression_evaluate (self->expr, &expr_value))
+ return FALSE;
+
+ g_value_init (value, gtk_expression_get_value_type (expr));
+ result = self->cast_func (value, &expr_value);
+ g_value_unset (&expr_value);
+
+ return result;
+}
+
+static const GtkExpressionClass GTK_CAST_EXPRESSION_CLASS =
+{
+ sizeof (GtkCastExpression),
+ "GtkCastExpression",
+ gtk_cast_expression_finalize,
+ gtk_cast_expression_print,
+ gtk_cast_expression_notify,
+ gtk_cast_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_cast (GtkExpression *expr,
+ GType type)
+{
+ GtkCastExpression *result;
+
+ result = gtk_expression_alloc (>K_CAST_EXPRESSION_CLASS, type);
+
+ gtk_expression_set_owner (expr, (GtkExpression *) result);
+ result->expr = expr;
+ result->cast_func = gtk_cast_expression_find_cast_func (gtk_expression_get_value_type (expr),
+ type);
+
+ return (GtkExpression *) result;
+}
+
+/*** ASSIGN ***/
+
+typedef struct _GtkAssignExpression GtkAssignExpression;
+
+struct _GtkAssignExpression
+{
+ GtkExpression parent;
+
+ GObject *object;
+ char *property;
+ GtkExpression *expr;
+};
+
+static void
+gtk_assign_expression_weak_cb (gpointer expr,
+ GObject *unused)
+{
+ GtkObjectExpression *self = (GtkObjectExpression *) expr;
+
+ self->object = NULL;
+ gtk_expression_unref (expr);
+}
+
+static void
+gtk_assign_expression_finalize (GtkExpression *expr)
+{
+ GtkAssignExpression *self = (GtkAssignExpression *) expr;
+
+ g_assert (self->object == NULL);
+ gtk_expression_unref (self->expr);
+ g_free (self->property);
+}
+
+static void
+gtk_assign_expression_print (GtkExpression *expr,
+ GString *string)
+{
+ GtkAssignExpression *self = (GtkAssignExpression *) expr;
+
+ print_object (self->object, string);
+ g_string_append (string, ".");
+ g_string_append (string, self->property);
+ g_string_append (string, " = ");
+ gtk_expression_print (self->expr, string);
+}
+
+static void
+gtk_assign_expression_notify (GtkExpression *expr,
+ GtkExpression *source)
+{
+ GtkAssignExpression *self = (GtkAssignExpression *) expr;
+ GValue value = G_VALUE_INIT;
+
+ if (self->object)
+ {
+ if (gtk_expression_evaluate (self->expr, &value))
+ {
+ g_object_set_property (self->object, self->property, &value);
+ g_value_unset (&value);
+ }
+ else
+ {
+ /* XXX: init default value here or just not do anything? */
+ /* g_warn_if_reached (); */
+ }
+ }
+
+ gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_assign_expression_evaluate (GtkExpression *expr,
+ GValue *value)
+{
+ GtkAssignExpression *self = (GtkAssignExpression *) expr;
+
+ if (!gtk_expression_evaluate (self->expr, value))
+ return FALSE;
+
+ g_object_set_property (self->object, self->property, value);
+ return TRUE;
+}
+
+static const GtkExpressionClass GTK_ASSIGN_EXPRESSION_CLASS =
+{
+ sizeof (GtkAssignExpression),
+ "GtkAssignExpression",
+ gtk_assign_expression_finalize,
+ gtk_assign_expression_print,
+ gtk_assign_expression_notify,
+ gtk_assign_expression_evaluate,
+};
+
+GtkExpression *
+gtk_expression_new_assign (GObject *object,
+ const char *property,
+ GtkExpression *expr)
+{
+ GtkAssignExpression *result;
+
+ result = gtk_expression_alloc (>K_ASSIGN_EXPRESSION_CLASS,
+ gtk_expression_get_value_type (expr));
+
+ gtk_expression_set_owner (expr, (GtkExpression *) result);
+ result->object = object;
+ result->expr = expr;
+ result->property = g_strdup (property);
+ if (result->object)
+ {
+ g_object_weak_ref (result->object, gtk_assign_expression_weak_cb, result);
+ gtk_expression_ref ((GtkExpression *) result);
+ }
+
+ return (GtkExpression *) result;
+}
+
+/*** NEGATE ***/
+
+typedef struct _GtkNegateExpression GtkNegateExpression;
+
+struct _GtkNegateExpression
+{
+ GtkExpression parent;
+
+ GtkExpression *expr;
+};
+
+static void
+gtk_negate_expression_finalize (GtkExpression *expr)
+{
+ GtkNegateExpression *self = (GtkNegateExpression *) expr;
+
+ gtk_expression_unref (self->expr);
+}
+
+static void
+gtk_negate_expression_print (GtkExpression *expr,
+ GString *string)
+{
+ GtkNegateExpression *self = (GtkNegateExpression *) expr;
+
+ g_string_append (string, "!");
+ gtk_expression_print (self->expr, string);
+}
+
+static void
+gtk_negate_expression_notify (GtkExpression *expr,
+ GtkExpression *source)
+{
+ gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_negate_expression_evaluate (GtkExpression *expr,
+ GValue *value)
+{
+ GtkNegateExpression *self = (GtkNegateExpression *) expr;
+ GValue expr_value = G_VALUE_INIT;
+
+ if (!gtk_expression_evaluate (self->expr, &expr_value))
+ return FALSE;
+
+ g_value_init (value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (value, !gtk_expression_value_to_boolean (&expr_value));
+ g_value_unset (&expr_value);
+ return TRUE;
+}
+
+static const GtkExpressionClass GTK_NEGATE_EXPRESSION_CLASS =
+{
+ sizeof (GtkNegateExpression),
+ "GtkNegateExpression",
+ gtk_negate_expression_finalize,
+ gtk_negate_expression_print,
+ gtk_negate_expression_notify,
+ gtk_negate_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_negate (GtkExpression *expr)
+{
+ GtkNegateExpression *result;
+
+ result = gtk_expression_alloc (>K_NEGATE_EXPRESSION_CLASS,
+ G_TYPE_BOOLEAN);
+
+ gtk_expression_set_owner (expr, (GtkExpression *) result);
+ result->expr = expr;
+
+ return (GtkExpression *) result;
+}
+
+/*** SUM ***/
+
+typedef struct _GtkSumExpression GtkSumExpression;
+
+struct _GtkSumExpression
+{
+ GtkExpression parent;
+
+ GtkExpression *left;
+ GtkExpression *right;
+};
+
+static void
+gtk_sum_expression_finalize (GtkExpression *expr)
+{
+ GtkSumExpression *self = (GtkSumExpression *) expr;
+
+ gtk_expression_unref (self->left);
+ gtk_expression_unref (self->right);
+}
+
+static void
+gtk_sum_expression_print (GtkExpression *expr,
+ GString *string)
+{
+ GtkSumExpression *self = (GtkSumExpression *) expr;
+
+ gtk_expression_print (self->left, string);
+ g_string_append (string, " + ");
+ gtk_expression_print (self->right, string);
+}
+
+static void
+gtk_sum_expression_notify (GtkExpression *expr,
+ GtkExpression *source)
+{
+ gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_sum_expression_evaluate (GtkExpression *expr,
+ GValue *value)
+{
+ GtkSumExpression *self = (GtkSumExpression *) expr;
+ GValue lvalue = G_VALUE_INIT;
+ GValue rvalue = G_VALUE_INIT;
+ char *lstr, *rstr;
+
+ if (!gtk_expression_evaluate (self->left, &lvalue))
+ return FALSE;
+ if (!gtk_expression_evaluate (self->right, &rvalue))
+ {
+ g_value_unset (&lvalue);
+ return FALSE;
+ }
+
+ lstr = gtk_expression_value_to_string (&lvalue);
+ rstr = gtk_expression_value_to_string (&rvalue);
+ g_value_init (value, G_TYPE_STRING);
+ g_value_take_string (value, g_strconcat (lstr, rstr, NULL));
+ g_free (lstr);
+ g_free (rstr);
+
+ g_value_unset (&rvalue);
+ g_value_unset (&lvalue);
+ return TRUE;
+}
+
+static const GtkExpressionClass GTK_SUM_EXPRESSION_CLASS =
+{
+ sizeof (GtkSumExpression),
+ "GtkSumExpression",
+ gtk_sum_expression_finalize,
+ gtk_sum_expression_print,
+ gtk_sum_expression_notify,
+ gtk_sum_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_sum (GtkExpression *left,
+ GtkExpression *right)
+{
+ GtkSumExpression *result;
+
+ result = gtk_expression_alloc (>K_SUM_EXPRESSION_CLASS,
+ G_TYPE_STRING);
+
+ gtk_expression_set_owner (left, (GtkExpression *) result);
+ gtk_expression_set_owner (right, (GtkExpression *) result);
+ result->left = left;
+ result->right = right;
+
+ return (GtkExpression *) result;
+}
+
+/*** DEBUG ***/
+
+typedef struct _GtkDebugExpression GtkDebugExpression;
+
+struct _GtkDebugExpression
+{
+ GtkExpression parent;
+
+ GtkExpression *expr;
+};
+
+static void
+gtk_debug_expression_finalize (GtkExpression *expr)
+{
+ GtkDebugExpression *self = (GtkDebugExpression *) expr;
+
+ gtk_expression_unref (self->expr);
+}
+
+static void
+gtk_debug_expression_print (GtkExpression *expr,
+ GString *string)
+{
+ GtkDebugExpression *self = (GtkDebugExpression *) expr;
+
+ g_string_append (string, "debug(");
+ gtk_expression_print (self->expr, string);
+ g_string_append (string, ")");
+}
+
+static void
+gtk_debug_expression_notify (GtkExpression *expr,
+ GtkExpression *source)
+{
+ gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_debug_expression_evaluate (GtkExpression *expr,
+ GValue *value)
+{
+ GtkDebugExpression *self = (GtkDebugExpression *) expr;
+ char *message, *expr_string;
+ gboolean result;
+
+ result = gtk_expression_evaluate (self->expr, value);
+
+ expr_string = gtk_expression_to_string (self->expr);
+ if (result)
+ message = gtk_expression_value_to_string (value);
+ else
+ message = g_strdup ("%s: **** failed to evaluate ***");
+
+ g_printerr ("%s: %s\n", expr_string, message);
+ g_free (message);
+ g_free (expr_string);
+
+ return result;
+}
+
+static const GtkExpressionClass GTK_DEBUG_EXPRESSION_CLASS =
+{
+ sizeof (GtkDebugExpression),
+ "GtkDebugExpression",
+ gtk_debug_expression_finalize,
+ gtk_debug_expression_print,
+ gtk_debug_expression_notify,
+ gtk_debug_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_debug (GtkExpression *expr)
+{
+ GtkDebugExpression *result;
+
+ result = gtk_expression_alloc (>K_DEBUG_EXPRESSION_CLASS,
+ gtk_expression_get_value_type (expr));
+
+ gtk_expression_set_owner (expr, (GtkExpression *) result);
+ result->expr = expr;
+
+ return (GtkExpression *) result;
+}
+
+/*** FILE INFO ***/
+
+typedef struct _GtkFileInfoExpression GtkFileInfoExpression;
+
+struct _GtkFileInfoExpression
+{
+ GtkExpression parent;
+
+ GtkExpression *expr;
+ char *attribute;
+ GFileAttributeType type;
+};
+
+const char *file_attribute_type_names[] = {
+ [G_FILE_ATTRIBUTE_TYPE_STRING] = "string",
+ [G_FILE_ATTRIBUTE_TYPE_BOOLEAN] = "boolean",
+ [G_FILE_ATTRIBUTE_TYPE_UINT32] = "uint32",
+ [G_FILE_ATTRIBUTE_TYPE_INT32] = "int32",
+ [G_FILE_ATTRIBUTE_TYPE_UINT64] = "uint64",
+ [G_FILE_ATTRIBUTE_TYPE_INT64] = "int64",
+ [G_FILE_ATTRIBUTE_TYPE_OBJECT] = "object",
+};
+
+static GType
+gtk_file_attribute_type_get_gtype (GFileAttributeType type)
+{
+ switch (type)
+ {
+ case G_FILE_ATTRIBUTE_TYPE_STRING:
+ return G_TYPE_STRING;
+ case G_FILE_ATTRIBUTE_TYPE_BOOLEAN:
+ return G_TYPE_BOOLEAN;
+ case G_FILE_ATTRIBUTE_TYPE_UINT32:
+ return G_TYPE_UINT;
+ case G_FILE_ATTRIBUTE_TYPE_INT32:
+ return G_TYPE_INT;
+ case G_FILE_ATTRIBUTE_TYPE_UINT64:
+ return G_TYPE_UINT64;
+ case G_FILE_ATTRIBUTE_TYPE_INT64:
+ return G_TYPE_INT64;
+ case G_FILE_ATTRIBUTE_TYPE_OBJECT:
+ return G_TYPE_OBJECT;
+ case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING:
+ case G_FILE_ATTRIBUTE_TYPE_STRINGV:
+ case G_FILE_ATTRIBUTE_TYPE_INVALID:
+ default:
+ /* not supported */
+ g_return_val_if_reached (G_TYPE_INVALID);
+ }
+}
+
+static void
+gtk_file_info_expression_finalize (GtkExpression *expr)
+{
+ GtkFileInfoExpression *self = (GtkFileInfoExpression *) expr;
+
+ gtk_expression_unref (self->expr);
+ g_free (self->attribute);
+}
+
+static void
+gtk_file_info_expression_print (GtkExpression *expr,
+ GString *string)
+{
+ GtkFileInfoExpression *self = (GtkFileInfoExpression *) expr;
+
+ g_string_append (string, "file-info(");
+ gtk_expression_print (self->expr, string);
+ g_string_append_printf (string, ", %s, %s)",
+ self->attribute,
+ file_attribute_type_names [self->type]);
+}
+
+static void
+gtk_file_info_expression_notify (GtkExpression *expr,
+ GtkExpression *source)
+{
+ gtk_expression_notify (expr);
+}
+
+static gboolean
+gtk_file_info_expression_evaluate (GtkExpression *expr,
+ GValue *value)
+{
+ GtkFileInfoExpression *self = (GtkFileInfoExpression *) expr;
+ GValue expr_value = G_VALUE_INIT;
+ GFileInfo *info;
+
+ if (!gtk_expression_evaluate (self->expr, &expr_value))
+ return FALSE;
+
+ info = g_value_get_object (&expr_value);
+ if (info == NULL)
+ return FALSE;
+ if (g_file_info_get_attribute_type (info, self->attribute) != self->type)
+ return FALSE;
+
+ switch (self->type)
+ {
+ case G_FILE_ATTRIBUTE_TYPE_STRING:
+ g_value_init (value, G_TYPE_STRING);
+ g_value_set_string (value, g_file_info_get_attribute_string (info, self->attribute));
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_BOOLEAN:
+ g_value_init (value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (value, g_file_info_get_attribute_boolean (info, self->attribute));
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_UINT32:
+ g_value_init (value, G_TYPE_UINT);
+ g_value_set_uint (value, g_file_info_get_attribute_uint32 (info, self->attribute));
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_INT32:
+ g_value_init (value, G_TYPE_INT);
+ g_value_set_int (value, g_file_info_get_attribute_int32 (info, self->attribute));
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_UINT64:
+ g_value_init (value, G_TYPE_UINT64);
+ g_value_set_uint64 (value, g_file_info_get_attribute_uint64 (info, self->attribute));
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_INT64:
+ g_value_init (value, G_TYPE_INT64);
+ g_value_set_int64 (value, g_file_info_get_attribute_int64 (info, self->attribute));
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_OBJECT:
+ g_value_init (value, G_TYPE_OBJECT);
+ g_value_set_object (value, g_file_info_get_attribute_object (info, self->attribute));
+ break;
+ case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING:
+ case G_FILE_ATTRIBUTE_TYPE_STRINGV:
+ case G_FILE_ATTRIBUTE_TYPE_INVALID:
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_value_unset (&expr_value);
+ return TRUE;
+}
+
+static const GtkExpressionClass GTK_FILE_INFO_EXPRESSION_CLASS =
+{
+ sizeof (GtkFileInfoExpression),
+ "GtkFileInfoExpression",
+ gtk_file_info_expression_finalize,
+ gtk_file_info_expression_print,
+ gtk_file_info_expression_notify,
+ gtk_file_info_expression_evaluate,
+};
+
+static GtkExpression *
+gtk_expression_new_file_info (GtkExpression *expr,
+ char *attribute,
+ GFileAttributeType type)
+{
+ GtkFileInfoExpression *result;
+
+ g_return_val_if_fail (g_type_is_a (gtk_expression_get_value_type (expr), G_TYPE_FILE_INFO), NULL);
+
+ result = gtk_expression_alloc (>K_FILE_INFO_EXPRESSION_CLASS,
+ gtk_file_attribute_type_get_gtype (type));
+
+ gtk_expression_set_owner (expr, (GtkExpression *) result);
+ result->expr = expr;
+ result->attribute = g_strdup (attribute);
+ result->type = type;
+
+ return (GtkExpression *) result;
+}
+
+/*** PUBLIC API ***/
+
+static void
+gtk_expression_finalize (GtkExpression *self)
+{
+ self->expression_class->finalize (self);
+}
+
+/**
+ * gtk_expression_ref:
+ * @self: (allow-none): a #GtkExpression
+ *
+ * Acquires a reference on the given #GtkExpression.
+ *
+ * Returns: (transfer none): the #GtkExpression with an additional reference
+ */
+GtkExpression *
+gtk_expression_ref (GtkExpression *self)
+{
+ return g_atomic_rc_box_acquire (self);
+}
+
+/**
+ * gtk_expression_unref:
+ * @self: (allow-none): a #GtkExpression
+ *
+ * Releases a reference on the given #GtkExpression.
+ *
+ * If the reference was the last, the resources associated to the @self are
+ * freed.
+ */
+void
+gtk_expression_unref (GtkExpression *self)
+{
+ g_atomic_rc_box_release_full (self, (GDestroyNotify) gtk_expression_finalize);
+}
+
+/**
+ * gtk_expression_print:
+ * @self: a #GtkExpression
+ * @string: The string to print into
+ *
+ * Converts @self into a human-readable string representation suitable
+ * for printing that can be parsed with gtk_expression_parse().
+ **/
+void
+gtk_expression_print (GtkExpression *self,
+ GString *string)
+{
+ g_return_if_fail (GTK_IS_EXPRESSION (self));
+ g_return_if_fail (string != NULL);
+
+ self->expression_class->print (self, string);
+}
+
+/**
+ * gtk_expression_to_string:
+ * @self: a #GtkExpression
+ *
+ * Converts an expression into a string that is suitable for
+ * printing and can later be parsed with gtk_expression_parse().
+ *
+ * This is a wrapper around gtk_expression_print(), see that function
+ * for details.
+ *
+ * Returns: A new string for @self
+ **/
+char *
+gtk_expression_to_string (GtkExpression *self)
+{
+ GString *string;
+
+ string = g_string_new ("");
+
+ gtk_expression_print (self, string);
+
+ return g_string_free (string, FALSE);
+}
+
+/**
+ * gtk_expression_get_value_type:
+ * @self: a #GtkExpression
+ *
+ * Gets the #GType that this expression evaluates to. This type
+ * is constant and will not change over the lifetime of this expression.
+ *
+ * Returns: The type returned from gtk_expression_evaluate()
+ **/
+GType
+gtk_expression_get_value_type (GtkExpression *self)
+{
+ g_return_val_if_fail (GTK_IS_EXPRESSION (self), G_TYPE_INVALID);
+
+ return self->value_type;
+}
+
+/**
+ * gtk_expression_evaluate:
+ * @self: a #GtkExpression
+ * @value: an empty #GValue
+ *
+ * Evaluates the given expression and on success stores the result
+ * in @value. The #GType of @value will be the type given by
+ * gtk_expression_get_value_type().
+ *
+ * It is possible that expressions cannot be evaluated - for example
+ * when the expression references objects that have been destroyed or
+ * set to %NULL. In that case @value will remain empty and %FALSE
+ * will be returned.
+ *
+ * Returns: %TRUE if the expression could be evaluated
+ **/
+gboolean
+gtk_expression_evaluate (GtkExpression *self,
+ GValue *value)
+{
+ g_return_val_if_fail (GTK_IS_EXPRESSION (self), FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ return self->expression_class->evaluate (self, value);
+}
+
+/* forward declaration */
+static GtkExpression *
+gtk_expression_parser_parse (GtkBuilder *scope,
+ GtkCssParser *parser);
+
+typedef struct {
+ GtkBuilder *scope;
+
+ GtkExpression *expr;
+ char *attribute;
+ GFileAttributeType type;
+} FileInfoData;
+
+static guint
+gtk_expression_parser_parse_file_info (GtkCssParser *parser,
+ guint n,
+ gpointer _data)
+{
+ FileInfoData *data = _data;
+ guint i;
+
+ switch (n)
+ {
+ case 0:
+ data->expr = gtk_expression_parser_parse (data->scope, parser);
+ if (data->expr == NULL)
+ return 0;
+ if (!g_type_is_a (gtk_expression_get_value_type (data->expr), G_TYPE_FILE_INFO))
+ {
+ gtk_css_parser_error_value (parser, "Expression does not evaluate a GFileInfo");
+ return 0;
+ }
+ return 1;
+
+ case 1:
+ data->attribute = gtk_css_parser_consume_string (parser);
+ if (data->attribute == NULL)
+ return 0;
+ return 1;
+
+ case 2:
+ for (i = 0; i < G_N_ELEMENTS (file_attribute_type_names); i++)
+ {
+ if (file_attribute_type_names[i] == NULL)
+ continue;
+
+ if (gtk_css_parser_try_ident (parser, file_attribute_type_names[i]))
+ {
+ data->type = i;
+ return 1;
+ }
+ }
+ return 0;
+
+ default:
+ g_assert_not_reached ();
+ return 0;
+ }
+}
+
+typedef struct {
+ GtkBuilder *scope;
+ GtkExpression *expr;
+} DebugData;
+
+static guint
+gtk_expression_parser_parse_debug (GtkCssParser *parser,
+ guint n,
+ gpointer _data)
+{
+ DebugData *data = _data;
+
+ data->expr = gtk_expression_parser_parse (data->scope, parser);
+ if (data->expr == NULL)
+ return 0;
+
+ return 1;
+}
+
+static GtkExpression *
+gtk_expression_parser_parse_primary (GtkBuilder *scope,
+ GtkCssParser *parser)
+{
+ if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_INTEGER))
+ {
+ GtkLiteralExpression *literal = gtk_expression_alloc (>K_LITERAL_EXPRESSION_CLASS, G_TYPE_INT);
+ int i;
+
+ g_value_init (&literal->value, G_TYPE_INT);
+ gtk_css_parser_consume_integer (parser, &i);
+ g_value_set_int (&literal->value, i);
+
+ return (GtkExpression *) literal;
+ }
+ else if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
+ {
+ GtkLiteralExpression *literal = gtk_expression_alloc (>K_LITERAL_EXPRESSION_CLASS, G_TYPE_UINT);
+ int i;
+
+ g_value_init (&literal->value, G_TYPE_UINT);
+ gtk_css_parser_consume_integer (parser, &i);
+ g_value_set_uint (&literal->value, i);
+
+ return (GtkExpression *) literal;
+ }
+ else if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_NUMBER) ||
+ gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_NUMBER))
+ {
+ GtkLiteralExpression *literal = gtk_expression_alloc (>K_LITERAL_EXPRESSION_CLASS, G_TYPE_DOUBLE);
+ double d;
+
+ g_value_init (&literal->value, G_TYPE_DOUBLE);
+ gtk_css_parser_consume_number (parser, &d);
+ g_value_set_double (&literal->value, d);
+
+ return (GtkExpression *) literal;
+ }
+ else if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_STRING))
+ {
+ GtkLiteralExpression *literal = gtk_expression_alloc (>K_LITERAL_EXPRESSION_CLASS, G_TYPE_STRING);
+
+ g_value_init (&literal->value, G_TYPE_STRING);
+ g_value_take_string (&literal->value, gtk_css_parser_consume_string (parser));
+
+ return (GtkExpression *) literal;
+ }
+ else if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_IDENT))
+ {
+ char *ident = gtk_css_parser_consume_ident (parser);
+ GObject *object = gtk_builder_get_object (scope, ident);
+
+ if (object == NULL)
+ {
+ gtk_css_parser_error_value (parser, "No variable named \"%s\"", ident);
+ g_free (ident);
+ return NULL;
+ }
+
+ g_free (ident);
+ return gtk_object_expression_new (object);
+ }
+ else if (gtk_css_parser_has_function (parser, "file-info"))
+ {
+ FileInfoData data = { scope, };
+
+ if (!gtk_css_parser_consume_function (parser, 3, 3, gtk_expression_parser_parse_file_info, &data))
+ {
+ g_clear_pointer (&data.expr, gtk_expression_unref);
+ g_free (data.attribute);
+ return FALSE;
+ }
+
+ return gtk_expression_new_file_info (data.expr, data.attribute, data.type);
+ }
+ else if (gtk_css_parser_has_function (parser, "debug"))
+ {
+ DebugData data = { scope, };
+
+ if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_expression_parser_parse_debug, &data))
+ return FALSE;
+
+ return gtk_expression_new_debug (data.expr);
+ }
+ else
+ {
+ gtk_css_parser_error_syntax (parser, "Unexpected syntax");
+ return NULL;
+ }
+}
+
+static GtkExpression *
+gtk_expression_parser_parse_postfix (GtkBuilder *scope,
+ GtkCssParser *parser)
+{
+ const GtkCssToken *token;
+ GtkExpression *expr;
+
+ expr = gtk_expression_parser_parse_primary (scope, parser);
+ if (expr == NULL)
+ return NULL;
+
+ while (TRUE)
+ {
+ token = gtk_css_parser_peek_token (parser);
+ if (gtk_css_token_is_delim (token, '.'))
+ {
+ GType type = gtk_expression_get_value_type (expr);
+ GParamSpec *pspec;
+
+ gtk_css_parser_consume_token (parser);
+ token = gtk_css_parser_peek_token (parser);
+ if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+ {
+ gtk_css_parser_error_syntax (parser, "Expected field member after '.'");
+ gtk_expression_unref (expr);
+ return NULL;
+ }
+ if (g_type_is_a (type, G_TYPE_OBJECT))
+ {
+ pspec = g_object_class_find_property (g_type_class_peek (type), token->string.string);
+ }
+ else if (g_type_is_a (type, G_TYPE_INTERFACE))
+ {
+ pspec = g_object_interface_find_property (g_type_default_interface_peek (type),
+ token->string.string);
+ }
+ else
+ {
+ gtk_css_parser_error_value (parser, "Values of type \"%s\" cannot have members",
+ g_type_name (type));
+ gtk_expression_unref (expr);
+ return NULL;
+ }
+ if (pspec == NULL)
+ {
+ gtk_css_parser_error_value (parser, "\"%s\" has no property named \"%s\"",
+ g_type_name (type), token->string.string);
+ gtk_expression_unref (expr);
+ return NULL;
+ }
+
+ expr = gtk_expression_new_property (expr, pspec);
+ gtk_css_parser_consume_token (parser);
+ }
+ else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COLON))
+ {
+ GType type;
+
+ gtk_css_parser_consume_token (parser);
+ token = gtk_css_parser_peek_token (parser);
+ if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
+ {
+ gtk_css_parser_error_syntax (parser, "Expected type name after ':'");
+ gtk_expression_unref (expr);
+ return NULL;
+ }
+ type = gtk_builder_get_type_from_name (scope, token->string.string);
+ if (type == G_TYPE_INVALID)
+ {
+ gtk_css_parser_error_value (parser, "Cannot cast to unknown type \"%s\"",
token->string.string);
+ gtk_expression_unref (expr);
+ return NULL;
+ }
+ expr = gtk_expression_new_cast (expr, type);
+ gtk_css_parser_consume_token (parser);
+ }
+ else
+ {
+ break;
+ }
+ }
+ return expr;
+}
+
+static GtkExpression *
+gtk_expression_parser_parse_unary (GtkBuilder *scope,
+ GtkCssParser *parser)
+{
+ if (gtk_css_parser_try_delim (parser, '!'))
+ {
+ GtkExpression *expr;
+
+ expr = gtk_expression_parser_parse_postfix (scope, parser);
+ if (expr == NULL)
+ return NULL;
+
+ return gtk_expression_new_negate (expr);
+ }
+ else
+ {
+ return gtk_expression_parser_parse_postfix (scope, parser);
+ }
+}
+
+static GtkExpression *
+gtk_expression_parser_parse_additive (GtkBuilder *scope,
+ GtkCssParser *parser)
+{
+ GtkExpression *expr;
+
+ expr = gtk_expression_parser_parse_unary (scope, parser);
+ if (expr == NULL)
+ return NULL;
+
+ while (TRUE)
+ {
+ if (gtk_css_parser_try_delim (parser, '+'))
+ {
+ GtkExpression *right;
+
+ right = gtk_expression_parser_parse_unary (scope, parser);
+ if (right == NULL)
+ {
+ gtk_expression_unref (expr);
+ return NULL;
+ }
+ expr = gtk_expression_new_sum (expr, right);
+ }
+ else
+ {
+ return expr;
+ }
+ }
+}
+
+static GtkExpression *
+gtk_expression_parser_parse (GtkBuilder *scope,
+ GtkCssParser *parser)
+{
+ return gtk_expression_parser_parse_additive (scope, parser);
+}
+
+static void
+gtk_expression_builder_error_forward (GtkCssParser *parser,
+ const GtkCssLocation *start,
+ const GtkCssLocation *end,
+ const GError *error,
+ gpointer user_data)
+{
+ GError **forward_error = (GError **) user_data;
+
+ if (forward_error && *forward_error == NULL)
+ *forward_error = g_error_copy (error);
+}
+
+/**
+ * gtk_expression_parse:
+ * @scope: a #GtkBuilder object to lookup variables in
+ * @string: the string to parse
+ * @error: Return location to store error or %NULL to ignore
+ *
+ * Parses the given @string into an expression and returns it.
+ * Strings printed via gtk_expression_to_string()
+ * can be read in again successfully using this function.
+ *
+ * If @string does not describe a valid expression, %NULL is
+ * returned.
+ *
+ * Returns: A new expression
+ **/
+GtkExpression *
+gtk_expression_parse (GtkBuilder *scope,
+ const char *string,
+ GError **error)
+{
+ GtkCssParser *parser;
+ GBytes *bytes;
+ GtkExpression *result;
+
+ g_return_val_if_fail (GTK_IS_BUILDER (scope), NULL);
+ g_return_val_if_fail (string != NULL, NULL);
+
+ bytes = g_bytes_new_static (string, strlen (string));
+ parser = gtk_css_parser_new_for_bytes (bytes,
+ NULL,
+ NULL,
+ gtk_expression_builder_error_forward,
+ error,
+ NULL);
+
+ result = gtk_expression_parser_parse (scope, parser);
+ if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
+ {
+ gtk_css_parser_error_syntax (parser, "Unexpected junk at end of value");
+ g_clear_pointer (&result, gtk_expression_unref);
+ }
+
+ gtk_css_parser_unref (parser);
+ g_bytes_unref (bytes);
+
+ return result;
+}
+
+/**
+ * gtk_expression_value_to_string:
+ * @value: a #GValue
+ *
+ * Converts a given #GValue to its string representation.
+ * This operation never fails, but the returned strings may
+ * not be useful.
+ *
+ * Returns: a new string, free with g_free().
+ **/
+char *
+gtk_expression_value_to_string (const GValue *value)
+{
+ g_return_val_if_fail (G_IS_VALUE (value), NULL);
+
+ switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value)))
+ {
+ case G_TYPE_INVALID:
+ return g_strdup ("[invalid]");
+ case G_TYPE_NONE:
+ return g_strdup ("[none]");
+ case G_TYPE_INTERFACE:
+ case G_TYPE_CHAR:
+ case G_TYPE_UCHAR:
+ g_return_val_if_reached (g_strdup ("FIXME"));
+ case G_TYPE_BOOLEAN:
+ return g_strdup (g_value_get_boolean (value) ? "true" : "false");
+ case G_TYPE_INT:
+ return g_strdup_printf ("%d", g_value_get_int (value));
+ case G_TYPE_UINT:
+ return g_strdup_printf ("%u", g_value_get_uint (value));
+ case G_TYPE_LONG:
+ return g_strdup_printf ("%ld", g_value_get_long (value));
+ case G_TYPE_ULONG:
+ return g_strdup_printf ("%lu", g_value_get_ulong (value));
+ case G_TYPE_INT64:
+ return g_strdup_printf ("%" G_GINT64_FORMAT , g_value_get_int64 (value));
+ case G_TYPE_UINT64:
+ return g_strdup_printf ("%" G_GUINT64_FORMAT , g_value_get_uint64 (value));
+ case G_TYPE_ENUM:
+ case G_TYPE_FLAGS:
+ g_return_val_if_reached (g_strdup ("FIXME"));
+ case G_TYPE_FLOAT:
+ return g_ascii_dtostr (g_malloc (G_ASCII_DTOSTR_BUF_SIZE),
+ G_ASCII_DTOSTR_BUF_SIZE,
+ g_value_get_float (value));
+ case G_TYPE_DOUBLE:
+ return g_ascii_dtostr (g_malloc (G_ASCII_DTOSTR_BUF_SIZE),
+ G_ASCII_DTOSTR_BUF_SIZE,
+ g_value_get_double (value));
+ case G_TYPE_STRING:
+ return g_value_dup_string (value);
+ case G_TYPE_POINTER:
+ return g_strdup_printf ("[0x%p]", g_value_get_pointer (value));
+ case G_TYPE_BOXED:
+ return g_strdup_printf ("[%s|%p]", G_VALUE_TYPE_NAME (value), g_value_get_boxed (value));
+ case G_TYPE_PARAM:
+ return g_strdup_printf ("[%s|%p]", G_VALUE_TYPE_NAME (value), g_value_get_param (value));
+ case G_TYPE_OBJECT:
+ return g_strdup_printf ("[%s|%p]", G_VALUE_TYPE_NAME (value), g_value_get_object (value));
+ case G_TYPE_VARIANT:
+ return g_variant_print (g_value_get_variant (value), TRUE);
+ default:
+ return g_strconcat ("[", g_type_name (G_VALUE_TYPE (value)), "]", NULL);
+ }
+}
+
+/**
+ * gtk_expression_value_to_boolean:
+ * @value: a #GValue
+ *
+ * Converts a given #GValue to its boolean value.
+ * Every value has a boolean representation.
+ *
+ * Number types are %TRUE when their value is different
+ * from 0, pointer types are %TRUE when their value
+ * is different from %NULL and unknown and invalid
+ * types are always %FALSE.
+ *
+ * In particular, this means that the empty string "" is %TRUE.
+ *
+ * Returns: the boolean representation of @value.
+ **/
+gboolean
+gtk_expression_value_to_boolean (const GValue *value)
+{
+ g_return_val_if_fail (G_IS_VALUE (value), FALSE);
+
+ switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value)))
+ {
+ case G_TYPE_INVALID:
+ case G_TYPE_NONE:
+ return FALSE;
+ case G_TYPE_INTERFACE:
+ g_return_val_if_reached (FALSE);
+ case G_TYPE_CHAR:
+ return g_value_get_char (value) != 0;
+ case G_TYPE_UCHAR:
+ return g_value_get_uchar (value) != 0;
+ case G_TYPE_BOOLEAN:
+ return g_value_get_boolean (value);
+ case G_TYPE_INT:
+ return g_value_get_int (value) != 0;
+ case G_TYPE_UINT:
+ return g_value_get_uint (value) != 0;
+ case G_TYPE_LONG:
+ return g_value_get_long (value) != 0;
+ case G_TYPE_ULONG:
+ return g_value_get_ulong (value) != 0;
+ case G_TYPE_INT64:
+ return g_value_get_int64 (value) != 0;
+ case G_TYPE_UINT64:
+ return g_value_get_uint64 (value) != 0;
+ case G_TYPE_ENUM:
+ return g_value_get_enum (value) != 0;
+ case G_TYPE_FLAGS:
+ return g_value_get_flags (value) != 0;
+ case G_TYPE_FLOAT:
+ return g_value_get_float (value) != 0;
+ case G_TYPE_DOUBLE:
+ return g_value_get_double (value) != 0;
+ case G_TYPE_STRING:
+ return g_value_get_string (value) != NULL;
+ case G_TYPE_POINTER:
+ return g_value_get_pointer (value) != NULL;
+ case G_TYPE_BOXED:
+ return g_value_get_boxed (value) != NULL;
+ case G_TYPE_PARAM:
+ return g_value_get_param (value) != NULL;
+ case G_TYPE_OBJECT:
+ return g_value_get_object (value) != NULL;
+ case G_TYPE_VARIANT:
+ return g_value_get_variant (value) != NULL;
+ default:
+ return FALSE;
+ }
+}
+
diff --git a/gtk/gtkexpressionprivate.h b/gtk/gtkexpressionprivate.h
new file mode 100644
index 0000000000..6af23807c5
--- /dev/null
+++ b/gtk/gtkexpressionprivate.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+
+#ifndef __GTK_EXPRESSION_PRIVATE_H__
+#define __GTK_EXPRESSION_PRIVATE_H__
+
+#include <gtk/gtktypes.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkExpression GtkExpression;
+
+#define GTK_IS_EXPRESSION(expr) ((expr) != NULL)
+
+GtkExpression * gtk_expression_new_assign (GObject *object,
+ const char *property,
+ GtkExpression *expr);
+
+GtkExpression * gtk_expression_ref (GtkExpression *self);
+void gtk_expression_unref (GtkExpression *self);
+
+void gtk_expression_print (GtkExpression *self,
+ GString *string);
+char * gtk_expression_to_string (GtkExpression *self);
+GtkExpression * gtk_expression_parse (GtkBuilder *scope,
+ const char *string,
+ GError **error);
+
+GType gtk_expression_get_value_type (GtkExpression *self);
+gboolean gtk_expression_evaluate (GtkExpression *self,
+ GValue *value);
+
+char * gtk_expression_value_to_string (const GValue *value);
+gboolean gtk_expression_value_to_boolean (const GValue *value);
+G_END_DECLS
+
+#endif /* __GTK_EXPRESSION_PRIVATE_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 5dadaaa5af..fac94d2748 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -230,6 +230,7 @@ gtk_public_sources = files([
'gtkeventcontrollermotion.c',
'gtkeventcontrollerscroll.c',
'gtkexpander.c',
+ 'gtkexpression.c',
'gtkfilechooser.c',
'gtkfilechooserbutton.c',
'gtkfilechooserdialog.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]