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, >k_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