gtk2/gtk/gtkconstraintvflparser.c
Emmanuele Bassi 5ea8167802 Add VFL parser for constraints
Constraints can be expressed with a compact syntax, called VFL (visual
format language).
2019-06-30 23:42:45 +01:00

1227 lines
33 KiB
C

/* 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;
}