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__ */