1/* gtkconstraintvflparser.c: VFL constraint definition parser
2 *
3 * Copyright 2017 Endless
4 * Copyright 2019 GNOME Foundation
5 *
6 * SPDX-License-Identifier: LGPL-2.1-or-later
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include "config.h"
23
24#include "gtkconstraintvflparserprivate.h"
25#include "gtkenums.h"
26
27#include <string.h>
28
29typedef enum {
30 VFL_HORIZONTAL,
31 VFL_VERTICAL
32} VflOrientation;
33
34typedef struct {
35 GtkConstraintRelation relation;
36
37 double constant;
38 double multiplier;
39 const char *subject;
40 char *object;
41 const char *attr;
42
43 double priority;
44} VflPredicate;
45
46typedef enum {
47 SPACING_SET = 1 << 0,
48 SPACING_DEFAULT = 1 << 1,
49 SPACING_PREDICATE = 1 << 2
50} VflSpacingFlags;
51
52typedef struct {
53 double size;
54 VflSpacingFlags flags;
55 VflPredicate predicate;
56} VflSpacing;
57
58typedef struct _VflView VflView;
59
60struct _VflView
61{
62 char *name;
63
64 /* Decides which attributes are admissible */
65 VflOrientation orientation;
66
67 /* A set of predicates, which will be used to
68 * set up constraints
69 */
70 GArray *predicates;
71
72 VflSpacing spacing;
73
74 VflView *prev_view;
75 VflView *next_view;
76};
77
78struct _GtkConstraintVflParser
79{
80 char *buffer;
81 gsize buffer_len;
82
83 int error_offset;
84 int error_range;
85
86 int default_spacing[2];
87
88 /* Set<name, double> */
89 GHashTable *metrics_set;
90 /* Set<name, widget> */
91 GHashTable *views_set;
92
93 const char *cursor;
94
95 /* Decides which attributes are admissible */
96 VflOrientation orientation;
97
98 VflView *leading_super;
99 VflView *trailing_super;
100
101 VflView *current_view;
102 VflView *views;
103};
104
105/* NOTE: These two symbols are defined in gtkconstraintlayout.h, but we
106 * cannot include that header here
107 */
108#define GTK_CONSTRAINT_VFL_PARSER_ERROR (gtk_constraint_vfl_parser_error_quark ())
109GQuark gtk_constraint_vfl_parser_error_quark (void);
110
111GtkConstraintVflParser *
112gtk_constraint_vfl_parser_new (void)
113{
114 GtkConstraintVflParser *res = g_new0 (GtkConstraintVflParser, 1);
115
116 res->default_spacing[VFL_HORIZONTAL] = 8;
117 res->default_spacing[VFL_VERTICAL] = 8;
118
119 res->orientation = VFL_HORIZONTAL;
120
121 return res;
122}
123
124void
125gtk_constraint_vfl_parser_set_default_spacing (GtkConstraintVflParser *parser,
126 int hspacing,
127 int vspacing)
128{
129 parser->default_spacing[VFL_HORIZONTAL] = hspacing < 0 ? 8 : hspacing;
130 parser->default_spacing[VFL_VERTICAL] = vspacing < 0 ? 8 : vspacing;
131}
132
133void
134gtk_constraint_vfl_parser_set_metrics (GtkConstraintVflParser *parser,
135 GHashTable *metrics)
136{
137 parser->metrics_set = metrics;
138}
139
140void
141gtk_constraint_vfl_parser_set_views (GtkConstraintVflParser *parser,
142 GHashTable *views)
143{
144 parser->views_set = views;
145}
146
147static int
148get_default_spacing (GtkConstraintVflParser *parser)
149{
150 return parser->default_spacing[parser->orientation];
151}
152
153/* Default attributes, if unnamed, depending on the orientation */
154static const char *default_attribute[2] = {
155 [VFL_HORIZONTAL] = "width",
156 [VFL_VERTICAL] = "height",
157};
158
159static gboolean
160parse_relation (const char *str,
161 GtkConstraintRelation *relation,
162 char **endptr,
163 GError **error)
164{
165 const char *cur = str;
166
167 if (*cur == '=')
168 {
169 cur += 1;
170
171 if (*cur == '=')
172 {
173 *relation = GTK_CONSTRAINT_RELATION_EQ;
174 *endptr = (char *) cur + 1;
175 return TRUE;
176 }
177
178 goto out;
179 }
180 else if (*cur == '>')
181 {
182 cur += 1;
183
184 if (*cur == '=')
185 {
186 *relation = GTK_CONSTRAINT_RELATION_GE;
187 *endptr = (char *) cur + 1;
188 return TRUE;
189 }
190
191 goto out;
192 }
193 else if (*cur == '<')
194 {
195 cur += 1;
196
197 if (*cur == '=')
198 {
199 *relation = GTK_CONSTRAINT_RELATION_LE;
200 *endptr = (char *) cur + 1;
201 return TRUE;
202 }
203
204 goto out;
205 }
206
207out:
208 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
209 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_RELATION,
210 format: "Unknown relation; must be one of '==', '>=', or '<='");
211 return FALSE;
212}
213
214static gboolean
215has_metric (GtkConstraintVflParser *parser,
216 const char *name)
217{
218 if (parser->metrics_set == NULL)
219 return FALSE;
220
221 return g_hash_table_contains (hash_table: parser->metrics_set, key: name);
222}
223
224static gboolean
225has_view (GtkConstraintVflParser *parser,
226 const char *name)
227{
228 if (parser->views_set == NULL)
229 return FALSE;
230
231 if (!g_hash_table_contains (hash_table: parser->views_set, key: name))
232 return FALSE;
233
234 return g_hash_table_lookup (hash_table: parser->views_set, key: name) != NULL;
235}
236
237/* Valid attributes */
238static const struct {
239 int len;
240 const char *name;
241} valid_attributes[] = {
242 { 5, "width" },
243 { 6, "height" },
244 { 7, "centerX" },
245 { 7, "centerY" },
246 { 3, "top" },
247 { 6, "bottom" },
248 { 4, "left" },
249 { 5, "right" },
250 { 5, "start" },
251 { 3, "end" },
252 { 8, "baseline" }
253};
254
255static char *
256get_offset_to (const char *str,
257 const char *tokens)
258{
259 char *offset = NULL;
260 int n_tokens = strlen (s: tokens);
261
262 for (int i = 0; i < n_tokens; i++)
263 {
264 if ((offset = strchr (s: str, c: tokens[i])) != NULL)
265 break;
266 }
267
268 return offset;
269}
270
271static gboolean
272parse_predicate (GtkConstraintVflParser *parser,
273 const char *cursor,
274 VflPredicate *predicate,
275 char **endptr,
276 GError **error)
277{
278 VflOrientation orientation = parser->orientation;
279 const char *end = cursor;
280
281 predicate->object = NULL;
282 predicate->multiplier = 1.0;
283
284 /* <predicate> = (<relation>)? (<objectOfPredicate>) ('.'<attribute>)? (<operator>)? ('@'<priority>)?
285 * <relation> = '==' | '<=' | '>='
286 * <objectOfPredicate> = <constant> | <viewName>
287 * <constant> = <number> | <metricName>
288 * <viewName> = [A-Za-z_]([A-Za-z0-9_]*)
289 * <metricName> = [A-Za-z_]([A-Za-z0-9_]*)
290 * <operator> = (['*'|'/']<positiveNumber>)? (['+'|'-']<positiveNumber>)?
291 * <priority> = <positiveNumber> | 'weak' | 'medium' | 'strong' | 'required'
292 */
293
294 /* Parse relation */
295 if (*end == '=' || *end == '>' || *end == '<')
296 {
297 GtkConstraintRelation relation;
298 char *tmp;
299
300 if (!parse_relation (str: end, relation: &relation, endptr: &tmp, error))
301 {
302 parser->error_offset = end - parser->cursor;
303 parser->error_range = 0;
304 return FALSE;
305 }
306
307 predicate->relation = relation;
308
309 end = tmp;
310 }
311 else
312 predicate->relation = GTK_CONSTRAINT_RELATION_EQ;
313
314 /* Parse object of predicate */
315 if (g_ascii_isdigit (*end))
316 {
317 char *tmp;
318
319 /* <constant> */
320 predicate->object = NULL;
321 predicate->attr = default_attribute[orientation];
322 predicate->constant = g_ascii_strtod (nptr: end, endptr: &tmp);
323
324 end = tmp;
325 }
326 else if (g_ascii_isalpha (*end) || *end == '_')
327 {
328 const char *name_start = end;
329
330 while (g_ascii_isalnum (*end) || *end == '_')
331 end += 1;
332
333 char *name = g_strndup (str: name_start, n: end - name_start);
334
335 /* We only accept view names if the subject of the predicate
336 * is a view, i.e. we do not allow view names inside a spacing
337 * predicate
338 */
339 if (predicate->subject == NULL)
340 {
341 if (parser->metrics_set == NULL || !has_metric (parser, name))
342 {
343 parser->error_offset = name_start - parser->cursor;
344 parser->error_range = end - name_start;
345 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
346 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_METRIC,
347 format: "Unable to find metric with name '%s'", name);
348 g_free (mem: name);
349 return FALSE;
350 }
351
352 double *val = g_hash_table_lookup (hash_table: parser->metrics_set, key: name);
353
354 predicate->object = NULL;
355 predicate->attr = default_attribute[orientation];
356 predicate->constant = *val;
357
358 g_free (mem: name);
359
360 goto parse_operators;
361 }
362
363 if (has_metric (parser, name))
364 {
365 double *val = g_hash_table_lookup (hash_table: parser->metrics_set, key: name);
366
367 predicate->object = NULL;
368 predicate->attr = default_attribute[orientation];
369 predicate->constant = *val;
370
371 g_free (mem: name);
372
373 goto parse_operators;
374 }
375
376 if (has_view (parser, name))
377 {
378 /* Transfer name's ownership to the predicate */
379 predicate->object = name;
380 predicate->attr = default_attribute[orientation];
381 predicate->constant = 0;
382
383 goto parse_attribute;
384 }
385
386 parser->error_offset = name_start - parser->cursor;
387 parser->error_range = end - name_start;
388 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
389 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
390 format: "Unable to find view with name '%s'", name);
391 g_free (mem: name);
392 return FALSE;
393 }
394 else
395 {
396 parser->error_offset = end - parser->cursor;
397 parser->error_range = 0;
398 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
399 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
400 format: "Expected constant, view name, or metric");
401 return FALSE;
402 }
403
404parse_attribute:
405 if (*end == '.')
406 {
407 end += 1;
408 predicate->attr = NULL;
409
410 for (int i = 0; i < G_N_ELEMENTS (valid_attributes); i++)
411 {
412 if (g_ascii_strncasecmp (s1: valid_attributes[i].name, s2: end, n: valid_attributes[i].len) == 0)
413 {
414 predicate->attr = valid_attributes[i].name;
415 end += valid_attributes[i].len;
416 }
417 }
418
419 if (predicate->attr == NULL)
420 {
421 char *range_end = get_offset_to (str: end, tokens: "*/+-@,)]");
422
423 if (range_end != NULL)
424 parser->error_range = range_end - end - 1;
425 else
426 parser->error_range = 0;
427
428 g_free (mem: predicate->object);
429
430 parser->error_offset = end - parser->cursor;
431 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
432 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_ATTRIBUTE,
433 format: "Attribute must be on one of 'width', 'height', "
434 "'centerX', 'centerY', 'top', 'bottom', "
435 "'left', 'right', 'start', 'end', 'baseline'");
436 return FALSE;
437 }
438 }
439
440parse_operators:
441 /* Parse multiplier operator */
442 while (g_ascii_isspace (*end))
443 end += 1;
444
445 if ((*end == '*') || (*end == '/'))
446 {
447 double multiplier;
448 const char *operator;
449
450 operator = end;
451 end += 1;
452
453 while (g_ascii_isspace (*end))
454 end += 1;
455
456 if (g_ascii_isdigit (*end))
457 {
458 char *tmp;
459
460 multiplier = g_ascii_strtod (nptr: end, endptr: &tmp);
461 end = tmp;
462 }
463 else
464 {
465 g_free (mem: predicate->object);
466
467 parser->error_offset = end - parser->cursor;
468 parser->error_range = 0;
469 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
470 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
471 format: "Expected a positive number as a multiplier");
472 return FALSE;
473 }
474
475 if (predicate->object != NULL)
476 {
477 if (*operator == '*')
478 predicate->multiplier = multiplier;
479 else
480 predicate->multiplier = 1.0 / multiplier;
481 }
482 else
483 {
484 /* If the subject is a constant then apply multiplier directly */
485 if (*operator == '*')
486 predicate->constant *= multiplier;
487 else
488 predicate->constant *= 1.0 / multiplier;
489 }
490 }
491
492 /* Parse constant operator */
493 while (g_ascii_isspace (*end))
494 end += 1;
495
496 if ((*end == '+') || (*end == '-'))
497 {
498 double constant;
499 const char *operator;
500
501 operator = end;
502 end += 1;
503
504 while (g_ascii_isspace (*end))
505 end += 1;
506
507 if (g_ascii_isdigit (*end))
508 {
509 char *tmp;
510
511 constant = g_ascii_strtod (nptr: end, endptr: &tmp);
512 end = tmp;
513 }
514 else
515 {
516 g_free (mem: predicate->object);
517
518 parser->error_offset = end - parser->cursor;
519 parser->error_range = 0;
520 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
521 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
522 format: "Expected positive number as a constant");
523 return FALSE;
524 }
525
526 if (*operator == '+')
527 predicate->constant += constant;
528 else
529 predicate->constant += -1.0 * constant;
530 }
531
532 /* Parse priority */
533 if (*end == '@')
534 {
535 double priority;
536 end += 1;
537
538 if (g_ascii_isdigit (*end))
539 {
540 char *tmp;
541
542 priority = g_ascii_strtod (nptr: end, endptr: &tmp);
543 end = tmp;
544 }
545 else if (strncmp (s1: end, s2: "weak", n: 4) == 0)
546 {
547 priority = GTK_CONSTRAINT_STRENGTH_WEAK;
548 end += 4;
549 }
550 else if (strncmp (s1: end, s2: "medium", n: 6) == 0)
551 {
552 priority = GTK_CONSTRAINT_STRENGTH_MEDIUM;
553 end += 6;
554 }
555 else if (strncmp (s1: end, s2: "strong", n: 6) == 0)
556 {
557 priority = GTK_CONSTRAINT_STRENGTH_STRONG;
558 end += 6;
559 }
560 else if (strncmp (s1: end, s2: "required", n: 8) == 0)
561 {
562 priority = GTK_CONSTRAINT_STRENGTH_REQUIRED;
563 end += 8;
564 }
565 else
566 {
567 char *range_end = get_offset_to (str: end, tokens: ",)]");
568
569 g_free (mem: predicate->object);
570
571 if (range_end != NULL)
572 parser->error_range = range_end - end - 1;
573 else
574 parser->error_range = 0;
575
576 parser->error_offset = end - parser->cursor;
577 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
578 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_PRIORITY,
579 format: "Priority must be a positive number or one of "
580 "'weak', 'medium', 'strong', and 'required'");
581 return FALSE;
582 }
583
584 predicate->priority = priority;
585 }
586 else
587 predicate->priority = GTK_CONSTRAINT_STRENGTH_REQUIRED;
588
589 if (endptr != NULL)
590 *endptr = (char *) end;
591
592 return TRUE;
593}
594
595static gboolean
596parse_view (GtkConstraintVflParser *parser,
597 const char *cursor,
598 VflView *view,
599 char **endptr,
600 GError **error)
601{
602 const char *end = cursor;
603
604 /* <view> = '[' <viewName> (<predicateListWithParens>)? ']'
605 * <viewName> = [A-Za-z_]([A-Za-z0-9_]+)
606 */
607
608 g_assert (*end == '[');
609
610 /* Skip '[' */
611 end += 1;
612
613 if (!(g_ascii_isalpha (*end) || *end == '_'))
614 {
615 parser->error_offset = end - parser->cursor;
616 parser->error_range = 0;
617 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
618 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
619 format: "View identifiers must be valid C identifiers");
620 return FALSE;
621 }
622
623 while (g_ascii_isalnum (*end))
624 end += 1;
625
626 if (*end == '\0')
627 {
628 parser->error_offset = end - parser->cursor;
629 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
630 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
631 format: "A view must end with ']'");
632 return FALSE;
633 }
634
635 char *name = g_strndup (str: cursor + 1, n: end - cursor - 1);
636 if (!has_view (parser, name))
637 {
638 parser->error_offset = (cursor + 1) - parser->cursor;
639 parser->error_range = end - cursor - 1;
640 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
641 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
642 format: "Unable to find view with name '%s'", name);
643 g_free (mem: name);
644 return FALSE;
645 }
646
647 view->name = name;
648 view->predicates = g_array_new (FALSE, FALSE, element_size: sizeof (VflPredicate));
649
650 if (*end == ']')
651 {
652 if (endptr != NULL)
653 *endptr = (char *) end + 1;
654
655 return TRUE;
656 }
657
658 /* <predicateListWithParens> = '(' <predicate> (',' <predicate>)* ')' */
659 if (*end != '(')
660 {
661 parser->error_offset = end - parser->cursor;
662 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
663 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
664 format: "A predicate must follow a view name");
665 return FALSE;
666 }
667
668 end += 1;
669
670 while (*end != '\0')
671 {
672 VflPredicate cur_predicate;
673 char *tmp;
674
675 if (*end == ']' || *end == '\0')
676 {
677 parser->error_offset = end - parser->cursor;
678 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
679 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
680 format: "A predicate on a view must end with ')'");
681 return FALSE;
682 }
683
684 memset (s: &cur_predicate, c: 0, n: sizeof (VflPredicate));
685
686 cur_predicate.subject = view->name;
687 if (!parse_predicate (parser, cursor: end, predicate: &cur_predicate, endptr: &tmp, error))
688 return FALSE;
689
690 end = tmp;
691
692#ifdef G_ENABLE_DEBUG
693 g_debug ("*** Found predicate: %s.%s %s %g %s (%g)\n",
694 cur_predicate.object != NULL ? cur_predicate.object : view->name,
695 cur_predicate.attr,
696 cur_predicate.relation == GTK_CONSTRAINT_RELATION_EQ ? "==" :
697 cur_predicate.relation == GTK_CONSTRAINT_RELATION_LE ? "<=" :
698 cur_predicate.relation == GTK_CONSTRAINT_RELATION_GE ? ">=" :
699 "unknown relation",
700 cur_predicate.constant,
701 cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_WEAK ? "weak" :
702 cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_MEDIUM ? "medium" :
703 cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_STRONG ? "strong" :
704 cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_REQUIRED ? "required" :
705 "explicit strength",
706 cur_predicate.priority);
707#endif
708
709 g_array_append_val (view->predicates, cur_predicate);
710
711 /* If the predicate is a list, iterate again */
712 if (*end == ',')
713 {
714 end += 1;
715 continue;
716 }
717
718 /* We reached the end of the predicate */
719 if (*end == ')')
720 {
721 end += 1;
722 break;
723 }
724
725 parser->error_offset = end - parser->cursor;
726 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
727 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
728 format: "Expected ')' at the end of a predicate, not '%c'", *end);
729 return FALSE;
730 }
731
732 if (*end != ']')
733 {
734 parser->error_offset = end - parser->cursor;
735 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
736 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
737 format: "Expected ']' at the end of a view, not '%c'", *end);
738 return FALSE;
739 }
740
741 if (endptr != NULL)
742 *endptr = (char *) end + 1;
743
744 return TRUE;
745}
746
747static void
748vfl_view_free (VflView *view)
749{
750 if (view == NULL)
751 return;
752
753 g_free (mem: view->name);
754
755 if (view->predicates != NULL)
756 {
757 for (int i = 0; i < view->predicates->len; i++)
758 {
759 VflPredicate *p = &g_array_index (view->predicates, VflPredicate, i);
760
761 g_free (mem: p->object);
762 }
763
764 g_array_free (array: view->predicates, TRUE);
765 view->predicates = NULL;
766 }
767
768 view->prev_view = NULL;
769 view->next_view = NULL;
770
771 g_slice_free (VflView, view);
772}
773
774static void
775gtk_constraint_vfl_parser_clear (GtkConstraintVflParser *parser)
776{
777 parser->error_offset = 0;
778 parser->error_range = 0;
779
780 VflView *iter = parser->views;
781 while (iter != NULL)
782 {
783 VflView *next = iter->next_view;
784
785 vfl_view_free (view: iter);
786
787 iter = next;
788 }
789
790 parser->views = NULL;
791 parser->current_view = NULL;
792 parser->leading_super = NULL;
793 parser->trailing_super = NULL;
794
795 parser->cursor = NULL;
796
797 g_free (mem: parser->buffer);
798 parser->buffer_len = 0;
799}
800
801void
802gtk_constraint_vfl_parser_free (GtkConstraintVflParser *parser)
803{
804 if (parser == NULL)
805 return;
806
807 gtk_constraint_vfl_parser_clear (parser);
808
809 g_free (mem: parser);
810}
811
812gboolean
813gtk_constraint_vfl_parser_parse_line (GtkConstraintVflParser *parser,
814 const char *buffer,
815 gssize len,
816 GError **error)
817{
818 gtk_constraint_vfl_parser_clear (parser);
819
820 if (len > 0)
821 {
822 parser->buffer = g_strndup (str: buffer, n: len);
823 parser->buffer_len = len;
824 }
825 else
826 {
827 parser->buffer = g_strdup (str: buffer);
828 parser->buffer_len = strlen (s: buffer);
829 }
830
831 parser->cursor = parser->buffer;
832
833 const char *cur = parser->cursor;
834
835 /* Skip leading whitespace */
836 while (g_ascii_isspace (*cur))
837 cur += 1;
838
839 /* Check orientation; if none is specified, then we assume horizontal */
840 parser->orientation = VFL_HORIZONTAL;
841 if (*cur == 'H')
842 {
843 cur += 1;
844
845 if (*cur != ':')
846 {
847 parser->error_offset = cur - parser->cursor;
848 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
849 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
850 format: "Expected ':' after horizontal orientation");
851 return FALSE;
852 }
853
854 parser->orientation = VFL_HORIZONTAL;
855 cur += 1;
856 }
857 else if (*cur == 'V')
858 {
859 cur += 1;
860
861 if (*cur != ':')
862 {
863 parser->error_offset = cur - parser->cursor;
864 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
865 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
866 format: "Expected ':' after vertical orientation");
867 return FALSE;
868 }
869
870 parser->orientation = VFL_VERTICAL;
871 cur += 1;
872 }
873
874 while (*cur != '\0')
875 {
876 /* Super-view */
877 if (*cur == '|')
878 {
879 if (parser->views == NULL && parser->leading_super == NULL)
880 {
881 parser->leading_super = g_slice_new0 (VflView);
882
883 parser->leading_super->name = g_strdup (str: "super");
884 parser->leading_super->orientation = parser->orientation;
885
886 parser->current_view = parser->leading_super;
887 parser->views = parser->leading_super;
888 }
889 else if (parser->trailing_super == NULL)
890 {
891 parser->trailing_super = g_slice_new0 (VflView);
892
893 parser->trailing_super->name = g_strdup (str: "super");
894 parser->trailing_super->orientation = parser->orientation;
895
896 parser->current_view->next_view = parser->trailing_super;
897 parser->trailing_super->prev_view = parser->current_view;
898
899 parser->current_view = parser->trailing_super;
900
901 /* If we reached the second '|' then we completed a line
902 * of layout, and we can stop
903 */
904 break;
905 }
906 else
907 {
908 parser->error_offset = cur - parser->cursor;
909 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
910 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
911 format: "Super views can only appear at the beginning "
912 "and end of the layout, and not multiple times");
913 return FALSE;
914 }
915
916 cur += 1;
917
918 continue;
919 }
920
921 /* Spacing */
922 if (*cur == '-')
923 {
924 if (*(cur + 1) == '\0')
925 {
926 parser->error_offset = cur - parser->cursor;
927 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
928 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
929 format: "Unterminated spacing");
930 return FALSE;
931 }
932
933 if (parser->current_view == NULL)
934 {
935 parser->error_offset = cur - parser->cursor;
936 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
937 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
938 format: "Spacing cannot be set without a view");
939 return FALSE;
940 }
941
942 if (*(cur + 1) == '|' || *(cur + 1) == '[')
943 {
944 VflSpacing *spacing = &(parser->current_view->spacing);
945
946 /* Default spacer */
947 spacing->flags = SPACING_SET | SPACING_DEFAULT;
948 spacing->size = 0;
949
950 cur += 1;
951
952 continue;
953 }
954 else if (*(cur + 1) == '(')
955 {
956 VflPredicate *predicate;
957 VflSpacing *spacing;
958 char *tmp;
959
960 /* Predicate */
961 cur += 1;
962
963 spacing = &(parser->current_view->spacing);
964 spacing->flags = SPACING_SET | SPACING_PREDICATE;
965 spacing->size = 0;
966
967 /* Spacing predicates have no subject */
968 predicate = &(spacing->predicate);
969 predicate->subject = NULL;
970
971 cur += 1;
972 if (!parse_predicate (parser, cursor: cur, predicate, endptr: &tmp, error))
973 return FALSE;
974
975 if (*tmp != ')')
976 {
977 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
978 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
979 format: "Expected ')' at the end of a predicate, not '%c'", *tmp);
980 return FALSE;
981 }
982
983 cur = tmp + 1;
984 if (*cur != '-')
985 {
986 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
987 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
988 format: "Explicit spacing must be followed by '-'");
989 return FALSE;
990 }
991
992 cur += 1;
993
994 continue;
995 }
996 else if (g_ascii_isdigit (*(cur + 1)))
997 {
998 VflSpacing *spacing;
999 char *tmp;
1000
1001 /* Explicit spacing */
1002 cur += 1;
1003
1004 spacing = &(parser->current_view->spacing);
1005 spacing->flags = SPACING_SET;
1006 spacing->size = g_ascii_strtod (nptr: cur, endptr: &tmp);
1007
1008 if (tmp == cur)
1009 {
1010 parser->error_offset = cur - parser->cursor;
1011 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
1012 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
1013 format: "Spacing must be a number");
1014 return FALSE;
1015 }
1016
1017 if (*tmp != '-')
1018 {
1019 parser->error_offset = cur - parser->cursor;
1020 parser->error_range = tmp - cur;
1021 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
1022 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
1023 format: "Explicit spacing must be followed by '-'");
1024 return FALSE;
1025 }
1026
1027 cur = tmp + 1;
1028
1029 continue;
1030 }
1031 else
1032 {
1033 parser->error_offset = cur - parser->cursor;
1034 g_set_error (err: error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
1035 code: GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
1036 format: "Spacing can either be '-' or a number");
1037 return FALSE;
1038 }
1039 }
1040
1041 if (*cur == '[')
1042 {
1043 VflView *view = g_slice_new0 (VflView);
1044 char *tmp;
1045
1046 view->orientation = parser->orientation;
1047
1048 if (!parse_view (parser, cursor: cur, view, endptr: &tmp, error))
1049 {
1050 vfl_view_free (view);
1051 return FALSE;
1052 }
1053
1054 cur = tmp;
1055
1056 if (parser->views == NULL)
1057 parser->views = view;
1058
1059 view->prev_view = parser->current_view;
1060
1061 if (parser->current_view != NULL)
1062 parser->current_view->next_view = view;
1063
1064 parser->current_view = view;
1065
1066 continue;
1067 }
1068
1069 cur += 1;
1070 }
1071
1072 return TRUE;
1073}
1074
1075GtkConstraintVfl *
1076gtk_constraint_vfl_parser_get_constraints (GtkConstraintVflParser *parser,
1077 int *n_constraints)
1078{
1079 GArray *constraints;
1080 VflView *iter;
1081
1082 constraints = g_array_new (FALSE, FALSE, element_size: sizeof (GtkConstraintVfl));
1083
1084 iter = parser->views;
1085 while (iter != NULL)
1086 {
1087 GtkConstraintVfl c;
1088
1089 if (iter->predicates != NULL)
1090 {
1091 for (int i = 0; i < iter->predicates->len; i++)
1092 {
1093 const VflPredicate *p = &g_array_index (iter->predicates, VflPredicate, i);
1094
1095 c.view1 = iter->name;
1096 c.attr1 = iter->orientation == VFL_HORIZONTAL ? "width" : "height";
1097 if (p->object != NULL)
1098 {
1099 c.view2 = p->object;
1100 c.attr2 = p->attr;
1101 }
1102 else
1103 {
1104 c.view2 = NULL;
1105 c.attr2 = NULL;
1106 }
1107 c.relation = p->relation;
1108 c.constant = p->constant;
1109 c.multiplier = p->multiplier;
1110 c.strength = p->priority;
1111
1112 g_array_append_val (constraints, c);
1113 }
1114 }
1115
1116 if ((iter->spacing.flags & SPACING_SET) != 0)
1117 {
1118 c.view1 = iter->name;
1119
1120 /* If this is the first view, we need to anchor the leading edge */
1121 if (iter == parser->leading_super)
1122 c.attr1 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
1123 else
1124 c.attr1 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
1125
1126 c.view2 = iter->next_view != NULL ? iter->next_view->name : "super";
1127
1128 if (iter == parser->trailing_super || iter->next_view == parser->trailing_super)
1129 c.attr2 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
1130 else
1131 c.attr2 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
1132
1133 if ((iter->spacing.flags & SPACING_PREDICATE) != 0)
1134 {
1135 const VflPredicate *p = &(iter->spacing.predicate);
1136
1137 c.constant = p->constant * -1.0;
1138 c.relation = p->relation;
1139 c.strength = p->priority;
1140 }
1141 else if ((iter->spacing.flags & SPACING_DEFAULT) != 0)
1142 {
1143 c.constant = get_default_spacing (parser) * -1.0;
1144 c.relation = GTK_CONSTRAINT_RELATION_EQ;
1145 c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
1146 }
1147 else
1148 {
1149 c.constant = iter->spacing.size * -1.0;
1150 c.relation = GTK_CONSTRAINT_RELATION_EQ;
1151 c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
1152 }
1153
1154 c.multiplier = 1.0;
1155
1156 g_array_append_val (constraints, c);
1157 }
1158 else if (iter->next_view != NULL)
1159 {
1160 c.view1 = iter->name;
1161
1162 /* If this is the first view, we need to anchor the leading edge */
1163 if (iter == parser->leading_super)
1164 c.attr1 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
1165 else
1166 c.attr1 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
1167
1168 c.relation = GTK_CONSTRAINT_RELATION_EQ;
1169
1170 c.view2 = iter->next_view->name;
1171
1172 if (iter->next_view == parser->trailing_super)
1173 c.attr2 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
1174 else
1175 c.attr2 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
1176
1177 c.constant = 0.0;
1178 c.multiplier = 1.0;
1179 c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
1180
1181 g_array_append_val (constraints, c);
1182 }
1183
1184 iter = iter->next_view;
1185 }
1186
1187 if (n_constraints != NULL)
1188 *n_constraints = constraints->len;
1189
1190#ifdef G_ENABLE_DEBUG
1191 for (int i = 0; i < constraints->len; i++)
1192 {
1193 const GtkConstraintVfl *c = &g_array_index (constraints, GtkConstraintVfl, i);
1194
1195 g_debug ("{\n"
1196 " .view1: '%s',\n"
1197 " .attr1: '%s',\n"
1198 " .relation: '%d',\n"
1199 " .view2: '%s',\n"
1200 " .attr2: '%s',\n"
1201 " .constant: %g,\n"
1202 " .multiplier: %g,\n"
1203 " .strength: %g\n"
1204 "}\n",
1205 c->view1, c->attr1,
1206 c->relation,
1207 c->view2 != NULL ? c->view2 : "none", c->attr2 != NULL ? c->attr2 : "none",
1208 c->constant,
1209 c->multiplier,
1210 c->strength);
1211 }
1212#endif
1213
1214 return (GtkConstraintVfl *) g_array_free (array: constraints, FALSE);
1215}
1216
1217int
1218gtk_constraint_vfl_parser_get_error_offset (GtkConstraintVflParser *parser)
1219{
1220 return parser->error_offset;
1221}
1222
1223int
1224gtk_constraint_vfl_parser_get_error_range (GtkConstraintVflParser *parser)
1225{
1226 return parser->error_range;
1227}
1228

source code of gtk/gtk/gtkconstraintvflparser.c