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