1 /* dzl-state-machine-buildable.c
2 *
3 * Copyright (C) 2015 Christian Hergert <christian@hergert.me>
4 *
5 * This file is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This file is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #define G_LOG_DOMAIN "dzl-state-machine"
20
21 #include "config.h"
22
23 #include <errno.h>
24 #include <glib/gi18n.h>
25
26 #include "dzl-state-machine.h"
27 #include "dzl-state-machine-buildable.h"
28
29 typedef struct
30 {
31 DzlStateMachine *self;
32 GtkBuilder *builder;
33 GQueue *stack;
34 } StatesParserData;
35
36 typedef enum
37 {
38 STACK_ITEM_OBJECT,
39 STACK_ITEM_STATE,
40 STACK_ITEM_PROPERTY,
41 } StackItemType;
42
43 typedef struct
44 {
45 StackItemType type;
46 union {
47 struct {
48 gchar *id;
49 GSList *classes;
50 GSList *properties;
51 } object;
52 struct {
53 gchar *name;
54 GSList *objects;
55 } state;
56 struct {
57 gchar *name;
58 gchar *bind_source;
59 gchar *bind_property;
60 gchar *text;
61 GBindingFlags bind_flags;
62 } property;
63 } u;
64 } StackItem;
65
66 static GtkBuildableIface *dzl_state_machine_parent_buildable;
67
68 static void
stack_item_free(StackItem * item)69 stack_item_free (StackItem *item)
70 {
71 switch (item->type)
72 {
73 case STACK_ITEM_OBJECT:
74 g_free (item->u.object.id);
75 g_slist_free_full (item->u.object.classes, g_free);
76 g_slist_free_full (item->u.object.properties, (GDestroyNotify)stack_item_free);
77 break;
78
79 case STACK_ITEM_STATE:
80 g_free (item->u.state.name);
81 g_slist_free_full (item->u.state.objects, (GDestroyNotify)stack_item_free);
82 break;
83
84 case STACK_ITEM_PROPERTY:
85 g_free (item->u.property.name);
86 g_free (item->u.property.bind_source);
87 g_free (item->u.property.bind_property);
88 g_free (item->u.property.text);
89 break;
90
91 default:
92 g_assert_not_reached ();
93 break;
94 }
95
96 g_slice_free (StackItem, item);
97 }
98
99 static StackItem *
stack_item_new(StackItemType type)100 stack_item_new (StackItemType type)
101 {
102 StackItem *item;
103
104 item = g_slice_new0 (StackItem);
105 item->type = type;
106
107 return item;
108 }
109
110 static void
add_state(StatesParserData * parser_data,StackItem * item,GError ** error)111 add_state (StatesParserData *parser_data,
112 StackItem *item,
113 GError **error)
114 {
115 GSList *iter;
116
117 g_assert (parser_data != NULL);
118 g_assert (item != NULL);
119 g_assert (item->type == STACK_ITEM_STATE);
120
121 for (iter = item->u.state.objects; iter; iter = iter->next)
122 {
123 StackItem *stack_obj = iter->data;
124 GObject *object;
125 GSList *prop_iter;
126 GSList *style_iter;
127
128 g_assert (stack_obj->type == STACK_ITEM_OBJECT);
129 g_assert (stack_obj->u.object.id != NULL);
130
131 object = gtk_builder_get_object (parser_data->builder, stack_obj->u.object.id);
132
133 if (object == NULL)
134 {
135 g_set_error (error,
136 GTK_BUILDER_ERROR,
137 GTK_BUILDER_ERROR_INVALID_VALUE,
138 "Unknown object for state '%s': %s",
139 item->u.state.name,
140 stack_obj->u.object.id);
141 return;
142 }
143
144 if (GTK_IS_WIDGET (object))
145 for (style_iter = stack_obj->u.object.classes; style_iter; style_iter = style_iter->next)
146 dzl_state_machine_add_style (parser_data->self,
147 item->u.state.name,
148 GTK_WIDGET (object),
149 style_iter->data);
150
151 for (prop_iter = stack_obj->u.object.properties; prop_iter; prop_iter = prop_iter->next)
152 {
153 StackItem *stack_prop = prop_iter->data;
154 GObject *bind_source;
155
156 g_assert (stack_prop->type == STACK_ITEM_PROPERTY);
157
158 if ((stack_prop->u.property.bind_source != NULL) &&
159 (stack_prop->u.property.bind_property != NULL) &&
160 (bind_source = gtk_builder_get_object (parser_data->builder, stack_prop->u.property.bind_source)))
161 {
162 dzl_state_machine_add_binding (parser_data->self,
163 item->u.state.name,
164 bind_source,
165 stack_prop->u.property.bind_property,
166 object,
167 stack_prop->u.property.name,
168 stack_prop->u.property.bind_flags);
169 }
170 else if (stack_prop->u.property.text != NULL)
171 {
172 GParamSpec *pspec;
173 GValue value = G_VALUE_INIT;
174
175 pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), stack_prop->u.property.name);
176
177 if (pspec == NULL)
178 {
179 g_set_error (error,
180 GTK_BUILDER_ERROR,
181 GTK_BUILDER_ERROR_INVALID_PROPERTY,
182 "No such property: %s",
183 stack_prop->u.property.name);
184 return;
185 }
186
187 if (g_type_is_a (pspec->value_type, G_TYPE_OBJECT))
188 {
189 GObject *relative;
190
191 relative = gtk_builder_get_object (parser_data->builder, stack_prop->u.property.text);
192
193 if (relative == NULL)
194 {
195 g_set_error (error,
196 GTK_BUILDER_ERROR,
197 GTK_BUILDER_ERROR_INVALID_VALUE,
198 "Unknown object for property '%s': %s",
199 stack_prop->u.property.name,
200 stack_prop->u.property.text);
201 return;
202 }
203
204 g_value_init (&value, pspec->value_type);
205 g_value_set_object (&value, relative);
206 }
207 else if (!gtk_builder_value_from_string (parser_data->builder,
208 pspec,
209 stack_prop->u.property.text,
210 &value,
211 error))
212 {
213 return;
214 }
215
216 dzl_state_machine_add_propertyv (parser_data->self,
217 item->u.state.name,
218 object,
219 stack_prop->u.property.name,
220 &value);
221
222 g_value_unset (&value);
223 }
224 }
225 }
226 }
227
228 static void
add_object(StatesParserData * parser_data,StackItem * parent,StackItem * item)229 add_object (StatesParserData *parser_data,
230 StackItem *parent,
231 StackItem *item)
232 {
233 g_assert (parser_data != NULL);
234 g_assert (parent != NULL);
235 g_assert (parent->type == STACK_ITEM_STATE);
236 g_assert (item != NULL);
237 g_assert (item->type == STACK_ITEM_OBJECT);
238
239 parent->u.state.objects = g_slist_prepend (parent->u.state.objects, item);
240 }
241
242 static void
add_property(StatesParserData * parser_data,StackItem * parent,StackItem * item)243 add_property (StatesParserData *parser_data,
244 StackItem *parent,
245 StackItem *item)
246 {
247 g_assert (parser_data != NULL);
248 g_assert (parent != NULL);
249 g_assert (parent->type == STACK_ITEM_OBJECT);
250 g_assert (item != NULL);
251 g_assert (item->type == STACK_ITEM_PROPERTY);
252
253 parent->u.object.properties = g_slist_prepend (parent->u.object.properties, item);
254 }
255
256 static gboolean
check_parent(GMarkupParseContext * context,const gchar * element_name,GError ** error)257 check_parent (GMarkupParseContext *context,
258 const gchar *element_name,
259 GError **error)
260 {
261 const GSList *stack;
262 const gchar *parent_name;
263 const gchar *our_name;
264
265 stack = g_markup_parse_context_get_element_stack (context);
266 our_name = stack->data;
267 parent_name = stack->next ? stack->next->data : "";
268
269 if (g_strcmp0 (parent_name, element_name) != 0)
270 {
271 gint line;
272 gint col;
273
274 g_markup_parse_context_get_position (context, &line, &col);
275 g_set_error (error,
276 GTK_BUILDER_ERROR,
277 GTK_BUILDER_ERROR_INVALID_TAG,
278 "%d:%d: Element <%s> found in <%s>, expected <%s>.",
279 line, col, our_name, parent_name, element_name);
280 return FALSE;
281 }
282
283 return TRUE;
284 }
285
286 /*
287 * flags_from_string:
288 *
289 * gtkbuilder.c
290 *
291 * Copyright (C) 1998-2002 James Henstridge <james@daa.com.au>
292 * Copyright (C) 2006-2007 Async Open Source,
293 * Johan Dahlin <jdahlin@async.com.br>,
294 * Henrique Romano <henrique@async.com.br>
295 *
296 * This library is free software; you can redistribute it and/or
297 * modify it under the terms of the GNU Library General Public
298 * License as published by the Free Software Foundation; either
299 * version 2.1 of the License, or (at your option) any later version.
300 *
301 * This library is distributed in the hope that it will be useful,
302 * but WITHOUT ANY WARRANTY; without even the implied warranty of
303 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
304 * Library General Public License for more details.
305 *
306 * You should have received a copy of the GNU Library General Public
307 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
308 */
309 gboolean
flags_from_string(GType type,const gchar * string,guint * flags_value,GError ** error)310 flags_from_string (GType type,
311 const gchar *string,
312 guint *flags_value,
313 GError **error)
314 {
315 GFlagsClass *fclass;
316 gchar *endptr, *prevptr;
317 guint i, j, value;
318 gchar *flagstr;
319 GFlagsValue *fv;
320 const gchar *flag;
321 gunichar ch;
322 gboolean eos, ret;
323
324 g_return_val_if_fail (G_TYPE_IS_FLAGS (type), FALSE);
325 g_return_val_if_fail (string != 0, FALSE);
326
327 ret = TRUE;
328
329 endptr = NULL;
330 errno = 0;
331 value = g_ascii_strtoull (string, &endptr, 0);
332 if (errno == 0 && endptr != string) /* parsed a number */
333 *flags_value = value;
334 else
335 {
336 fclass = g_type_class_ref (type);
337
338 flagstr = g_strdup (string);
339 for (value = i = j = 0; ; i++)
340 {
341
342 eos = flagstr[i] == '\0';
343
344 if (!eos && flagstr[i] != '|')
345 continue;
346
347 flag = &flagstr[j];
348 endptr = &flagstr[i];
349
350 if (!eos)
351 {
352 flagstr[i++] = '\0';
353 j = i;
354 }
355
356 /* trim spaces */
357 for (;;)
358 {
359 ch = g_utf8_get_char (flag);
360 if (!g_unichar_isspace (ch))
361 break;
362 flag = g_utf8_next_char (flag);
363 }
364
365 while (endptr > flag)
366 {
367 prevptr = g_utf8_prev_char (endptr);
368 ch = g_utf8_get_char (prevptr);
369 if (!g_unichar_isspace (ch))
370 break;
371 endptr = prevptr;
372 }
373
374 if (endptr > flag)
375 {
376 *endptr = '\0';
377 fv = g_flags_get_value_by_name (fclass, flag);
378
379 if (!fv)
380 fv = g_flags_get_value_by_nick (fclass, flag);
381
382 if (fv)
383 value |= fv->value;
384 else
385 {
386 g_set_error (error,
387 GTK_BUILDER_ERROR,
388 GTK_BUILDER_ERROR_INVALID_VALUE,
389 "Unknown flag: `%s'",
390 flag);
391 ret = FALSE;
392 break;
393 }
394 }
395
396 if (eos)
397 {
398 *flags_value = value;
399 break;
400 }
401 }
402
403 g_free (flagstr);
404
405 g_type_class_unref (fclass);
406 }
407
408 return ret;
409 }
410
411 static void
states_parser_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)412 states_parser_start_element (GMarkupParseContext *context,
413 const gchar *element_name,
414 const gchar **attribute_names,
415 const gchar **attribute_values,
416 gpointer user_data,
417 GError **error)
418 {
419 StatesParserData *parser_data = user_data;
420 StackItem *item;
421
422 g_assert (context != NULL);
423 g_assert (element_name != NULL);
424 g_assert (parser_data != NULL);
425
426 if (g_strcmp0 (element_name, "state") == 0)
427 {
428 const gchar *name;
429
430 if (!check_parent (context, "states", error))
431 return;
432
433 if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error,
434 G_MARKUP_COLLECT_STRING, "name", &name,
435 G_MARKUP_COLLECT_INVALID))
436 return;
437
438 item = stack_item_new (STACK_ITEM_STATE);
439 item->u.state.name = g_strdup (name);
440 g_queue_push_head (parser_data->stack, item);
441 }
442 else if (g_strcmp0 (element_name, "states") == 0)
443 {
444 if (!check_parent (context, "object", error))
445 return;
446 }
447 else if (g_strcmp0 (element_name, "object") == 0)
448 {
449 const gchar *id;
450
451 if (!check_parent (context, "state", error))
452 return;
453
454 if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error,
455 G_MARKUP_COLLECT_STRING, "id", &id,
456 G_MARKUP_COLLECT_INVALID))
457 return;
458
459 item = stack_item_new (STACK_ITEM_OBJECT);
460 item->u.object.id = g_strdup (id);
461 g_queue_push_head (parser_data->stack, item);
462 }
463 else if (g_strcmp0 (element_name, "property") == 0)
464 {
465 const gchar *name = NULL;
466 const gchar *translatable = NULL;
467 const gchar *bind_source = NULL;
468 const gchar *bind_property = NULL;
469 const gchar *bind_flags_str = NULL;
470 GBindingFlags bind_flags = 0;
471
472 if (!check_parent (context, "object", error))
473 return;
474
475 if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error,
476 G_MARKUP_COLLECT_STRING, "name", &name,
477 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "translatable", &translatable,
478 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-source", &bind_source,
479 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-property", &bind_property,
480 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-flags", &bind_flags_str,
481 G_MARKUP_COLLECT_INVALID))
482 return;
483
484 if (name != NULL)
485 {
486 if (g_strcmp0 (translatable, "yes") == 0)
487 {
488 const gchar *domain;
489
490 domain = gtk_builder_get_translation_domain (parser_data->builder);
491 name = dgettext (domain, name);
492 }
493 }
494
495 if ((bind_flags_str != NULL) && !flags_from_string (G_TYPE_BINDING_FLAGS, bind_flags_str, &bind_flags, error))
496 return;
497
498 item = stack_item_new (STACK_ITEM_PROPERTY);
499 item->u.property.name = g_strdup (name);
500 item->u.property.bind_source = g_strdup (bind_source);
501 item->u.property.bind_property = g_strdup (bind_property);
502 item->u.property.bind_flags = bind_flags;
503 g_queue_push_head (parser_data->stack, item);
504 }
505 else if (g_strcmp0 (element_name, "style") == 0)
506 {
507 if (!check_parent (context, "object", error))
508 return;
509 }
510 else if (g_strcmp0 (element_name, "class") == 0)
511 {
512 const gchar *name = NULL;
513
514 if (!check_parent (context, "style", error))
515 return;
516
517 if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error,
518 G_MARKUP_COLLECT_STRING, "name", &name,
519 G_MARKUP_COLLECT_INVALID))
520 return;
521
522 item = g_queue_peek_head (parser_data->stack);
523 g_assert (item->type == STACK_ITEM_OBJECT);
524
525 item->u.object.classes = g_slist_prepend (item->u.object.classes, g_strdup (name));
526 }
527 else
528 {
529 const GSList *stack;
530 const gchar *parent_name;
531 const gchar *our_name;
532 gint line;
533 gint col;
534
535 stack = g_markup_parse_context_get_element_stack (context);
536 our_name = stack->data;
537 parent_name = stack->next ? stack->next->data : "";
538
539 g_markup_parse_context_get_position (context, &line, &col);
540 g_set_error (error,
541 GTK_BUILDER_ERROR,
542 GTK_BUILDER_ERROR_INVALID_TAG,
543 "%d:%d: Unknown element <%s> found in <%s>.",
544 line, col, our_name, parent_name);
545 }
546
547 return;
548 }
549
550 static void
states_parser_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)551 states_parser_end_element (GMarkupParseContext *context,
552 const gchar *element_name,
553 gpointer user_data,
554 GError **error)
555 {
556 StatesParserData *parser_data = user_data;
557 StackItem *item;
558
559 g_assert (context != NULL);
560 g_assert (element_name != NULL);
561 g_assert (parser_data != NULL);
562
563 if (g_strcmp0 (element_name, "state") == 0)
564 {
565 item = g_queue_pop_head (parser_data->stack);
566 g_assert (item->type == STACK_ITEM_STATE);
567 add_state (parser_data, item, error);
568 stack_item_free (item);
569 }
570 else if (g_strcmp0 (element_name, "object") == 0)
571 {
572 StackItem *parent;
573
574 item = g_queue_pop_head (parser_data->stack);
575 g_assert (item->type == STACK_ITEM_OBJECT);
576
577 parent = g_queue_peek_head (parser_data->stack);
578 g_assert (parent->type == STACK_ITEM_STATE);
579
580 add_object (parser_data, parent, item);
581 }
582 else if (g_strcmp0 (element_name, "property") == 0)
583 {
584 StackItem *parent;
585
586 item = g_queue_pop_head (parser_data->stack);
587 g_assert (item->type == STACK_ITEM_PROPERTY);
588
589 parent = g_queue_peek_head (parser_data->stack);
590 g_assert (parent->type == STACK_ITEM_OBJECT);
591
592 add_property (parser_data, parent, item);
593 }
594 }
595
596 static void
states_parser_text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)597 states_parser_text (GMarkupParseContext *context,
598 const gchar *text,
599 gsize text_len,
600 gpointer user_data,
601 GError **error)
602 {
603 StatesParserData *parser_data = user_data;
604 StackItem *item;
605
606 g_assert (parser_data != NULL);
607
608 item = g_queue_peek_head (parser_data->stack);
609 if ((item != NULL) && (item->type == STACK_ITEM_PROPERTY))
610 item->u.property.text = g_strndup (text, text_len);
611 }
612
613 static GMarkupParser StatesParser = {
614 states_parser_start_element,
615 states_parser_end_element,
616 states_parser_text,
617 };
618
619 static gboolean
dzl_state_machine_buildable_custom_tag_start(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,GMarkupParser * parser,gpointer * data)620 dzl_state_machine_buildable_custom_tag_start (GtkBuildable *buildable,
621 GtkBuilder *builder,
622 GObject *child,
623 const gchar *tagname,
624 GMarkupParser *parser,
625 gpointer *data)
626 {
627 DzlStateMachine *self = (DzlStateMachine *)buildable;
628
629 g_assert (DZL_IS_STATE_MACHINE (self));
630 g_assert (GTK_IS_BUILDER (builder));
631 g_assert (tagname != NULL);
632 g_assert (parser != NULL);
633 g_assert (data != NULL);
634
635 if (g_strcmp0 (tagname, "states") == 0)
636 {
637 StatesParserData *parser_data;
638
639 parser_data = g_slice_new0 (StatesParserData);
640 parser_data->self = g_object_ref (DZL_STATE_MACHINE (buildable));
641 parser_data->builder = g_object_ref (builder);
642 parser_data->stack = g_queue_new ();
643
644 *parser = StatesParser;
645 *data = parser_data;
646
647 return TRUE;
648 }
649
650 return FALSE;
651 }
652
653 static void
dzl_state_machine_buildable_custom_finished(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,gpointer user_data)654 dzl_state_machine_buildable_custom_finished (GtkBuildable *buildable,
655 GtkBuilder *builder,
656 GObject *child,
657 const gchar *tagname,
658 gpointer user_data)
659 {
660 DzlStateMachine *self = (DzlStateMachine *)buildable;
661
662 g_assert (DZL_IS_STATE_MACHINE (self));
663 g_assert (GTK_IS_BUILDER (builder));
664 g_assert (tagname != NULL);
665
666 if (g_strcmp0 (tagname, "states") == 0)
667 {
668 StatesParserData *parser_data = user_data;
669
670 g_object_unref (parser_data->self);
671 g_object_unref (parser_data->builder);
672 g_queue_free_full (parser_data->stack, (GDestroyNotify)stack_item_free);
673 g_slice_free (StatesParserData, parser_data);
674 }
675 }
676
677 /**
678 * dzl_state_machine_buildable_iface_init: (skip)
679 */
680 void
dzl_state_machine_buildable_iface_init(GtkBuildableIface * iface)681 dzl_state_machine_buildable_iface_init (GtkBuildableIface *iface)
682 {
683 g_assert (iface != NULL);
684
685 dzl_state_machine_parent_buildable = g_type_interface_peek_parent (iface);
686
687 iface->custom_tag_start = dzl_state_machine_buildable_custom_tag_start;
688 iface->custom_finished = dzl_state_machine_buildable_custom_finished;
689 }
690