<interface>
<menu id="appmenu">
<section>
- <item label="_New" action="app.new" accel="<Primary>n"/>
- <item label="_Open" action="app.open"/>
- <item label="_Save" action="app.save" accel="<Primary>s"/>
- <item label="Save _As..." action="app.save-as" accel="<Primary>s"/>
+ <item>
+ <attribute name="label" translatable="yes">_New</attribute>
+ <attribute name="action">app.new</attribute>
+ <attribute name="accel"><Primary>n</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Open</attribute>
+ <attribute name="action">app.open</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Save</attribute>
+ <attribute name="action">app.save</attribute>
+ <attribute name="accel"><Primary>s</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Save _As...</attribute>
+ <attribute name="action">app.save-as</attribute>
+ <attribute name="accel"><Primary>s</attribute>
+ </item>
</section>
<section>
- <item label="_Quit" action="app.quit" accel="<Primary>q"/>
+ <item>
+ <attribute name="label" translatable="yes">_Quit</attribute>
+ <attribute name="action">app.quit</attribute>
+ <attribute name="accel"><Primary>q</attribute>
+ </item>
</section>
</menu>
<menu id="menubar">
- <submenu label="_Preferences">
+ <submenu>
+ <attribute name="label" translatable="yes">_Preferences</attribute>
<section>
- <item label="_Prefer Dark Theme" action="app.dark"/>
- <item label="_Hide Titlebar when maximized" action="win.titlebar"/>
- <submenu label="_Color">
+ <item>
+ <attribute name="label" translatable="yes">_Prefer Dark Theme</attribute>
+ <attribute name="action">app.dark</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Hide Titlebar when maximized</attribute>
+ <attribute name="action">win.titlebar</attribute>
+ </item>
+ <submenu>
+ <attribute name="label" translatable="yes">_Color</attribute>
<section>
- <item label="_Red" action="app.color" target="red" accel="<Primary>r"/>
- <item label="_Green" action="app.color" target="green" accel="<Primary>g"/>
- <item label="_Blue" action="app.color" target="blue" accel="<Primary>b"/>
+ <item>
+ <attribute name="label" translatable="yes">_Red</attribute>
+ <attribute name="action">app.color</attribute>
+ <attribute name="target">red</attribute>
+ <attribute name="accel"><Primary>r</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Green</attribute>
+ <attribute name="action">app.color</attribute>
+ <attribute name="target">green</attribute>
+ <attribute name="accel"><Primary>g</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Blue</attribute>
+ <attribute name="action">app.color</attribute>
+ <attribute name="target">blue</attribute>
+ <attribute name="accel"><Primary>b</attribute>
+ </item>
</section>
</submenu>
- <submenu label="_Shape">
+ <submenu>
+ <attribute name="label" translatable="yes">_Shape</attribute>
<section>
- <item label="_Square" action="win.shape" target="square" accel="<Primary>s"/>
- <item label="_Rectangle" action="win.shape" target="rectangle" accel="<Primary>r"/>
- <item label="_Oval" action="win.shape" target="oval" accel="<Primary>o"/>
+ <item>
+ <attribute name="label" translatable="yes">_Square</attribute>
+ <attribute name="action">win.shape</attribute>
+ <attribute name="target">square</attribute>
+ <attribute name="accel"><Primary>s</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Rectangle</attribute>
+ <attribute name="action">win.shape</attribute>
+ <attribute name="target">rectangle</attribute>
+ <attribute name="accel"><Primary>r</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Oval</attribute>
+ <attribute name="action">win.shape</attribute>
+ <attribute name="target">oval</attribute>
+ <attribute name="accel"><Primary>o</attribute>
+ </item>
</section>
</submenu>
- <item label="_Bold" action="win.bold" accel="<Primary>b"/>
+ <item>
+ <attribute name="label" translatable="yes">_Bold</attribute>
+ <attribute name="action">win.bold</attribute>
+ <attribute name="accel"><Primary>b</attribute>
+ </item>
</section>
</submenu>
- <submenu label="_Help">
- <item label="_About" action="win.about" accel="<Primary>a"/>
+ <submenu>
+ <attribute name="label" translatable="yes">_Help</attribute>
+ <item>
+ <attribute name="label" translatable="yes">_About</attribute>
+ <attribute name="action">win.about</attribute>
+ <attribute name="accel"><Primary>a</attribute>
+ </item>
</submenu>
</menu>
</interface>
--- /dev/null
+/*
+ * 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 <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#include "gtkbuilderprivate.h"
+#include "gtkintl.h"
+
+#include <gio/gio.h>
+#include <string.h>
+
+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 '<item>', '<submenu>' or '<section>' 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 '<attribute>' or '<link>' 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);
+}
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,
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
}
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)
{