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 | |
29 | struct frame |
30 | { |
31 | GMenu *; |
32 | GMenuItem *item; |
33 | struct frame *prev; |
34 | }; |
35 | |
36 | typedef struct |
37 | { |
38 | ParserData *; |
39 | struct frame ; |
40 | |
41 | /* attributes */ |
42 | char *; |
43 | GVariantType *; |
44 | GString *; |
45 | |
46 | /* translation */ |
47 | char *; |
48 | gboolean ; |
49 | } ; |
50 | |
51 | static void |
52 | (GtkBuilderMenuState *state, |
53 | GMenu *, |
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 | |
66 | static void |
67 | (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 | |
83 | static void |
84 | (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 *; |
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 *; |
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 *; |
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 | |
234 | static void |
235 | (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 | |
301 | static void |
302 | (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 | |
325 | static void |
326 | (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 | |
353 | static GtkBuildableParser = |
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 | |
361 | void |
362 | (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: >k_builder_menu_subparser, user_data: state); |
374 | |
375 | if (COLLECT (STRING, "id" , &id)) |
376 | { |
377 | GMenu *; |
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 | |
386 | void |
387 | (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 | |