[glib/wip/gmenu: 10/59] Add GMenu markup
- From: Ryan Lortie <ryanl src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/gmenu: 10/59] Add GMenu markup
- Date: Fri, 2 Dec 2011 21:31:56 +0000 (UTC)
commit b97b019b3859295bf8eee4620603e753864a4d4f
Author: Matthias Clasen <mclasen redhat com>
Date: Sat Nov 26 22:00:48 2011 -0500
Add GMenu markup
These functions serialize and deserialize a GMenuModel
to and from XML.
docs/reference/gio/gio-docs.xml | 1 +
docs/reference/gio/gio-sections.txt | 10 +
gio/Makefile.am | 5 +
gio/gio.h | 1 +
gio/gio.symbols | 6 +
gio/gmenumarkup.c | 717 +++++++++++++++++++++++++++++++++++
gio/gmenumarkup.h | 47 +++
gio/menumarkup.dtd | 29 ++
gio/menumarkup.xml | 26 ++
gio/menumarkup2.xml | 75 ++++
10 files changed, 917 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml
index 2fd3ef5..a1e6ad2 100644
--- a/docs/reference/gio/gio-docs.xml
+++ b/docs/reference/gio/gio-docs.xml
@@ -202,6 +202,7 @@
<xi:include href="xml/gdbusactiongroup.xml"/>
<xi:include href="xml/gmenumodel.xml"/>
<xi:include href="xml/gmenu.xml"/>
+ <xi:include href="xml/gmenumarkup.xml"/>
</chapter>
<chapter id="extending">
<title>Extending GIO</title>
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index 0d81f50..88bc4e9 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -3679,3 +3679,13 @@ G_MENU_ATTRIBUTE_ITER_CLASS
G_IS_MENU_ATTRIBUTE_ITER_CLASS
G_MENU_ATTRIBUTE_ITER_GET_CLASS
</SECTION>
+
+<SECTION>
+<FILE>gmenumarkup</FILE>
+g_menu_markup_parser_end
+g_menu_markup_parser_end_menu
+g_menu_markup_parser_start
+g_menu_markup_parser_start_menu
+g_menu_markup_print_stderr
+g_menu_markup_print_string
+</SECTION>
diff --git a/gio/Makefile.am b/gio/Makefile.am
index 9f96e3c..d80a97b 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -135,6 +135,7 @@ application_headers = \
gapplication.h \
gmenumodel.h \
gmenu.h \
+ gmenumarkup.h \
$(NULL)
application_sources = \
@@ -150,6 +151,7 @@ application_sources = \
gapplication.c \
gmenumodel.c \
gmenu.c \
+ gmenumarkup.c \
$(NULL)
local_sources = \
@@ -586,6 +588,9 @@ EXTRA_DIST += \
abicheck.sh \
gio.rc.in \
gschema.dtd \
+ menumarkup.xml \
+ menumarkup2.xml \
+ menumarkup.dtd \
$(NULL)
BUILT_EXTRA_DIST = \
diff --git a/gio/gio.h b/gio/gio.h
index 00ef1e9..f379ecc 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -148,6 +148,7 @@
#include <gio/gdbusactiongroup.h>
#include <gio/gmenumodel.h>
#include <gio/gmenu.h>
+#include <gio/gmenumarkup.h>
#undef __GIO_GIO_H_INSIDE__
diff --git a/gio/gio.symbols b/gio/gio.symbols
index f906092..1091455 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1638,6 +1638,12 @@ g_menu_link_iter_get_next
g_menu_link_iter_get_type
g_menu_link_iter_get_value
g_menu_link_iter_next
+g_menu_markup_parser_end
+g_menu_markup_parser_end_menu
+g_menu_markup_parser_start
+g_menu_markup_parser_start_menu
+g_menu_markup_print_stderr
+g_menu_markup_print_string
g_menu_model_get_item_attribute
g_menu_model_get_item_attribute_value
g_menu_model_get_item_link
diff --git a/gio/gmenumarkup.c b/gio/gmenumarkup.c
new file mode 100644
index 0000000..3363fa2
--- /dev/null
+++ b/gio/gmenumarkup.c
@@ -0,0 +1,717 @@
+/*
+ * Copyright  2011 Canonical Ltd.
+ * All rights reserved.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "gmenumarkup.h"
+
+#include <gi18n.h>
+
+/**
+ * SECTION:gmenumarkup
+ * @title: GMenu Markup
+ * @short_description: parsing and printing GMenuModel XML
+ *
+ * The functions here allow to instantiate #GMenuModels by parsing
+ * fragments of an XML document.
+ * * The XML format for #GMenuModel consists of a toplevel
+ * <tag class="starttag">menu</tag> element, which contains one or more
+ * <tag class="starttag">item</tag> elements. Each <tag class="starttag">item</tag>
+ * element contains <tag class="starttag">attribute</tag> and <tag class="starttag">link</tag>
+ * elements with a mandatory name attribute.
+ * <tag class="starttag">link</tag> elements have the same content
+ * model as <tag class="starttag">menu</tag>.
+ *
+ * Here is the XML for <xref linkend="menu-example"/>:
+ * |[<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gio/menumarkup2.xml"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include>]|
+ *
+ * The parser also understands a somewhat less verbose format, in which
+ * attributes are encoded as actual XML attributes of <tag class="starttag">item</tag>
+ * elements, and <tag class="starttag">link</tag> elements are replaced by
+ * <tag class="starttag">section</tag> and <tag class="starttag">submenu</tag> elements.
+ *
+ * Here is how the example looks in this format:
+ * |[<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gio/menumarkup.xml"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include>]|
+ *
+ * The parser can obtaing translations for attribute values using gettext.
+ * To make use of this, the <tag class="starttag">menu</tag> element must
+ * have a domain attribute which specifies the gettext domain to use, and
+ * <tag class="starttag">attribute</tag> elements can be marked for translation
+ * with a <literal>translatable="yes"</literal> attribute. It is also possible
+ * to specify message context and translator comments, using the context
+ * and comments attributes.
+ *
+ * The following DTD describes the XML format approximately:
+ * |[<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gio/menumarkup.dtd"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include>]|
+ *
+ * To serialize a #GMenuModel into an XML fragment, use
+ * g_menu_markup_print_string().
+ */
+
+struct frame
+{
+ GMenu *menu;
+ GMenuItem *item;
+ struct frame *prev;
+};
+
+typedef struct
+{
+ GHashTable *objects;
+ struct frame frame;
+
+ /* attributes */
+ GQuark attribute;
+ GVariantType *type;
+ GString *string;
+
+ /* translation */
+ gchar *domain;
+ gchar *context;
+ gboolean translatable;
+} GMenuMarkupState;
+
+static gboolean
+boolean_from_string (const gchar *str,
+ gboolean *val)
+{
+ if (strcmp (str, "true") == 0 ||
+ strcmp (str, "yes") == 0 ||
+ strcmp (str, "t") == 0 ||
+ strcmp (str, "1") == 0)
+ *val = TRUE;
+ else if (strcmp (str, "false") == 0 ||
+ strcmp (str, "no") == 0 ||
+ strcmp (str, "f") == 0 ||
+ strcmp (str, "0") == 0)
+ *val = FALSE;
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+g_menu_markup_push_frame (GMenuMarkupState *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
+g_menu_markup_pop_frame (GMenuMarkupState *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);
+ }
+
+ state->frame = *prev;
+
+ g_slice_free (struct frame, prev);
+}
+
+static void
+add_string_attributes (GMenuItem *item,
+ const gchar **names,
+ const gchar **values)
+{
+ gint i;
+
+ for (i = 0; names[i]; i++)
+ {
+ g_menu_item_set_attribute (item, names[i], "s", values[i]);
+ }
+}
+
+static void
+g_menu_markup_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ GMenuMarkupState *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 STRDUP G_MARKUP_COLLECT_STRDUP
+#define STRING G_MARKUP_COLLECT_STRING
+#define NO_ATTRS() COLLECT (G_MARKUP_COLLECT_INVALID, NULL)
+
+ if (!(state->frame.menu || state->frame.menu || state->string))
+ {
+ /* Can only have <menu> here. */
+ if (g_str_equal (element_name, "menu"))
+ {
+ gchar *id;
+
+ if (COLLECT (STRDUP, "id", &id))
+ {
+ GMenu *menu;
+
+ menu = g_menu_new ();
+ g_hash_table_insert (state->objects, id, menu);
+ g_menu_markup_push_frame (state, menu, NULL);
+ }
+
+ return;
+ }
+ }
+
+ 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);
+ add_string_attributes (item, attribute_names, attribute_values);
+ g_menu_markup_push_frame (state, NULL, item);
+ return;
+ }
+
+ else if (g_str_equal (element_name, "submenu"))
+ {
+ GMenuItem *item;
+ GMenu *menu;
+
+ menu = g_menu_new ();
+ item = g_menu_item_new_submenu (NULL, G_MENU_MODEL (menu));
+ add_string_attributes (item, attribute_names, attribute_values);
+ g_menu_markup_push_frame (state, menu, item);
+ return;
+ }
+
+ else if (g_str_equal (element_name, "section"))
+ {
+ GMenuItem *item;
+ GMenu *menu;
+
+ menu = g_menu_new ();
+ item = g_menu_item_new_section (NULL, G_MENU_MODEL (menu));
+ add_string_attributes (item, attribute_names, attribute_values);
+ g_menu_markup_push_frame (state, menu, item);
+ 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 *translatable;
+ const gchar *context;
+
+ if (COLLECT (STRING, "name", &name,
+ OPTIONAL | STRING, "translatable", &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_quark_from_string (name);
+ state->context = g_strdup (context);
+ if (!translatable)
+ state->translatable = FALSE;
+ else if (!boolean_from_string (translatable, &state->translatable))
+ {
+ g_set_error (error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Invalid boolean attribute: '%s'", translatable);
+ return;
+ }
+
+ g_menu_markup_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));
+ g_menu_markup_push_frame (state, menu, NULL);
+
+ if (id != NULL)
+ g_hash_table_insert (state->objects, g_strdup (id), g_object_ref (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
+g_menu_markup_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ GMenuMarkupState *state = user_data;
+
+ g_menu_markup_pop_frame (state);
+
+ if (state->string)
+ {
+ GVariant *value;
+ gchar *text;
+
+ text = g_string_free (state->string, FALSE);
+ state->string = NULL;
+
+ /* If error is set here, it will follow us out, ending the parse.
+ * We still need to free everything, though.
+ */
+ if ((value = g_variant_parse (state->type, text, NULL, NULL, error)))
+ {
+ /* Deal with translatable string attributes */
+ if (state->domain && state->translatable && state->type &&
+ g_variant_type_equal (state->type, G_VARIANT_TYPE_STRING))
+ {
+ const gchar *msgid;
+ const gchar *msgstr;
+
+ msgid = g_variant_get_string (value, NULL);
+ if (state->context)
+ msgstr = g_dpgettext2 (state->domain, state->context, msgid);
+ else
+ msgstr = g_dgettext (state->domain, msgid);
+
+ if (msgstr != msgid)
+ {
+ g_variant_unref (value);
+ value = g_variant_new_string (msgstr);
+ }
+ }
+
+ g_menu_item_set_attribute_value (state->frame.item, g_quark_to_string (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 (text);
+ }
+}
+
+static void
+g_menu_markup_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ GMenuMarkupState *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
+g_menu_markup_error (GMarkupParseContext *context,
+ GError *error,
+ gpointer user_data)
+{
+ GMenuMarkupState *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);
+
+ if (state->objects)
+ g_hash_table_unref (state->objects);
+
+ g_free (state->context);
+
+ g_slice_free (GMenuMarkupState, state);
+}
+
+static GMarkupParser g_menu_subparser =
+{
+ g_menu_markup_start_element,
+ g_menu_markup_end_element,
+ g_menu_markup_text,
+ NULL, /* passthrough */
+ g_menu_markup_error
+};
+
+/**
+ * g_menu_markup_parser_start:
+ * @context: a #GMarkupParseContext
+ * @domain: (allow-none): translation domain for labels, or %NULL
+ * @objects: (allow-none): a #GHashTable for the objects, or %NULL
+ *
+ * Begin parsing a group of menus in XML form.
+ *
+ * If @domain is not %NULL, it will be used to translate attributes
+ * that are marked as translatable, using gettext().
+ *
+ * If @objects is specified then it must be a #GHashTable that was
+ * created using g_hash_table_new_full() with g_str_hash(), g_str_equal(),
+ * g_free() and g_object_unref(). Any named menus that are encountered
+ * while parsing will be added to this table. Each toplevel menu must
+ * be named.
+ *
+ * If @objects is %NULL then an empty hash table will be created.
+ *
+ * This function should be called from the start_element function for
+ * the element representing the group containing the menus. In other
+ * words, the content inside of this element is expected to be a list of
+ * menus.
+ */
+void
+g_menu_markup_parser_start (GMarkupParseContext *context,
+ const gchar *domain,
+ GHashTable *objects)
+{
+ GMenuMarkupState *state;
+
+ g_return_if_fail (context != NULL);
+
+ state = g_slice_new0 (GMenuMarkupState);
+
+ state->domain = g_strdup (domain);
+
+ if (objects != NULL)
+ state->objects = g_hash_table_ref (objects);
+ else
+ state->objects = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+
+ g_markup_parse_context_push (context, &g_menu_subparser, state);
+}
+
+/**
+ * g_menu_markup_parser_end:
+ * @context: a #GMarkupParseContext
+ *
+ * Stop the parsing of a set of menus and return the #GHashTable.
+ *
+ * The #GHashTable maps strings to #GObject instances. The parser only
+ * adds #GMenu instances to the table, but it may contain other types if
+ * a table was provided to g_menu_markup_parser_start().
+ *
+ * This call should be matched with g_menu_markup_parser_start().
+ * See that function for more information
+ *
+ * Returns: (transfer full): the #GHashTable containing the objects
+ **/
+GHashTable *
+g_menu_markup_parser_end (GMarkupParseContext *context)
+{
+ GMenuMarkupState *state = g_markup_parse_context_pop (context);
+ GHashTable *objects;
+
+ objects = state->objects;
+
+ g_free (state->domain);
+
+ g_slice_free (GMenuMarkupState, state);
+
+ return objects;
+}
+
+/**
+ * g_menu_markup_parser_start_menu:
+ * @context: a #GMarkupParseContext
+ * @domain: (allow-none): translation domain for labels, or %NULL
+ * @objects: (allow-none): a #GHashTable for the objects, or %NULL
+ *
+ * Begin parsing the XML definition of a menu.
+ *
+ * This function should be called from the start_element function for
+ * the element representing the menu itself. In other words, the
+ * content inside of this element is expected to be a list of items.
+ *
+ * If @domain is not %NULL, it will be used to translate attributes
+ * that are marked as translatable, using gettext().
+ *
+ * If @objects is specified then it must be a #GHashTable that was
+ * created using g_hash_table_new_full() with g_str_hash(), g_str_equal(),
+ * g_free() and g_object_unref(). Any named menus that are encountered
+ * while parsing will be added to this table.
+ *
+ * If @object is %NULL then named menus will not be supported.
+ *
+ * You should call g_menu_markup_parser_end_menu() from the
+ * corresponding end_element function in order to collect the newly
+ * parsed menu.
+ **/
+void
+g_menu_markup_parser_start_menu (GMarkupParseContext *context,
+ const gchar *domain,
+ GHashTable *objects)
+{
+ GMenuMarkupState *state;
+
+ g_return_if_fail (context != NULL);
+
+ state = g_slice_new0 (GMenuMarkupState);
+
+ if (objects)
+ state->objects = g_hash_table_ref (objects);
+
+ state->domain = g_strdup (domain);
+
+ g_markup_parse_context_push (context, &g_menu_subparser, state);
+
+ state->frame.menu = g_menu_new ();
+}
+
+/**
+ * g_menu_markup_parser_end_menu:
+ * @context: a #GMarkupParseContext
+ *
+ * Stop the parsing of a menu and return the newly-created #GMenu.
+ *
+ * This call should be matched with g_menu_markup_parser_start_menu().
+ * See that function for more information
+ *
+ * Returns: (transfer full): the newly-created #GMenu
+ **/
+GMenu *
+g_menu_markup_parser_end_menu (GMarkupParseContext *context)
+{
+ GMenuMarkupState *state = g_markup_parse_context_pop (context);
+ GMenu *menu;
+
+ menu = state->frame.menu;
+
+ if (state->objects)
+ g_hash_table_unref (state->objects);
+ g_free (state->domain);
+ g_slice_free (GMenuMarkupState, state);
+
+ return menu;
+}
+
+static void
+indent_string (GString *string,
+ gint indent)
+{
+ while (indent--)
+ g_string_append_c (string, ' ');
+}
+
+/**
+ * g_menu_markup_print_string:
+ * @string: a #GString
+ * @model: the #GMenuModel to print
+ * @indent: the intentation level to start at
+ * @tabstop: how much to indent each level
+ *
+ * Print the contents of @model to @string.
+ * Note that you have to provide the containing
+ * <tag class="starttag">menu</tag> element yourself.
+ *
+ * Returns: @string
+ *
+ * Since: 2.32
+ */
+GString *
+g_menu_markup_print_string (GString *string,
+ GMenuModel *model,
+ gint indent,
+ gint tabstop)
+{
+ gboolean need_nl = FALSE;
+ gint i, n;
+
+ if G_UNLIKELY (string == NULL)
+ string = g_string_new (NULL);
+
+ n = g_menu_model_get_n_items (model);
+
+ for (i = 0; i < n; i++)
+ {
+ GMenuAttributeIter *attr_iter;
+ GMenuLinkIter *link_iter;
+ GString *contents;
+ GString *attrs;
+
+ attr_iter = g_menu_model_iterate_item_attributes (model, i);
+ link_iter = g_menu_model_iterate_item_links (model, i);
+ contents = g_string_new (NULL);
+ attrs = g_string_new (NULL);
+
+ while (g_menu_attribute_iter_next (attr_iter))
+ {
+ const char *name = g_menu_attribute_iter_get_name (attr_iter);
+ GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
+
+ if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *str;
+ str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
+ g_string_append (attrs, str);
+ g_free (str);
+ }
+
+ else
+ {
+ gchar *printed;
+ gchar *str;
+
+ printed = g_variant_print (value, TRUE);
+ str = g_markup_printf_escaped ("<attribute name='%s'>%s</attribute>\n", name, printed);
+ indent_string (contents, indent + tabstop);
+ g_string_append (contents, str);
+ g_variant_unref (value);
+ g_free (printed);
+ g_free (str);
+ }
+
+ g_variant_unref (value);
+ }
+ g_object_unref (attr_iter);
+
+ while (g_menu_link_iter_next (link_iter))
+ {
+ const gchar *name = g_menu_link_iter_get_name (link_iter);
+ GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
+ gchar *str;
+
+ if (contents->str[0])
+ g_string_append_c (contents, '\n');
+
+ str = g_markup_printf_escaped ("<link name='%s'>\n", name);
+ indent_string (contents, indent + tabstop);
+ g_string_append (contents, str);
+ g_free (str);
+
+ g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
+
+ indent_string (contents, indent + tabstop);
+ g_string_append (contents, "</link>\n");
+ g_object_unref (menu);
+ }
+ g_object_unref (link_iter);
+
+ if (contents->str[0])
+ {
+ indent_string (string, indent);
+ g_string_append_printf (string, "<item%s>\n", attrs->str);
+ g_string_append (string, contents->str);
+ indent_string (string, indent);
+ g_string_append (string, "</item>\n");
+ need_nl = TRUE;
+ }
+
+ else
+ {
+ if (need_nl)
+ g_string_append_c (string, '\n');
+
+ indent_string (string, indent);
+ g_string_append_printf (string, "<item%s/>\n", attrs->str);
+ need_nl = FALSE;
+ }
+
+ g_string_free (contents, TRUE);
+ g_string_free (attrs, TRUE);
+ }
+
+ return string;
+}
+
+/**
+ * g_menu_markup_print_stderr:
+ * @model: a #GMenuModel
+ *
+ * Print @model to stderr for debugging purposes.
+ *
+ * This debugging function will be removed in the future.
+ **/
+void
+g_menu_markup_print_stderr (GMenuModel *model)
+{
+ GString *string;
+
+ string = g_string_new ("<menu>\n");
+ g_menu_markup_print_string (string, model, 2, 2);
+ g_printerr ("%s</menu>\n", string->str);
+ g_string_free (string, TRUE);
+}
diff --git a/gio/gmenumarkup.h b/gio/gmenumarkup.h
new file mode 100644
index 0000000..9bcf59a
--- /dev/null
+++ b/gio/gmenumarkup.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright  2011 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>
+ */
+
+#ifndef __G_MENU_MARKUP_H__
+#define __G_MENU_MARKUP_H__
+
+#include <gio/gmenu.h>
+
+G_BEGIN_DECLS
+
+void g_menu_markup_parser_start (GMarkupParseContext *context,
+ const gchar *domain,
+ GHashTable *objects);
+GHashTable * g_menu_markup_parser_end (GMarkupParseContext *context);
+
+void g_menu_markup_parser_start_menu (GMarkupParseContext *context,
+ const gchar *domain,
+ GHashTable *objects);
+GMenu * g_menu_markup_parser_end_menu (GMarkupParseContext *context);
+
+void g_menu_markup_print_stderr (GMenuModel *model);
+GString * g_menu_markup_print_string (GString *string,
+ GMenuModel *model,
+ gint indent,
+ gint tabstop);
+
+G_END_DECLS
+
+#endif /* __G_MENU_MARKUP_H__ */
diff --git a/gio/menumarkup.dtd b/gio/menumarkup.dtd
new file mode 100644
index 0000000..a554910
--- /dev/null
+++ b/gio/menumarkup.dtd
@@ -0,0 +1,29 @@
+<!ELEMENT menu (item|submenu|section)* >
+<!ATTLIST menu id CDATA #REQUIRED
+ domain #IMPLIED >
+
+<!ELEMENT item (attribute|link)* >
+<!ATTLIST item label CDATA #IMPLIED
+ action CDATA #IMPLIED
+ target CDATA #IMPLIED >
+
+<!ELEMENT attribute (#PCDATA) >
+<!ATTLIST attribute name CDATA #REQUIRED
+ type CDATA #IMPLIED
+ translatable (yes|no) #IMPLIED
+ context CDATA #IMPLIED
+ comments CDATA #IMPLIED >
+
+<!ELEMENT link (item*) >
+<!ATTLIST link name CDATA #REQUIRED
+ id CDATA #IMPLIED >
+
+<!ELEMENT submenu (item|submenu|section)* >
+<!ATTLIST submenu label CDATA #IMPLIED
+ action CDATA #IMPLIED
+ target CDATA #IMPLIED >
+
+<!ELEMENT section (item|submenu|section)* >
+<!ATTLIST section label CDATA #IMPLIED
+ action CDATA #IMPLIED
+ target CDATA #IMPLIED >
diff --git a/gio/menumarkup.xml b/gio/menumarkup.xml
new file mode 100644
index 0000000..8c0ef30
--- /dev/null
+++ b/gio/menumarkup.xml
@@ -0,0 +1,26 @@
+<menu id='menubar'>
+ <submenu label='File'></submenu>
+ <submenu label='Edit'></submenu>
+ <submenu label='View'>
+ <section>
+ <item label='Toolbar' action='toolbar'/>
+ <item label='Statusbar' action='statusbar'/>
+ </section>
+ <section>
+ <item label='Fullscreen' action='fullscreen'/>
+ </section>
+ <section>
+ <submenu label='Highlight Mode'>
+ <section label='Sources'>
+ <item label='Vala' action='sources' target='vala'/>
+ <item label='Python' action='sources' target='python'/>
+ </section>
+ <section label='Markup'>
+ <item label='asciidoc' action='markup' target='asciidoc'/>
+ <item label='HTML' action='markup' target='html'/>
+ </section>
+ </submenu>
+ </section>
+ </submenu>
+ <submenu label='Help'></submenu>
+</menu>
diff --git a/gio/menumarkup2.xml b/gio/menumarkup2.xml
new file mode 100644
index 0000000..a505e88
--- /dev/null
+++ b/gio/menumarkup2.xml
@@ -0,0 +1,75 @@
+<menu id='menubar'>
+ <item>
+ <attribute name='label'>File</attribute>
+ </item>
+ <item>
+ <attribute name='label'>Edit</attribute>
+ </item>
+ <item>
+ <attribute name='label'>View</attribute>
+ <link name='submenu'>
+ <item>
+ <link name='section'>
+ <item>
+ <attribute name='label'>Toolbar</attribute>
+ <attribute name='action'>toolbar</attribute>
+ </item>
+ <item>
+ <attribute name='label'>Statusbar</attribute>
+ <attribute name='action'>statusbar</attribute>
+ </item>
+ </link>
+ </item>
+ <item>
+ <link name='section'>
+ <item>
+ <attribute name='label'>Fullscreen</attribute>
+ <attribute name='action'>fullscreen</attribute>
+ </item>
+ </link>
+ </item>
+ <item>
+ <link name='section'>
+ <item>
+ <attribute name='label'>Highlight Mode</attribute>
+ <link name='submenu'>
+ <item>
+ <attribute name='label'>Sources</attribute>
+ <link name='section'>
+ <item>
+ <attribute name='label'>Vala</attribute>
+ <attribute name='action'>sources</attribute>
+ <attribute name='target'>vala</attribute>
+ </item>
+ <item>
+ <attribute name='label'>Python</attribute>
+ <attribute name='action'>sources</attribute>
+ <attribute name='target'>python</attribute>
+ </item>
+ </link>
+ </item>
+ <item>
+ <attribute name='label'>Markup</attribute>
+ <link name='section'>
+ <item>
+ <attribute name='label'>asciidoc</attribute>
+ <attribute name='action'>markup</attribute>
+ <attribute name='target'>asciidoc</attribute>
+ </item>
+ <item>
+ <attribute name='label'>HTML</attribute>
+ <attribute name='action'>markup</attribute>
+ <attribute name='target'>html</attribute>
+ </item>
+ </link>
+ </item>
+ </link>
+ </item>
+ </link>
+ </item>
+ </link>
+ </item>
+ <item>
+ <attribute name='label'>Help</attribute>
+ </item>
+</menu>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]