From eed307713b8ef4da5e4edf0aa8d85aa5c32a6d4f Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Wed, 25 Jan 2012 18:23:25 -0500 Subject: [PATCH] GtkBuilder: change format of menus Change the format of GtkBuilder to be more in-line with the style of the rest of GtkBuilder so that we can do translation in a consistent way. The format is now substantially more difficult to hand-write, but tools should be along soon. There is an xslt program attached to the bug to help you convert your existing .ui files from the old format to the new one. https://bugzilla.gnome.org/show_bug.cgi?id=668696 --- demos/gtk-demo/application.ui | 8 +- demos/gtk-demo/menus.ui | 105 +++++++-- examples/bloatpad.c | 41 +++- gtk/Makefile.am | 1 + gtk/gtkbuilder-menus.c | 391 ++++++++++++++++++++++++++++++++++ gtk/gtkbuilderparser.c | 48 +---- gtk/gtkbuilderprivate.h | 8 + 7 files changed, 526 insertions(+), 76 deletions(-) create mode 100644 gtk/gtkbuilder-menus.c diff --git a/demos/gtk-demo/application.ui b/demos/gtk-demo/application.ui index b99e5ca0d5..7085134396 100644 --- a/demos/gtk-demo/application.ui +++ b/demos/gtk-demo/application.ui @@ -18,8 +18,7 @@ - - + - + + File1 + win.file1 + diff --git a/demos/gtk-demo/menus.ui b/demos/gtk-demo/menus.ui index 2f1e105f6b..0bbe11a8d9 100644 --- a/demos/gtk-demo/menus.ui +++ b/demos/gtk-demo/menus.ui @@ -2,39 +2,106 @@
- - - - + + _New + app.new + <Primary>n + + + _Open + app.open + + + _Save + app.save + <Primary>s + + + Save _As... + app.save-as + <Primary>s +
- + + _Quit + app.quit + <Primary>q +
- + + _Preferences
- - - + + _Prefer Dark Theme + app.dark + + + _Hide Titlebar when maximized + win.titlebar + + + _Color
- - - + + _Red + app.color + red + <Primary>r + + + _Green + app.color + green + <Primary>g + + + _Blue + app.color + blue + <Primary>b +
- + + _Shape
- - - + + _Square + win.shape + square + <Primary>s + + + _Rectangle + win.shape + rectangle + <Primary>r + + + _Oval + win.shape + oval + <Primary>o +
- + + _Bold + win.bold + <Primary>b +
- - + + _Help + + _About + win.about + <Primary>a +
diff --git a/examples/bloatpad.c b/examples/bloatpad.c index ba8164a708..4178580262 100644 --- a/examples/bloatpad.c +++ b/examples/bloatpad.c @@ -275,25 +275,50 @@ bloat_pad_startup (GApplication *application) "" " " "
" - " " + " " + " _New Window" + " app.new" + " <Primary>n" + " " "
" "
" - " " + " " + " _About Bloatpad" + " app.about" + " " "
" "
" - " " + " " + " _Quit" + " app.quit" + " <Primary>q" + " " "
" "
" " " - " " + " " + " _Edit" "
" - " " - " " + " " + " _Copy" + " win.copy" + " <Primary>c" + " " + " " + " _Parse" + " win.parse" + " <Primary>v" + " " "
" "
" - " " + " " + " _View" "
" - " " + " " + " _Fullscreen" + " win.fullscreen" + " F11" + " " "
" "
" "
" diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 98cdf3d2b0..6d5f926452 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -567,6 +567,7 @@ gtk_base_c_sources = \ gtkbuildable.c \ gtkbuilder.c \ gtkbuilderparser.c \ + gtkbuilder-menus.c \ gtkbutton.c \ gtkcalendar.c \ gtkcellarea.c \ diff --git a/gtk/gtkbuilder-menus.c b/gtk/gtkbuilder-menus.c new file mode 100644 index 0000000000..5a32a4186c --- /dev/null +++ b/gtk/gtkbuilder-menus.c @@ -0,0 +1,391 @@ +/* + * Copyright © 2011, 2012 Canonical Ltd. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + * + * Author: Ryan Lortie + */ + +#include "config.h" + +#include "gtkbuilderprivate.h" +#include "gtkintl.h" + +#include +#include + +struct frame +{ + GMenu *menu; + GMenuItem *item; + struct frame *prev; +}; + +typedef struct +{ + ParserData *parser_data; + struct frame frame; + + /* attributes */ + gchar *attribute; + GVariantType *type; + GString *string; + + /* translation */ + gchar *context; + gboolean translatable; +} GtkBuilderMenuState; + +static void +gtk_builder_menu_push_frame (GtkBuilderMenuState *state, + GMenu *menu, + GMenuItem *item) +{ + struct frame *new; + + new = g_slice_new (struct frame); + *new = state->frame; + + state->frame.menu = menu; + state->frame.item = item; + state->frame.prev = new; +} + +static void +gtk_builder_menu_pop_frame (GtkBuilderMenuState *state) +{ + struct frame *prev = state->frame.prev; + + if (state->frame.item) + { + g_assert (prev->menu != NULL); + g_menu_append_item (prev->menu, state->frame.item); + g_object_unref (state->frame.item); + } + + state->frame = *prev; + + g_slice_free (struct frame, prev); +} + +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) +{ + GtkBuilderMenuState *state = user_data; + +#define COLLECT(first, ...) \ + g_markup_collect_attributes (element_name, \ + attribute_names, attribute_values, error, \ + first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID) +#define OPTIONAL G_MARKUP_COLLECT_OPTIONAL +#define BOOLEAN G_MARKUP_COLLECT_BOOLEAN +#define STRING G_MARKUP_COLLECT_STRING + + if (state->frame.menu) + { + /* Can have '', '' or '
' here. */ + if (g_str_equal (element_name, "item")) + { + GMenuItem *item; + + item = g_menu_item_new (NULL, NULL); + gtk_builder_menu_push_frame (state, NULL, item); + return; + } + + else if (g_str_equal (element_name, "submenu")) + { + const gchar *id; + + if (COLLECT (STRING | OPTIONAL, "id", &id)) + { + GMenuItem *item; + GMenu *menu; + + menu = g_menu_new (); + item = g_menu_item_new_submenu (NULL, G_MENU_MODEL (menu)); + gtk_builder_menu_push_frame (state, menu, item); + + if (id != NULL) + _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu)); + } + + return; + } + + else if (g_str_equal (element_name, "section")) + { + const gchar *id; + + if (COLLECT (STRING | OPTIONAL, "id", &id)) + { + GMenuItem *item; + GMenu *menu; + + menu = g_menu_new (); + item = g_menu_item_new_section (NULL, G_MENU_MODEL (menu)); + gtk_builder_menu_push_frame (state, menu, item); + + if (id != NULL) + _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu)); + } + + return; + } + } + + if (state->frame.item) + { + /* Can have '' or '' here. */ + if (g_str_equal (element_name, "attribute")) + { + const gchar *typestr; + const gchar *name; + const gchar *context; + + if (COLLECT (STRING, "name", &name, + OPTIONAL | BOOLEAN, "translatable", &state->translatable, + OPTIONAL | STRING, "context", &context, + OPTIONAL | STRING, "comments", NULL, /* ignore, just for translators */ + OPTIONAL | STRING, "type", &typestr)) + { + if (typestr && !g_variant_type_string_is_valid (typestr)) + { + g_set_error (error, G_VARIANT_PARSE_ERROR, + G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING, + "Invalid GVariant type string '%s'", typestr); + return; + } + + state->type = typestr ? g_variant_type_new (typestr) : NULL; + state->string = g_string_new (NULL); + state->attribute = g_strdup (name); + state->context = g_strdup (context); + + gtk_builder_menu_push_frame (state, NULL, NULL); + } + + return; + } + + if (g_str_equal (element_name, "link")) + { + const gchar *name; + const gchar *id; + + if (COLLECT (STRING, "name", &name, + STRING | OPTIONAL, "id", &id)) + { + GMenu *menu; + + menu = g_menu_new (); + g_menu_item_set_link (state->frame.item, name, G_MENU_MODEL (menu)); + gtk_builder_menu_push_frame (state, menu, NULL); + + if (id != NULL) + _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu)); + } + + return; + } + } + + { + const GSList *element_stack; + + element_stack = g_markup_parse_context_get_element_stack (context); + + if (element_stack->next) + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + _("Element <%s> not allowed inside <%s>"), + element_name, (const gchar *) element_stack->next->data); + + else + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + _("Element <%s> not allowed at toplevel"), element_name); + } +} + +static void +gtk_builder_menu_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + GtkBuilderMenuState *state = user_data; + + gtk_builder_menu_pop_frame (state); + + if (state->string) + { + GVariant *value; + gchar *text; + + text = g_string_free (state->string, FALSE); + state->string = NULL; + + /* do the translation if necessary */ + if (state->translatable && state->parser_data->domain) + { + const gchar *translated; + + if (state->context) + translated = g_dpgettext2 (state->parser_data->domain, state->context, text); + else + translated = g_dgettext (state->parser_data->domain, text); + + if (translated != text) + { + /* it's safe because we know that translated != text */ + g_free (text); + text = g_strdup (translated); + } + } + + if (state->type == NULL) + /* No type string specified -> it's a normal string. */ + g_menu_item_set_attribute (state->frame.item, state->attribute, "s", text); + + /* Else, we try to parse it according to the type string. If + * error is set here, it will follow us out, ending the parse. + * + * We still need to free everything, though, so ignore it here. + */ + else if ((value = g_variant_parse (state->type, text, NULL, NULL, error))) + { + g_menu_item_set_attribute_value (state->frame.item, state->attribute, value); + g_variant_unref (value); + } + + if (state->type) + { + g_variant_type_free (state->type); + state->type = NULL; + } + + g_free (state->context); + state->context = NULL; + + g_free (state->attribute); + state->attribute = NULL; + + g_free (text); + } +} + +static void +gtk_builder_menu_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + GtkBuilderMenuState *state = user_data; + gint i; + + for (i = 0; i < text_len; i++) + if (!g_ascii_isspace (text[i])) + { + if (state->string) + g_string_append_len (state->string, text, text_len); + + else + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + _("text may not appear inside <%s>"), + g_markup_parse_context_get_element (context)); + break; + } +} + +static void +gtk_builder_menu_error (GMarkupParseContext *context, + GError *error, + gpointer user_data) +{ + GtkBuilderMenuState *state = user_data; + + while (state->frame.prev) + { + struct frame *prev = state->frame.prev; + + state->frame = *prev; + + g_slice_free (struct frame, prev); + } + + if (state->string) + g_string_free (state->string, TRUE); + + if (state->type) + g_variant_type_free (state->type); + + g_free (state->attribute); + g_free (state->context); + + g_slice_free (GtkBuilderMenuState, state); +} + +static GMarkupParser gtk_builder_menu_subparser = +{ + gtk_builder_menu_start_element, + gtk_builder_menu_end_element, + gtk_builder_menu_text, + NULL, /* passthrough */ + gtk_builder_menu_error +}; + +void +_gtk_builder_menu_start (ParserData *parser_data, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error) +{ + GtkBuilderMenuState *state; + gchar *id; + + state = g_slice_new0 (GtkBuilderMenuState); + state->parser_data = parser_data; + g_markup_parse_context_push (parser_data->ctx, >k_builder_menu_subparser, state); + + if (COLLECT (STRING, "id", &id)) + { + GMenu *menu; + + menu = g_menu_new (); + _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu)); + gtk_builder_menu_push_frame (state, menu, NULL); + } +} + +void +_gtk_builder_menu_end (ParserData *parser_data) +{ + GtkBuilderMenuState *state; + + state = g_markup_parse_context_pop (parser_data->ctx); + gtk_builder_menu_pop_frame (state); + + g_assert (state->frame.prev == NULL); + g_assert (state->frame.item == NULL); + g_assert (state->frame.menu == NULL); + g_slice_free (GtkBuilderMenuState, state); +} diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c index 6d36140dc1..09b343cb22 100644 --- a/gtk/gtkbuilderparser.c +++ b/gtk/gtkbuilderparser.c @@ -832,34 +832,6 @@ parse_custom (GMarkupParseContext *context, return TRUE; } -static gboolean -parse_menu (GMarkupParseContext *context, - const gchar *element_name, - const gchar **names, - const gchar **values, - gpointer user_data, - GError **error) -{ - gchar *id; - ParserData *data = user_data; - MenuInfo *menu_info; - - if (!g_markup_collect_attributes (element_name, names, values, error, - G_MARKUP_COLLECT_STRING, "id", &id, - G_MARKUP_COLLECT_INVALID)) - return FALSE; - - menu_info = g_slice_new0 (MenuInfo); - menu_info->tag.name = element_name; - menu_info->id = g_strdup (id); - menu_info->objects = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); - state_push (data, menu_info); - - g_menu_markup_parser_start_menu (context, data->domain, menu_info->objects); - - return TRUE; -} - static void start_element (GMarkupParseContext *context, const gchar *element_name, @@ -921,7 +893,7 @@ start_element (GMarkupParseContext *context, else if (strcmp (element_name, "interface") == 0) parse_interface (data, element_name, names, values, error); else if (strcmp (element_name, "menu") == 0) - parse_menu (context, element_name, names, values, data, error); + _gtk_builder_menu_start (data, element_name, names, values, error); else if (strcmp (element_name, "placeholder") == 0) { /* placeholder has no special treatmeant, but it needs an @@ -995,23 +967,7 @@ end_element (GMarkupParseContext *context, } else if (strcmp (element_name, "menu") == 0) { - MenuInfo *menu_info; - GObject *menu; - GHashTableIter iter; - const gchar *id; - - menu_info = state_pop_info (data, MenuInfo); - menu = (GObject*)g_menu_markup_parser_end_menu (context); - _gtk_builder_add_object (data->builder, menu_info->id, menu); - g_object_unref (menu); - - g_hash_table_iter_init (&iter, menu_info->objects); - while (g_hash_table_iter_next (&iter, (gpointer*)&id, (gpointer*)&menu)) - { - _gtk_builder_add_object (data->builder, id, menu); - } - - free_menu_info (menu_info); + _gtk_builder_menu_end (data); } else if (data->requested_objects && !data->inside_requested_object) { diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h index e7528bcdb1..d4e5a29cea 100644 --- a/gtk/gtkbuilderprivate.h +++ b/gtk/gtkbuilderprivate.h @@ -154,4 +154,12 @@ gchar * _gtk_builder_get_resource_path (GtkBuilder *builder, gchar * _gtk_builder_get_absolute_filename (GtkBuilder *builder, const gchar *string); +void _gtk_builder_menu_start (ParserData *parser_data, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error); +void _gtk_builder_menu_end (ParserData *parser_data); + + #endif /* __GTK_BUILDER_PRIVATE_H__ */