]> Pileus Git - ~andy/gtk/blob - gtk/gtkbuilder-menus.c
stylecontext: Do invalidation on first resize container
[~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, 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
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
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
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             }
132
133           return;
134         }
135
136       else if (g_str_equal (element_name, "section"))
137         {
138           const gchar *id;
139
140           if (COLLECT (STRING | OPTIONAL, "id", &id))
141             {
142               GMenuItem *item;
143               GMenu *menu;
144
145               menu = g_menu_new ();
146               item = g_menu_item_new_section (NULL, G_MENU_MODEL (menu));
147               gtk_builder_menu_push_frame (state, menu, item);
148
149               if (id != NULL)
150                 _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
151             }
152
153           return;
154         }
155     }
156
157   if (state->frame.item)
158     {
159       /* Can have '<attribute>' or '<link>' here. */
160       if (g_str_equal (element_name, "attribute"))
161         {
162           const gchar *typestr;
163           const gchar *name;
164           const gchar *context;
165
166           if (COLLECT (STRING,             "name", &name,
167                        OPTIONAL | BOOLEAN, "translatable", &state->translatable,
168                        OPTIONAL | STRING,  "context", &context,
169                        OPTIONAL | STRING,  "comments", NULL, /* ignore, just for translators */
170                        OPTIONAL | STRING,  "type", &typestr))
171             {
172               if (typestr && !g_variant_type_string_is_valid (typestr))
173                 {
174                   g_set_error (error, G_VARIANT_PARSE_ERROR,
175                                G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING,
176                                "Invalid GVariant type string '%s'", typestr);
177                   return;
178                 }
179
180               state->type = typestr ? g_variant_type_new (typestr) : NULL;
181               state->string = g_string_new (NULL);
182               state->attribute = g_strdup (name);
183               state->context = g_strdup (context);
184
185               gtk_builder_menu_push_frame (state, NULL, NULL);
186             }
187
188           return;
189         }
190
191       if (g_str_equal (element_name, "link"))
192         {
193           const gchar *name;
194           const gchar *id;
195
196           if (COLLECT (STRING,            "name", &name,
197                        STRING | OPTIONAL, "id",   &id))
198             {
199               GMenu *menu;
200
201               menu = g_menu_new ();
202               g_menu_item_set_link (state->frame.item, name, G_MENU_MODEL (menu));
203               gtk_builder_menu_push_frame (state, menu, NULL);
204
205               if (id != NULL)
206                 _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
207             }
208
209           return;
210         }
211     }
212
213   {
214     const GSList *element_stack;
215
216     element_stack = g_markup_parse_context_get_element_stack (context);
217
218     if (element_stack->next)
219       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
220                    _("Element <%s> not allowed inside <%s>"),
221                    element_name, (const gchar *) element_stack->next->data);
222
223     else
224       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
225                    _("Element <%s> not allowed at toplevel"), element_name);
226   }
227 }
228
229 static void
230 gtk_builder_menu_end_element (GMarkupParseContext  *context,
231                               const gchar          *element_name,
232                               gpointer              user_data,
233                               GError              **error)
234 {
235   GtkBuilderMenuState *state = user_data;
236
237   gtk_builder_menu_pop_frame (state);
238
239   if (state->string)
240     {
241       GVariant *value;
242       gchar *text;
243
244       text = g_string_free (state->string, FALSE);
245       state->string = NULL;
246
247       /* do the translation if necessary */
248       if (state->translatable)
249         {
250           const gchar *translated;
251
252           if (state->context)
253             translated = g_dpgettext2 (state->parser_data->domain, state->context, text);
254           else
255             translated = g_dgettext (state->parser_data->domain, text);
256
257          if (translated != text)
258            {
259              /* it's safe because we know that translated != text */
260              g_free (text);
261              text = g_strdup (translated);
262            }
263         }
264
265       if (state->type == NULL)
266         /* No type string specified -> it's a normal string. */
267         g_menu_item_set_attribute (state->frame.item, state->attribute, "s", text);
268
269       /* Else, we try to parse it according to the type string.  If
270        * error is set here, it will follow us out, ending the parse.
271        *
272        * We still need to free everything, though, so ignore it here.
273        */
274       else if ((value = g_variant_parse (state->type, text, NULL, NULL, error)))
275         {
276           g_menu_item_set_attribute_value (state->frame.item, state->attribute, value);
277           g_variant_unref (value);
278         }
279
280       if (state->type)
281         {
282           g_variant_type_free (state->type);
283           state->type = NULL;
284         }
285
286       g_free (state->context);
287       state->context = NULL;
288
289       g_free (state->attribute);
290       state->attribute = NULL;
291
292       g_free (text);
293     }
294 }
295
296 static void
297 gtk_builder_menu_text (GMarkupParseContext  *context,
298                        const gchar          *text,
299                        gsize                 text_len,
300                        gpointer              user_data,
301                        GError              **error)
302 {
303   GtkBuilderMenuState *state = user_data;
304   gint i;
305
306   for (i = 0; i < text_len; i++)
307     if (!g_ascii_isspace (text[i]))
308       {
309         if (state->string)
310           g_string_append_len (state->string, text, text_len);
311
312         else
313           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
314                        _("text may not appear inside <%s>"),
315                        g_markup_parse_context_get_element (context));
316         break;
317       }
318 }
319
320 static void
321 gtk_builder_menu_error (GMarkupParseContext *context,
322                         GError              *error,
323                         gpointer             user_data)
324 {
325   GtkBuilderMenuState *state = user_data;
326
327   while (state->frame.prev)
328     {
329       struct frame *prev = state->frame.prev;
330
331       state->frame = *prev;
332
333       g_slice_free (struct frame, prev);
334     }
335
336   if (state->string)
337     g_string_free (state->string, TRUE);
338
339   if (state->type)
340     g_variant_type_free (state->type);
341
342   g_free (state->attribute);
343   g_free (state->context);
344
345   g_slice_free (GtkBuilderMenuState, state);
346 }
347
348 static GMarkupParser gtk_builder_menu_subparser =
349 {
350   gtk_builder_menu_start_element,
351   gtk_builder_menu_end_element,
352   gtk_builder_menu_text,
353   NULL,                            /* passthrough */
354   gtk_builder_menu_error
355 };
356
357 void
358 _gtk_builder_menu_start (ParserData   *parser_data,
359                          const gchar  *element_name,
360                          const gchar **attribute_names,
361                          const gchar **attribute_values,
362                          GError      **error)
363 {
364   GtkBuilderMenuState *state;
365   gchar *id;
366
367   state = g_slice_new0 (GtkBuilderMenuState);
368   state->parser_data = parser_data;
369   g_markup_parse_context_push (parser_data->ctx, &gtk_builder_menu_subparser, state);
370
371   if (COLLECT (STRING, "id", &id))
372     {
373       GMenu *menu;
374
375       menu = g_menu_new ();
376       _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
377       gtk_builder_menu_push_frame (state, menu, NULL);
378     }
379 }
380
381 void
382 _gtk_builder_menu_end (ParserData *parser_data)
383 {
384   GtkBuilderMenuState *state;
385
386   state = g_markup_parse_context_pop (parser_data->ctx);
387   gtk_builder_menu_pop_frame (state);
388
389   g_assert (state->frame.prev == NULL);
390   g_assert (state->frame.item == NULL);
391   g_assert (state->frame.menu == NULL);
392   g_slice_free (GtkBuilderMenuState, state);
393 }