[gtk/wip/ebassi/constraint-layout] Add VFL parser for constraints
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/ebassi/constraint-layout] Add VFL parser for constraints
- Date: Sat, 29 Jun 2019 17:52:19 +0000 (UTC)
commit 1e725b66a5d1031f87f687c689724a8ab920a1b0
Author: Emmanuele Bassi <ebassi gnome org>
Date: Sat Jun 29 18:03:42 2019 +0100
Add VFL parser for constraints
Constraints can be expressed with a compact syntax, called VFL (visual
format language).
gtk/gtkconstraintvflparser.c | 1226 +++++++++++++++++++++++++++++++++++
gtk/gtkconstraintvflparserprivate.h | 89 +++
gtk/meson.build | 1 +
3 files changed, 1316 insertions(+)
---
diff --git a/gtk/gtkconstraintvflparser.c b/gtk/gtkconstraintvflparser.c
new file mode 100644
index 0000000000..0d46784991
--- /dev/null
+++ b/gtk/gtkconstraintvflparser.c
@@ -0,0 +1,1226 @@
+/* gtkconstraintvflparser.c: VFL constraint definition parser
+ *
+ * Copyright 2017 Endless
+ * Copyright 2019 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * 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/>.
+ */
+
+#include "config.h"
+
+#include "gtkconstraintvflparserprivate.h"
+
+#include <string.h>
+
+typedef enum {
+ VFL_HORIZONTAL,
+ VFL_VERTICAL
+} VflOrientation;
+
+typedef struct {
+ GtkConstraintRelation relation;
+
+ double constant;
+ double multiplier;
+ const char *subject;
+ char *object;
+ const char *attr;
+
+ double priority;
+} VflPredicate;
+
+typedef enum {
+ SPACING_SET = 1 << 0,
+ SPACING_DEFAULT = 1 << 1,
+ SPACING_PREDICATE = 1 << 2
+} VflSpacingFlags;
+
+typedef struct {
+ double size;
+ VflSpacingFlags flags;
+ VflPredicate predicate;
+} VflSpacing;
+
+typedef struct _VflView VflView;
+
+struct _VflView
+{
+ char *name;
+
+ /* Decides which attributes are admissible */
+ VflOrientation orientation;
+
+ /* A set of predicates, which will be used to
+ * set up constraints
+ */
+ GArray *predicates;
+
+ VflSpacing spacing;
+
+ VflView *prev_view;
+ VflView *next_view;
+};
+
+struct _GtkConstraintVflParser
+{
+ char *buffer;
+ gsize buffer_len;
+
+ int error_offset;
+ int error_range;
+
+ int default_spacing[2];
+
+ /* Set<name, double> */
+ GHashTable *metrics_set;
+ /* Set<name, widget> */
+ GHashTable *views_set;
+
+ const char *cursor;
+
+ /* Decides which attributes are admissible */
+ VflOrientation orientation;
+
+ VflView *leading_super;
+ VflView *trailing_super;
+
+ VflView *current_view;
+ VflView *views;
+};
+
+GQuark
+gtk_constraint_vfl_parser_error_quark (void)
+{
+ return g_quark_from_static_string ("gtk-constraint-vfl-parser-error-quark");
+}
+
+GtkConstraintVflParser *
+gtk_constraint_vfl_parser_new (void)
+{
+ GtkConstraintVflParser *res = g_new0 (GtkConstraintVflParser, 1);
+
+ res->default_spacing[VFL_HORIZONTAL] = 8;
+ res->default_spacing[VFL_VERTICAL] = 8;
+
+ res->orientation = VFL_HORIZONTAL;
+
+ return res;
+}
+
+void
+gtk_constraint_vfl_parser_set_default_spacing (GtkConstraintVflParser *parser,
+ int hspacing,
+ int vspacing)
+{
+ parser->default_spacing[VFL_HORIZONTAL] = hspacing < 0 ? 8 : hspacing;
+ parser->default_spacing[VFL_VERTICAL] = vspacing < 0 ? 8 : vspacing;
+}
+
+void
+gtk_constraint_vfl_parser_set_metrics (GtkConstraintVflParser *parser,
+ GHashTable *metrics)
+{
+ parser->metrics_set = metrics;
+}
+
+void
+gtk_constraint_vfl_parser_set_views (GtkConstraintVflParser *parser,
+ GHashTable *views)
+{
+ parser->views_set = views;
+}
+
+static int
+get_default_spacing (GtkConstraintVflParser *parser)
+{
+ return parser->default_spacing[parser->orientation];
+}
+
+/* Default attributes, if unnamed, depending on the orientation */
+static const char *default_attribute[2] = {
+ [VFL_HORIZONTAL] = "width",
+ [VFL_VERTICAL] = "height",
+};
+
+static gboolean
+parse_relation (const char *str,
+ GtkConstraintRelation *relation,
+ char **endptr,
+ GError **error)
+{
+ const char *cur = str;
+
+ if (*cur == '=')
+ {
+ cur += 1;
+
+ if (*cur == '=')
+ {
+ *relation = GTK_CONSTRAINT_RELATION_EQ;
+ *endptr = (char *) cur + 1;
+ return TRUE;
+ }
+
+ goto out;
+ }
+ else if (*cur == '>')
+ {
+ cur += 1;
+
+ if (*cur == '=')
+ {
+ *relation = GTK_CONSTRAINT_RELATION_GE;
+ *endptr = (char *) cur + 1;
+ return TRUE;
+ }
+
+ goto out;
+ }
+ else if (*cur == '<')
+ {
+ cur += 1;
+
+ if (*cur == '=')
+ {
+ *relation = GTK_CONSTRAINT_RELATION_LE;
+ *endptr = (char *) cur + 1;
+ return TRUE;
+ }
+
+ goto out;
+ }
+
+out:
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_RELATION,
+ "Unknown relation; must be one of '==', '>=', or '<='");
+ return FALSE;
+}
+
+static gboolean
+has_metric (GtkConstraintVflParser *parser,
+ const char *name)
+{
+ if (parser->metrics_set == NULL)
+ return FALSE;
+
+ return g_hash_table_contains (parser->metrics_set, name);
+}
+
+static gboolean
+has_view (GtkConstraintVflParser *parser,
+ const char *name)
+{
+ if (parser->views_set == NULL)
+ return FALSE;
+
+ if (!g_hash_table_contains (parser->views_set, name))
+ return FALSE;
+
+ return g_hash_table_lookup (parser->views_set, name) != NULL;
+}
+
+/* Valid attributes */
+static const struct {
+ int len;
+ const char *name;
+} valid_attributes[] = {
+ { 5, "width" },
+ { 6, "height" },
+ { 7, "centerX" },
+ { 7, "centerY" },
+ { 3, "top" },
+ { 6, "bottom" },
+ { 4, "left" },
+ { 5, "right" },
+ { 5, "start" },
+ { 3, "end" },
+ { 8, "baseline" }
+};
+
+static char *
+get_offset_to (const char *str,
+ const char *tokens)
+{
+ char *offset = NULL;
+ int n_tokens = strlen (tokens);
+
+ for (int i = 0; i < n_tokens; i++)
+ {
+ if ((offset = strchr (str, tokens[i])) != NULL)
+ break;
+ }
+
+ return offset;
+}
+
+static gboolean
+parse_predicate (GtkConstraintVflParser *parser,
+ const char *cursor,
+ VflPredicate *predicate,
+ char **endptr,
+ GError **error)
+{
+ VflOrientation orientation = parser->orientation;
+ const char *end = cursor;
+
+ predicate->object = NULL;
+ predicate->multiplier = 1.0;
+
+ /* <predicate> = (<relation>)? (<objectOfPredicate>) ('.'<attribute>)? (<operator>)?
('@'<priority>)?
+ * <relation> = '==' | '<=' | '>='
+ * <objectOfPredicate> = <constant> | <viewName>
+ * <constant> = <number> | <metricName>
+ * <viewName> = [A-Za-z_]([A-Za-z0-9_]*)
+ * <metricName> = [A-Za-z_]([A-Za-z0-9_]*)
+ * <operator> = (['*'|'/']<positiveNumber>)? (['+'|'-']<positiveNumber>)?
+ * <priority> = <positiveNumber> | 'weak' | 'medium' | 'strong' | 'required'
+ */
+
+ /* Parse relation */
+ if (*end == '=' || *end == '>' || *end == '<')
+ {
+ GtkConstraintRelation relation;
+ char *tmp;
+
+ if (!parse_relation (end, &relation, &tmp, error))
+ {
+ parser->error_offset = end - parser->cursor;
+ parser->error_range = 0;
+ return FALSE;
+ }
+
+ predicate->relation = relation;
+
+ end = tmp;
+ }
+ else
+ predicate->relation = GTK_CONSTRAINT_RELATION_EQ;
+
+ /* Parse object of predicate */
+ if (g_ascii_isdigit (*end))
+ {
+ char *tmp;
+
+ /* <constant> */
+ predicate->object = NULL;
+ predicate->attr = default_attribute[orientation];
+ predicate->constant = g_ascii_strtod (end, &tmp);
+
+ end = tmp;
+ }
+ else if (g_ascii_isalpha (*end) || *end == '_')
+ {
+ const char *name_start = end;
+
+ while (g_ascii_isalnum (*end) || *end == '_')
+ end += 1;
+
+ char *name = g_strndup (name_start, end - name_start);
+
+ /* We only accept view names if the subject of the predicate
+ * is a view, i.e. we do not allow view names inside a spacing
+ * predicate
+ */
+ if (predicate->subject == NULL)
+ {
+ if (parser->metrics_set == NULL || !has_metric (parser, name))
+ {
+ parser->error_offset = name_start - parser->cursor;
+ parser->error_range = end - name_start;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_METRIC,
+ "Unable to find metric with name '%s'", name);
+ g_free (name);
+ return FALSE;
+ }
+
+ double *val = g_hash_table_lookup (parser->metrics_set, name);
+
+ predicate->object = NULL;
+ predicate->attr = default_attribute[orientation];
+ predicate->constant = *val;
+
+ g_free (name);
+
+ goto parse_operators;
+ }
+
+ if (has_metric (parser, name))
+ {
+ double *val = g_hash_table_lookup (parser->metrics_set, name);
+
+ predicate->object = NULL;
+ predicate->attr = default_attribute[orientation];
+ predicate->constant = *val;
+
+ g_free (name);
+
+ goto parse_operators;
+ }
+
+ if (has_view (parser, name))
+ {
+ /* Transfer name's ownership to the predicate */
+ predicate->object = name;
+ predicate->attr = default_attribute[orientation];
+ predicate->constant = 0;
+
+ goto parse_attribute;
+ }
+
+ parser->error_offset = name_start - parser->cursor;
+ parser->error_range = end - name_start;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+ "Unable to find view with name '%s'", name);
+ g_free (name);
+ return FALSE;
+ }
+ else
+ {
+ parser->error_offset = end - parser->cursor;
+ parser->error_range = 0;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected constant, view name, or metric");
+ return FALSE;
+ }
+
+parse_attribute:
+ if (*end == '.')
+ {
+ end += 1;
+ predicate->attr = NULL;
+
+ for (int i = 0; i < G_N_ELEMENTS (valid_attributes); i++)
+ {
+ if (g_ascii_strncasecmp (valid_attributes[i].name, end, valid_attributes[i].len) == 0)
+ {
+ predicate->attr = valid_attributes[i].name;
+ end += valid_attributes[i].len;
+ }
+ }
+
+ if (predicate->attr == NULL)
+ {
+ char *range_end = get_offset_to (end, "*/+-@,)]");
+
+ if (range_end != NULL)
+ parser->error_range = range_end - end - 1;
+ else
+ parser->error_range = 0;
+
+ g_free (predicate->object);
+
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_ATTRIBUTE,
+ "Attribute must be on one of 'width', 'height', "
+ "'centerX', 'centerY', 'top', 'bottom', "
+ "'left', 'right', 'start', 'end', 'baseline'");
+ return FALSE;
+ }
+ }
+
+parse_operators:
+ /* Parse multiplier operator */
+ while (g_ascii_isspace (*end))
+ end += 1;
+
+ if ((*end == '*') || (*end == '/'))
+ {
+ double multiplier;
+ const char *operator;
+
+ operator = end;
+ end += 1;
+
+ while (g_ascii_isspace (*end))
+ end += 1;
+
+ if (g_ascii_isdigit (*end))
+ {
+ char *tmp;
+
+ multiplier = g_ascii_strtod (end, &tmp);
+ end = tmp;
+ }
+ else
+ {
+ g_free (predicate->object);
+
+ parser->error_offset = end - parser->cursor;
+ parser->error_range = 0;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected a positive number as a multiplier");
+ return FALSE;
+ }
+
+ if (predicate->object != NULL)
+ {
+ if (*operator == '*')
+ predicate->multiplier = multiplier;
+ else
+ predicate->multiplier = 1.0 / multiplier;
+ }
+ else
+ {
+ /* If the subject is a constant then apply multiplier directly */
+ if (*operator == '*')
+ predicate->constant *= multiplier;
+ else
+ predicate->constant *= 1.0 / multiplier;
+ }
+ }
+
+ /* Parse constant operator */
+ while (g_ascii_isspace (*end))
+ end += 1;
+
+ if ((*end == '+') || (*end == '-'))
+ {
+ double constant;
+ const char *operator;
+
+ operator = end;
+ end += 1;
+
+ while (g_ascii_isspace (*end))
+ end += 1;
+
+ if (g_ascii_isdigit (*end))
+ {
+ char *tmp;
+
+ constant = g_ascii_strtod (end, &tmp);
+ end = tmp;
+ }
+ else
+ {
+ g_free (predicate->object);
+
+ parser->error_offset = end - parser->cursor;
+ parser->error_range = 0;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected positive number as a constant");
+ return FALSE;
+ }
+
+ if (*operator == '+')
+ predicate->constant += constant;
+ else
+ predicate->constant += -1.0 * constant;
+ }
+
+ /* Parse priority */
+ if (*end == '@')
+ {
+ double priority;
+ end += 1;
+
+ if (g_ascii_isdigit (*end))
+ {
+ char *tmp;
+
+ priority = g_ascii_strtod (end, &tmp);
+ end = tmp;
+ }
+ else if (strncmp (end, "weak", 4) == 0)
+ {
+ priority = GTK_CONSTRAINT_STRENGTH_WEAK;
+ end += 4;
+ }
+ else if (strncmp (end, "medium", 6) == 0)
+ {
+ priority = GTK_CONSTRAINT_STRENGTH_MEDIUM;
+ end += 6;
+ }
+ else if (strncmp (end, "strong", 6) == 0)
+ {
+ priority = GTK_CONSTRAINT_STRENGTH_STRONG;
+ end += 6;
+ }
+ else if (strncmp (end, "required", 8) == 0)
+ {
+ priority = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+ end += 8;
+ }
+ else
+ {
+ char *range_end = get_offset_to (end, ",)]");
+
+ g_free (predicate->object);
+
+ if (range_end != NULL)
+ parser->error_range = range_end - end - 1;
+ else
+ parser->error_range = 0;
+
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_PRIORITY,
+ "Priority must be a positive number or one of "
+ "'weak', 'medium', 'strong', and 'required'");
+ return FALSE;
+ }
+
+ predicate->priority = priority;
+ }
+ else
+ predicate->priority = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+
+ if (endptr != NULL)
+ *endptr = (char *) end;
+
+ return TRUE;
+}
+
+static gboolean
+parse_view (GtkConstraintVflParser *parser,
+ const char *cursor,
+ VflView *view,
+ char **endptr,
+ GError **error)
+{
+ const char *end = cursor;
+
+ /* <view> = '[' <viewName> (<predicateListWithParens>)? ']'
+ * <viewName> = [A-Za-z_]([A-Za-z0-9_]+)
+ */
+
+ g_assert (*end == '[');
+
+ /* Skip '[' */
+ end += 1;
+
+ if (!(g_ascii_isalpha (*end) || *end == '_'))
+ {
+ parser->error_offset = end - parser->cursor;
+ parser->error_range = 0;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+ "View identifiers must be valid C identifiers");
+ return FALSE;
+ }
+
+ while (g_ascii_isalnum (*end))
+ end += 1;
+
+ if (*end == '\0')
+ {
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "A view must end with ']'");
+ return FALSE;
+ }
+
+ char *name = g_strndup (cursor + 1, end - cursor - 1);
+ if (!has_view (parser, name))
+ {
+ parser->error_offset = (cursor + 1) - parser->cursor;
+ parser->error_range = end - cursor - 1;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+ "Unable to find view with name '%s'", name);
+ g_free (name);
+ return FALSE;
+ }
+
+ view->name = name;
+ view->predicates = g_array_new (FALSE, FALSE, sizeof (VflPredicate));
+
+ if (*end == ']')
+ {
+ if (endptr != NULL)
+ *endptr = (char *) end + 1;
+
+ return TRUE;
+ }
+
+ /* <predicateListWithParens> = '(' <predicate> (',' <predicate>)* ')' */
+ if (*end != '(')
+ {
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "A predicate must follow a view name");
+ return FALSE;
+ }
+
+ end += 1;
+
+ while (*end != '\0')
+ {
+ VflPredicate cur_predicate;
+ char *tmp;
+
+ if (*end == ']' || *end == '\0')
+ {
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "A predicate on a view must end with ')'");
+ return FALSE;
+ }
+
+ memset (&cur_predicate, 0, sizeof (VflPredicate));
+
+ cur_predicate.subject = view->name;
+ if (!parse_predicate (parser, end, &cur_predicate, &tmp, error))
+ return FALSE;
+
+ end = tmp;
+
+#ifdef G_ENABLE_DEBUG
+ g_debug ("*** Found predicate: %s.%s %s %g %s (%g)\n",
+ cur_predicate.object != NULL ? cur_predicate.object : view->name,
+ cur_predicate.attr,
+ cur_predicate.relation == GTK_CONSTRAINT_RELATION_EQ ? "==" :
+ cur_predicate.relation == GTK_CONSTRAINT_RELATION_LE ? "<=" :
+ cur_predicate.relation == GTK_CONSTRAINT_RELATION_GE ? ">=" :
+ "unknown relation",
+ cur_predicate.constant,
+ cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_WEAK ? "weak" :
+ cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_MEDIUM ? "medium" :
+ cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_STRONG ? "strong" :
+ cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_REQUIRED ? "required" :
+ "explicit strength",
+ cur_predicate.priority);
+#endif
+
+ g_array_append_val (view->predicates, cur_predicate);
+
+ /* If the predicate is a list, iterate again */
+ if (*end == ',')
+ {
+ end += 1;
+ continue;
+ }
+
+ /* We reached the end of the predicate */
+ if (*end == ')')
+ {
+ end += 1;
+ break;
+ }
+
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected ')' at the end of a predicate, not '%c'", *end);
+ return FALSE;
+ }
+
+ if (*end != ']')
+ {
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected ']' at the end of a view, not '%c'", *end);
+ return FALSE;
+ }
+
+ if (endptr != NULL)
+ *endptr = (char *) end + 1;
+
+ return TRUE;
+}
+
+static void
+vfl_view_free (VflView *view)
+{
+ if (view == NULL)
+ return;
+
+ g_free (view->name);
+
+ if (view->predicates != NULL)
+ {
+ for (int i = 0; i < view->predicates->len; i++)
+ {
+ VflPredicate *p = &g_array_index (view->predicates, VflPredicate, i);
+
+ g_free (p->object);
+ }
+
+ g_array_free (view->predicates, TRUE);
+ view->predicates = NULL;
+ }
+
+ view->prev_view = NULL;
+ view->next_view = NULL;
+
+ g_slice_free (VflView, view);
+}
+
+static void
+gtk_constraint_vfl_parser_clear (GtkConstraintVflParser *parser)
+{
+ parser->error_offset = 0;
+ parser->error_range = 0;
+
+ VflView *iter = parser->views;
+ while (iter != NULL)
+ {
+ VflView *next = iter->next_view;
+
+ vfl_view_free (iter);
+
+ iter = next;
+ }
+
+ parser->views = NULL;
+ parser->current_view = NULL;
+ parser->leading_super = NULL;
+ parser->trailing_super = NULL;
+
+ parser->cursor = NULL;
+
+ g_free (parser->buffer);
+ parser->buffer_len = 0;
+}
+
+void
+gtk_constraint_vfl_parser_free (GtkConstraintVflParser *parser)
+{
+ if (parser == NULL)
+ return;
+
+ gtk_constraint_vfl_parser_clear (parser);
+
+ g_free (parser);
+}
+
+gboolean
+gtk_constraint_vfl_parser_parse_line (GtkConstraintVflParser *parser,
+ const char *buffer,
+ gssize len,
+ GError **error)
+{
+ gtk_constraint_vfl_parser_clear (parser);
+
+ if (len > 0)
+ {
+ parser->buffer = g_strndup (buffer, len);
+ parser->buffer_len = len;
+ }
+ else
+ {
+ parser->buffer = g_strdup (buffer);
+ parser->buffer_len = strlen (buffer);
+ }
+
+ parser->cursor = parser->buffer;
+
+ const char *cur = parser->cursor;
+
+ /* Skip leading whitespace */
+ while (g_ascii_isspace (*cur))
+ cur += 1;
+
+ /* Check orientation; if none is specified, then we assume horizontal */
+ parser->orientation = VFL_HORIZONTAL;
+ if (*cur == 'H')
+ {
+ cur += 1;
+
+ if (*cur != ':')
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected ':' after horizontal orientation");
+ return FALSE;
+ }
+
+ parser->orientation = VFL_HORIZONTAL;
+ cur += 1;
+ }
+ else if (*cur == 'V')
+ {
+ cur += 1;
+
+ if (*cur != ':')
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected ':' after vertical orientation");
+ return FALSE;
+ }
+
+ parser->orientation = VFL_VERTICAL;
+ cur += 1;
+ }
+
+ while (*cur != '\0')
+ {
+ /* Super-view */
+ if (*cur == '|')
+ {
+ if (parser->views == NULL && parser->leading_super == NULL)
+ {
+ parser->leading_super = g_slice_new0 (VflView);
+
+ parser->leading_super->name = g_strdup ("super");
+ parser->leading_super->orientation = parser->orientation;
+
+ parser->current_view = parser->leading_super;
+ parser->views = parser->leading_super;
+ }
+ else if (parser->trailing_super == NULL)
+ {
+ parser->trailing_super = g_slice_new0 (VflView);
+
+ parser->trailing_super->name = g_strdup ("super");
+ parser->trailing_super->orientation = parser->orientation;
+
+ parser->current_view->next_view = parser->trailing_super;
+ parser->trailing_super->prev_view = parser->current_view;
+
+ parser->current_view = parser->trailing_super;
+
+ /* If we reached the second '|' then we completed a line
+ * of layout, and we can stop
+ */
+ break;
+ }
+ else
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Super views can only appear at the beginning "
+ "and end of the layout, and not multiple times");
+ return FALSE;
+ }
+
+ cur += 1;
+
+ continue;
+ }
+
+ /* Spacing */
+ if (*cur == '-')
+ {
+ if (*(cur + 1) == '\0')
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Unterminated spacing");
+ return FALSE;
+ }
+
+ if (parser->current_view == NULL)
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Spacing cannot be set without a view");
+ return FALSE;
+ }
+
+ if (*(cur + 1) == '|' || *(cur + 1) == '[')
+ {
+ VflSpacing *spacing = &(parser->current_view->spacing);
+
+ /* Default spacer */
+ spacing->flags = SPACING_SET | SPACING_DEFAULT;
+ spacing->size = 0;
+
+ cur += 1;
+
+ continue;
+ }
+ else if (*(cur + 1) == '(')
+ {
+ VflPredicate *predicate;
+ VflSpacing *spacing;
+ char *tmp;
+
+ /* Predicate */
+ cur += 1;
+
+ spacing = &(parser->current_view->spacing);
+ spacing->flags = SPACING_SET | SPACING_PREDICATE;
+ spacing->size = 0;
+
+ /* Spacing predicates have no subject */
+ predicate = &(spacing->predicate);
+ predicate->subject = NULL;
+
+ cur += 1;
+ if (!parse_predicate (parser, cur, predicate, &tmp, error))
+ return FALSE;
+
+ if (*tmp != ')')
+ {
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected ')' at the end of a predicate, not '%c'", *tmp);
+ return FALSE;
+ }
+
+ cur = tmp + 1;
+ if (*cur != '-')
+ {
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Explicit spacing must be followed by '-'");
+ return FALSE;
+ }
+
+ cur += 1;
+
+ continue;
+ }
+ else if (g_ascii_isdigit (*(cur + 1)))
+ {
+ VflSpacing *spacing;
+ char *tmp;
+
+ /* Explicit spacing */
+ cur += 1;
+
+ spacing = &(parser->current_view->spacing);
+ spacing->flags = SPACING_SET;
+ spacing->size = g_ascii_strtod (cur, &tmp);
+
+ if (tmp == cur)
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Spacing must be a number");
+ return FALSE;
+ }
+
+ if (*tmp != '-')
+ {
+ parser->error_offset = cur - parser->cursor;
+ parser->error_range = tmp - cur;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Explicit spacing must be followed by '-'");
+ return FALSE;
+ }
+
+ cur = tmp + 1;
+
+ continue;
+ }
+ else
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Spacing can either be '-' or a number");
+ return FALSE;
+ }
+ }
+
+ if (*cur == '[')
+ {
+ VflView *view = g_slice_new0 (VflView);
+ char *tmp;
+
+ view->orientation = parser->orientation;
+
+ if (!parse_view (parser, cur, view, &tmp, error))
+ {
+ vfl_view_free (view);
+ return FALSE;
+ }
+
+ cur = tmp;
+
+ if (parser->views == NULL)
+ parser->views = view;
+
+ view->prev_view = parser->current_view;
+
+ if (parser->current_view != NULL)
+ parser->current_view->next_view = view;
+
+ parser->current_view = view;
+
+ continue;
+ }
+
+ cur += 1;
+ }
+
+ return TRUE;
+}
+
+GtkConstraintVfl *
+gtk_constraint_vfl_parser_get_constraints (GtkConstraintVflParser *parser,
+ int *n_constraints)
+{
+ GArray *constraints;
+ VflView *iter;
+
+ constraints = g_array_new (FALSE, FALSE, sizeof (GtkConstraintVfl));
+
+ iter = parser->views;
+ while (iter != NULL)
+ {
+ GtkConstraintVfl c;
+
+ if (iter->predicates != NULL)
+ {
+ for (int i = 0; i < iter->predicates->len; i++)
+ {
+ const VflPredicate *p = &g_array_index (iter->predicates, VflPredicate, i);
+
+ c.view1 = iter->name;
+ c.attr1 = iter->orientation == VFL_HORIZONTAL ? "width" : "height";
+ if (p->object != NULL)
+ {
+ c.view2 = p->object;
+ c.attr2 = p->attr;
+ }
+ else
+ {
+ c.view2 = NULL;
+ c.attr2 = NULL;
+ }
+ c.relation = p->relation;
+ c.constant = p->constant;
+ c.multiplier = p->multiplier;
+ c.strength = p->priority;
+
+ g_array_append_val (constraints, c);
+ }
+ }
+
+ if ((iter->spacing.flags & SPACING_SET) != 0)
+ {
+ c.view1 = iter->name;
+
+ /* If this is the first view, we need to anchor the leading edge */
+ if (iter == parser->leading_super)
+ c.attr1 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+ else
+ c.attr1 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+
+ c.view2 = iter->next_view != NULL ? iter->next_view->name : "super";
+
+ if (iter == parser->trailing_super || iter->next_view == parser->trailing_super)
+ c.attr2 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+ else
+ c.attr2 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+
+ if ((iter->spacing.flags & SPACING_PREDICATE) != 0)
+ {
+ const VflPredicate *p = &(iter->spacing.predicate);
+
+ c.constant = p->constant * -1.0;
+ c.relation = p->relation;
+ c.strength = p->priority;
+ }
+ else if ((iter->spacing.flags & SPACING_DEFAULT) != 0)
+ {
+ c.constant = get_default_spacing (parser) * -1.0;
+ c.relation = GTK_CONSTRAINT_RELATION_EQ;
+ c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+ }
+ else
+ {
+ c.constant = iter->spacing.size * -1.0;
+ c.relation = GTK_CONSTRAINT_RELATION_EQ;
+ c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+ }
+
+ c.multiplier = 1.0;
+
+ g_array_append_val (constraints, c);
+ }
+ else if (iter->next_view != NULL)
+ {
+ c.view1 = iter->name;
+
+ /* If this is the first view, we need to anchor the leading edge */
+ if (iter == parser->leading_super)
+ c.attr1 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+ else
+ c.attr1 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+
+ c.relation = GTK_CONSTRAINT_RELATION_EQ;
+
+ c.view2 = iter->next_view->name;
+
+ if (iter->next_view == parser->trailing_super)
+ c.attr2 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+ else
+ c.attr2 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+
+ c.constant = 0.0;
+ c.multiplier = 1.0;
+ c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+
+ g_array_append_val (constraints, c);
+ }
+
+ iter = iter->next_view;
+ }
+
+ if (n_constraints != NULL)
+ *n_constraints = constraints->len;
+
+#ifdef G_ENABLE_DEBUG
+ for (int i = 0; i < constraints->len; i++)
+ {
+ const GtkConstraintVfl *c = &g_array_index (constraints, GtkConstraintVfl, i);
+
+ g_debug ("{\n"
+ " .view1: '%s',\n"
+ " .attr1: '%s',\n"
+ " .relation: '%d',\n"
+ " .view2: '%s',\n"
+ " .attr2: '%s',\n"
+ " .constant: %g,\n"
+ " .multiplier: %g,\n"
+ " .strength: %g\n"
+ "}\n",
+ c->view1, c->attr1,
+ c->relation,
+ c->view2 != NULL ? c->view2 : "none", c->attr2 != NULL ? c->attr2 : "none",
+ c->constant,
+ c->multiplier,
+ c->strength);
+ }
+#endif
+
+ return (GtkConstraintVfl *) g_array_free (constraints, FALSE);
+}
+
+int
+gtk_constraint_vfl_parser_get_error_offset (GtkConstraintVflParser *parser)
+{
+ return parser->error_offset;
+}
+
+int
+gtk_constraint_vfl_parser_get_error_range (GtkConstraintVflParser *parser)
+{
+ return parser->error_range;
+}
diff --git a/gtk/gtkconstraintvflparserprivate.h b/gtk/gtkconstraintvflparserprivate.h
new file mode 100644
index 0000000000..a89b474893
--- /dev/null
+++ b/gtk/gtkconstraintvflparserprivate.h
@@ -0,0 +1,89 @@
+/* gtkconstraintvflparserprivate.h: VFL constraint definition parser
+ *
+ * Copyright 2017 Endless
+ * Copyright 2019 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include "gtkconstrainttypesprivate.h"
+
+G_BEGIN_DECLS
+
+#define GTK_CONSTRAINT_VFL_PARSER_ERROR (gtk_constraint_vfl_parser_error_quark ())
+
+typedef enum {
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_ATTRIBUTE,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_METRIC,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_PRIORITY,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_RELATION
+} VflError;
+
+typedef struct _GtkConstraintVflParser GtkConstraintVflParser;
+
+typedef struct {
+ const char *view1;
+ const char *attr1;
+ GtkConstraintRelation relation;
+ const char *view2;
+ const char *attr2;
+ double constant;
+ double multiplier;
+ double strength;
+} GtkConstraintVfl;
+
+GQuark gtk_constraint_vfl_parser_error_quark (void);
+
+GtkConstraintVflParser *
+gtk_constraint_vfl_parser_new (void);
+
+void
+gtk_constraint_vfl_parser_free (GtkConstraintVflParser *parser);
+
+void
+gtk_constraint_vfl_parser_set_default_spacing (GtkConstraintVflParser *parser,
+ int hspacing,
+ int vspacing);
+
+void
+gtk_constraint_vfl_parser_set_metrics (GtkConstraintVflParser *parser,
+ GHashTable *metrics);
+
+void
+gtk_constraint_vfl_parser_set_views (GtkConstraintVflParser *parser,
+ GHashTable *views);
+
+gboolean
+gtk_constraint_vfl_parser_parse_line (GtkConstraintVflParser *parser,
+ const char *line,
+ gssize len,
+ GError **error);
+
+int
+gtk_constraint_vfl_parser_get_error_offset (GtkConstraintVflParser *parser);
+
+int
+gtk_constraint_vfl_parser_get_error_range (GtkConstraintVflParser *parser);
+
+GtkConstraintVfl *
+gtk_constraint_vfl_parser_get_constraints (GtkConstraintVflParser *parser,
+ int *n_constraints);
+
+G_END_DECLS
diff --git a/gtk/meson.build b/gtk/meson.build
index a79e3e7ecc..0b60ce22e4 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -39,6 +39,7 @@ gtk_private_sources = files([
'gtkcolorswatch.c',
'gtkconstraintexpression.c',
'gtkconstraintsolver.c',
+ 'gtkconstraintvflparser.c',
'gtkcssanimatedstyle.c',
'gtkcssanimation.c',
'gtkcssarrayvalue.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]