[glib/wip/gmenu: 10/59] Add GMenu markup



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]