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

source code of gtk/gtk/gtkbuilder-menus.c