1 /*
2  * Copyright © 2011, 2012 Canonical Ltd.
3  *
4  * This library is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * licence, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Author: Ryan Lortie <desrt@desrt.ca>
18  */
19 
20 #include "config.h"
21 
22 #include "gtkbuilderprivate.h"
23 #include "gtkintl.h"
24 
25 #include <gio/gio.h>
26 #include <string.h>
27 
28 struct frame
29 {
30   GMenu        *menu;
31   GMenuItem    *item;
32   struct frame *prev;
33 };
34 
35 typedef struct
36 {
37   ParserData *parser_data;
38   struct frame frame;
39 
40   /* attributes */
41   gchar        *attribute;
42   GVariantType *type;
43   GString      *string;
44 
45   /* translation */
46   gchar        *context;
47   gboolean      translatable;
48 } GtkBuilderMenuState;
49 
50 static void
gtk_builder_menu_push_frame(GtkBuilderMenuState * state,GMenu * menu,GMenuItem * item)51 gtk_builder_menu_push_frame (GtkBuilderMenuState *state,
52                              GMenu               *menu,
53                              GMenuItem           *item)
54 {
55   struct frame *new;
56 
57   new = g_slice_new (struct frame);
58   *new = state->frame;
59 
60   state->frame.menu = menu;
61   state->frame.item = item;
62   state->frame.prev = new;
63 }
64 
65 static void
gtk_builder_menu_pop_frame(GtkBuilderMenuState * state)66 gtk_builder_menu_pop_frame (GtkBuilderMenuState *state)
67 {
68   struct frame *prev = state->frame.prev;
69 
70   if (state->frame.item)
71     {
72       g_assert (prev->menu != NULL);
73       g_menu_append_item (prev->menu, state->frame.item);
74       g_object_unref (state->frame.item);
75     }
76 
77   state->frame = *prev;
78 
79   g_slice_free (struct frame, prev);
80 }
81 
82 static void
gtk_builder_menu_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)83 gtk_builder_menu_start_element (GMarkupParseContext  *context,
84                                 const gchar          *element_name,
85                                 const gchar         **attribute_names,
86                                 const gchar         **attribute_values,
87                                 gpointer              user_data,
88                                 GError              **error)
89 {
90   GtkBuilderMenuState *state = user_data;
91 
92 #define COLLECT(first, ...) \
93   g_markup_collect_attributes (element_name,                                 \
94                                attribute_names, attribute_values, error,     \
95                                first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID)
96 #define OPTIONAL   G_MARKUP_COLLECT_OPTIONAL
97 #define BOOLEAN    G_MARKUP_COLLECT_BOOLEAN
98 #define STRING     G_MARKUP_COLLECT_STRING
99 
100   if (state->frame.menu)
101     {
102       /* Can have '<item>', '<submenu>' or '<section>' here. */
103       if (g_str_equal (element_name, "item"))
104         {
105           GMenuItem *item;
106 
107           if (COLLECT (G_MARKUP_COLLECT_INVALID, NULL))
108             {
109               item = g_menu_item_new (NULL, NULL);
110               gtk_builder_menu_push_frame (state, NULL, item);
111             }
112 
113           return;
114         }
115 
116       else if (g_str_equal (element_name, "submenu"))
117         {
118           const gchar *id;
119 
120           if (COLLECT (STRING | OPTIONAL, "id", &id))
121             {
122               GMenuItem *item;
123               GMenu *menu;
124 
125               menu = g_menu_new ();
126               item = g_menu_item_new_submenu (NULL, G_MENU_MODEL (menu));
127               gtk_builder_menu_push_frame (state, menu, item);
128 
129               if (id != NULL)
130                 _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
131               g_object_unref (menu);
132             }
133 
134           return;
135         }
136 
137       else if (g_str_equal (element_name, "section"))
138         {
139           const gchar *id;
140 
141           if (COLLECT (STRING | OPTIONAL, "id", &id))
142             {
143               GMenuItem *item;
144               GMenu *menu;
145 
146               menu = g_menu_new ();
147               item = g_menu_item_new_section (NULL, G_MENU_MODEL (menu));
148               gtk_builder_menu_push_frame (state, menu, item);
149 
150               if (id != NULL)
151                 _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
152               g_object_unref (menu);
153             }
154 
155           return;
156         }
157     }
158 
159   if (state->frame.item)
160     {
161       /* Can have '<attribute>' or '<link>' here. */
162       if (g_str_equal (element_name, "attribute"))
163         {
164           const gchar *typestr;
165           const gchar *name;
166           const gchar *ctxt;
167 
168           if (COLLECT (STRING,             "name", &name,
169                        OPTIONAL | BOOLEAN, "translatable", &state->translatable,
170                        OPTIONAL | STRING,  "context", &ctxt,
171                        OPTIONAL | STRING,  "comments", NULL, /* ignore, just for translators */
172                        OPTIONAL | STRING,  "type", &typestr))
173             {
174               if (typestr && !g_variant_type_string_is_valid (typestr))
175                 {
176                   g_set_error (error, G_VARIANT_PARSE_ERROR,
177                                G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING,
178                                "Invalid GVariant type string '%s'", typestr);
179                   return;
180                 }
181 
182               state->type = typestr ? g_variant_type_new (typestr) : NULL;
183               state->string = g_string_new (NULL);
184               state->attribute = g_strdup (name);
185               state->context = g_strdup (ctxt);
186 
187               gtk_builder_menu_push_frame (state, NULL, NULL);
188             }
189 
190           return;
191         }
192 
193       if (g_str_equal (element_name, "link"))
194         {
195           const gchar *name;
196           const gchar *id;
197 
198           if (COLLECT (STRING,            "name", &name,
199                        STRING | OPTIONAL, "id",   &id))
200             {
201               GMenu *menu;
202 
203               menu = g_menu_new ();
204               g_menu_item_set_link (state->frame.item, name, G_MENU_MODEL (menu));
205               gtk_builder_menu_push_frame (state, menu, NULL);
206 
207               if (id != NULL)
208                 _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
209               g_object_unref (menu);
210             }
211 
212           return;
213         }
214     }
215 
216   {
217     const GSList *element_stack;
218 
219     element_stack = g_markup_parse_context_get_element_stack (context);
220 
221     if (element_stack->next)
222       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
223                    _("Element <%s> not allowed inside <%s>"),
224                    element_name, (const gchar *) element_stack->next->data);
225 
226     else
227       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
228                    _("Element <%s> not allowed at toplevel"), element_name);
229   }
230 }
231 
232 static void
gtk_builder_menu_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)233 gtk_builder_menu_end_element (GMarkupParseContext  *context,
234                               const gchar          *element_name,
235                               gpointer              user_data,
236                               GError              **error)
237 {
238   GtkBuilderMenuState *state = user_data;
239 
240   gtk_builder_menu_pop_frame (state);
241 
242   if (state->string)
243     {
244       GVariant *value;
245       gchar *text;
246 
247       text = g_string_free (state->string, FALSE);
248       state->string = NULL;
249 
250       /* do the translation if necessary */
251       if (state->translatable)
252         {
253           const gchar *translated;
254 
255           if (state->context)
256             translated = g_dpgettext2 (state->parser_data->domain, state->context, text);
257           else
258             translated = g_dgettext (state->parser_data->domain, text);
259 
260          if (translated != text)
261            {
262              /* it's safe because we know that translated != text */
263              g_free (text);
264              text = g_strdup (translated);
265            }
266         }
267 
268       if (state->type == NULL)
269         /* No type string specified -> it's a normal string. */
270         g_menu_item_set_attribute (state->frame.item, state->attribute, "s", text);
271 
272       /* Else, we try to parse it according to the type string.  If
273        * error is set here, it will follow us out, ending the parse.
274        *
275        * We still need to free everything, though, so ignore it here.
276        */
277       else if ((value = g_variant_parse (state->type, text, NULL, NULL, error)))
278         {
279           g_menu_item_set_attribute_value (state->frame.item, state->attribute, value);
280           g_variant_unref (value);
281         }
282 
283       if (state->type)
284         {
285           g_variant_type_free (state->type);
286           state->type = NULL;
287         }
288 
289       g_free (state->context);
290       state->context = NULL;
291 
292       g_free (state->attribute);
293       state->attribute = NULL;
294 
295       g_free (text);
296     }
297 }
298 
299 static void
gtk_builder_menu_text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)300 gtk_builder_menu_text (GMarkupParseContext  *context,
301                        const gchar          *text,
302                        gsize                 text_len,
303                        gpointer              user_data,
304                        GError              **error)
305 {
306   GtkBuilderMenuState *state = user_data;
307   gint i;
308 
309   for (i = 0; i < text_len; i++)
310     if (!g_ascii_isspace (text[i]))
311       {
312         if (state->string)
313           g_string_append_len (state->string, text, text_len);
314 
315         else
316           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
317                        _("Text may not appear inside <%s>"),
318                        g_markup_parse_context_get_element (context));
319         break;
320       }
321 }
322 
323 static void
gtk_builder_menu_error(GMarkupParseContext * context,GError * error,gpointer user_data)324 gtk_builder_menu_error (GMarkupParseContext *context,
325                         GError              *error,
326                         gpointer             user_data)
327 {
328   GtkBuilderMenuState *state = user_data;
329 
330   while (state->frame.prev)
331     {
332       struct frame *prev = state->frame.prev;
333 
334       state->frame = *prev;
335 
336       g_slice_free (struct frame, prev);
337     }
338 
339   if (state->string)
340     g_string_free (state->string, TRUE);
341 
342   if (state->type)
343     g_variant_type_free (state->type);
344 
345   g_free (state->attribute);
346   g_free (state->context);
347 
348   g_slice_free (GtkBuilderMenuState, state);
349 }
350 
351 static GMarkupParser gtk_builder_menu_subparser =
352 {
353   gtk_builder_menu_start_element,
354   gtk_builder_menu_end_element,
355   gtk_builder_menu_text,
356   NULL,                            /* passthrough */
357   gtk_builder_menu_error
358 };
359 
360 void
_gtk_builder_menu_start(ParserData * parser_data,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,GError ** error)361 _gtk_builder_menu_start (ParserData   *parser_data,
362                          const gchar  *element_name,
363                          const gchar **attribute_names,
364                          const gchar **attribute_values,
365                          GError      **error)
366 {
367   GtkBuilderMenuState *state;
368   gchar *id;
369 
370   state = g_slice_new0 (GtkBuilderMenuState);
371   state->parser_data = parser_data;
372   g_markup_parse_context_push (parser_data->ctx, &gtk_builder_menu_subparser, state);
373 
374   if (COLLECT (STRING, "id", &id))
375     {
376       GMenu *menu;
377 
378       menu = g_menu_new ();
379       _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
380       gtk_builder_menu_push_frame (state, menu, NULL);
381       g_object_unref (menu);
382     }
383 }
384 
385 void
_gtk_builder_menu_end(ParserData * parser_data)386 _gtk_builder_menu_end (ParserData *parser_data)
387 {
388   GtkBuilderMenuState *state;
389 
390   state = g_markup_parse_context_pop (parser_data->ctx);
391   gtk_builder_menu_pop_frame (state);
392 
393   g_assert (state->frame.prev == NULL);
394   g_assert (state->frame.item == NULL);
395   g_assert (state->frame.menu == NULL);
396   g_slice_free (GtkBuilderMenuState, state);
397 }
398