]> Pileus Git - ~andy/gtk/blob - gtk/gtkbuilder-menus.c
colorsel: include gtkcolorutils.h
[~andy/gtk] / gtk / gtkbuilder-menus.c
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, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17  * USA.
18  *
19  * Author: Ryan Lortie <desrt@desrt.ca>
20  */
21
22 #include "config.h"
23
24 #include "gtkbuilderprivate.h"
25 #include "gtkintl.h"
26
27 #include <gio/gio.h>
28 #include <string.h>
29
30 struct frame
31 {
32   GMenu        *menu;
33   GMenuItem    *item;
34   struct frame *prev;
35 };
36
37 typedef struct
38 {
39   ParserData *parser_data;
40   struct frame frame;
41
42   /* attributes */
43   gchar        *attribute;
44   GVariantType *type;
45   GString      *string;
46
47   /* translation */
48   gchar        *context;
49   gboolean      translatable;
50 } GtkBuilderMenuState;
51
52 static void
53 gtk_builder_menu_push_frame (GtkBuilderMenuState *state,
54                              GMenu               *menu,
55                              GMenuItem           *item)
56 {
57   struct frame *new;
58
59   new = g_slice_new (struct frame);
60   *new = state->frame;
61
62   state->frame.menu = menu;
63   state->frame.item = item;
64   state->frame.prev = new;
65 }
66
67 static void
68 gtk_builder_menu_pop_frame (GtkBuilderMenuState *state)
69 {
70   struct frame *prev = state->frame.prev;
71
72   if (state->frame.item)
73     {
74       g_assert (prev->menu != NULL);
75       g_menu_append_item (prev->menu, state->frame.item);
76       g_object_unref (state->frame.item);
77     }
78
79   state->frame = *prev;
80
81   g_slice_free (struct frame, prev);
82 }
83
84 static void
85 gtk_builder_menu_start_element (GMarkupParseContext  *context,
86                                 const gchar          *element_name,
87                                 const gchar         **attribute_names,
88                                 const gchar         **attribute_values,
89                                 gpointer              user_data,
90                                 GError              **error)
91 {
92   GtkBuilderMenuState *state = user_data;
93
94 #define COLLECT(first, ...) \
95   g_markup_collect_attributes (element_name,                                 \
96                                attribute_names, attribute_values, error,     \
97                                first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID)
98 #define OPTIONAL   G_MARKUP_COLLECT_OPTIONAL
99 #define BOOLEAN    G_MARKUP_COLLECT_BOOLEAN
100 #define STRING     G_MARKUP_COLLECT_STRING
101
102   if (state->frame.menu)
103     {
104       /* Can have '<item>', '<submenu>' or '<section>' here. */
105       if (g_str_equal (element_name, "item"))
106         {
107           GMenuItem *item;
108
109           if (COLLECT (G_MARKUP_COLLECT_INVALID, NULL))
110             {
111               item = g_menu_item_new (NULL, NULL);
112               gtk_builder_menu_push_frame (state, NULL, item);
113             }
114
115           return;
116         }
117
118       else if (g_str_equal (element_name, "submenu"))
119         {
120           const gchar *id;
121
122           if (COLLECT (STRING | OPTIONAL, "id", &id))
123             {
124               GMenuItem *item;
125               GMenu *menu;
126
127               menu = g_menu_new ();
128               item = g_menu_item_new_submenu (NULL, G_MENU_MODEL (menu));
129               gtk_builder_menu_push_frame (state, menu, item);
130
131               if (id != NULL)
132                 _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
133             }
134
135           return;
136         }
137
138       else if (g_str_equal (element_name, "section"))
139         {
140           const gchar *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 (state->parser_data->builder, id, G_OBJECT (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 *context;
167
168           if (COLLECT (STRING,             "name", &name,
169                        OPTIONAL | BOOLEAN, "translatable", &state->translatable,
170                        OPTIONAL | STRING,  "context", &context,
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 (context);
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             }
210
211           return;
212         }
213     }
214
215   {
216     const GSList *element_stack;
217
218     element_stack = g_markup_parse_context_get_element_stack (context);
219
220     if (element_stack->next)
221       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
222                    _("Element <%s> not allowed inside <%s>"),
223                    element_name, (const gchar *) element_stack->next->data);
224
225     else
226       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
227                    _("Element <%s> not allowed at toplevel"), element_name);
228   }
229 }
230
231 static void
232 gtk_builder_menu_end_element (GMarkupParseContext  *context,
233                               const gchar          *element_name,
234                               gpointer              user_data,
235                               GError              **error)
236 {
237   GtkBuilderMenuState *state = user_data;
238
239   gtk_builder_menu_pop_frame (state);
240
241   if (state->string)
242     {
243       GVariant *value;
244       gchar *text;
245
246       text = g_string_free (state->string, FALSE);
247       state->string = NULL;
248
249       /* do the translation if necessary */
250       if (state->translatable)
251         {
252           const gchar *translated;
253
254           if (state->context)
255             translated = g_dpgettext2 (state->parser_data->domain, state->context, text);
256           else
257             translated = g_dgettext (state->parser_data->domain, text);
258
259          if (translated != text)
260            {
261              /* it's safe because we know that translated != text */
262              g_free (text);
263              text = g_strdup (translated);
264            }
265         }
266
267       if (state->type == NULL)
268         /* No type string specified -> it's a normal string. */
269         g_menu_item_set_attribute (state->frame.item, state->attribute, "s", text);
270
271       /* Else, we try to parse it according to the type string.  If
272        * error is set here, it will follow us out, ending the parse.
273        *
274        * We still need to free everything, though, so ignore it here.
275        */
276       else if ((value = g_variant_parse (state->type, text, NULL, NULL, error)))
277         {
278           g_menu_item_set_attribute_value (state->frame.item, state->attribute, value);
279           g_variant_unref (value);
280         }
281
282       if (state->type)
283         {
284           g_variant_type_free (state->type);
285           state->type = NULL;
286         }
287
288       g_free (state->context);
289       state->context = NULL;
290
291       g_free (state->attribute);
292       state->attribute = NULL;
293
294       g_free (text);
295     }
296 }
297
298 static void
299 gtk_builder_menu_text (GMarkupParseContext  *context,
300                        const gchar          *text,
301                        gsize                 text_len,
302                        gpointer              user_data,
303                        GError              **error)
304 {
305   GtkBuilderMenuState *state = user_data;
306   gint i;
307
308   for (i = 0; i < text_len; i++)
309     if (!g_ascii_isspace (text[i]))
310       {
311         if (state->string)
312           g_string_append_len (state->string, text, text_len);
313
314         else
315           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
316                        _("text may not appear inside <%s>"),
317                        g_markup_parse_context_get_element (context));
318         break;
319       }
320 }
321
322 static void
323 gtk_builder_menu_error (GMarkupParseContext *context,
324                         GError              *error,
325                         gpointer             user_data)
326 {
327   GtkBuilderMenuState *state = user_data;
328
329   while (state->frame.prev)
330     {
331       struct frame *prev = state->frame.prev;
332
333       state->frame = *prev;
334
335       g_slice_free (struct frame, prev);
336     }
337
338   if (state->string)
339     g_string_free (state->string, TRUE);
340
341   if (state->type)
342     g_variant_type_free (state->type);
343
344   g_free (state->attribute);
345   g_free (state->context);
346
347   g_slice_free (GtkBuilderMenuState, state);
348 }
349
350 static GMarkupParser gtk_builder_menu_subparser =
351 {
352   gtk_builder_menu_start_element,
353   gtk_builder_menu_end_element,
354   gtk_builder_menu_text,
355   NULL,                            /* passthrough */
356   gtk_builder_menu_error
357 };
358
359 void
360 _gtk_builder_menu_start (ParserData   *parser_data,
361                          const gchar  *element_name,
362                          const gchar **attribute_names,
363                          const gchar **attribute_values,
364                          GError      **error)
365 {
366   GtkBuilderMenuState *state;
367   gchar *id;
368
369   state = g_slice_new0 (GtkBuilderMenuState);
370   state->parser_data = parser_data;
371   g_markup_parse_context_push (parser_data->ctx, &gtk_builder_menu_subparser, state);
372
373   if (COLLECT (STRING, "id", &id))
374     {
375       GMenu *menu;
376
377       menu = g_menu_new ();
378       _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
379       gtk_builder_menu_push_frame (state, menu, NULL);
380     }
381 }
382
383 void
384 _gtk_builder_menu_end (ParserData *parser_data)
385 {
386   GtkBuilderMenuState *state;
387
388   state = g_markup_parse_context_pop (parser_data->ctx);
389   gtk_builder_menu_pop_frame (state);
390
391   g_assert (state->frame.prev == NULL);
392   g_assert (state->frame.item == NULL);
393   g_assert (state->frame.menu == NULL);
394   g_slice_free (GtkBuilderMenuState, state);
395 }