Add GMenu markup

These functions serialize and deserialize a GMenuModel
to and from XML.
This commit is contained in:
Matthias Clasen 2011-11-26 22:00:48 -05:00 committed by Ryan Lortie
parent 13f5977735
commit 6b40d4eb6b
10 changed files with 917 additions and 0 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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 = \

View File

@ -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__

View File

@ -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

717
gio/gmenumarkup.c Normal file
View File

@ -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);
}

47
gio/gmenumarkup.h Normal file
View File

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

29
gio/menumarkup.dtd Normal file
View File

@ -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 >

26
gio/menumarkup.xml Normal file
View File

@ -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>

75
gio/menumarkup2.xml Normal file
View File

@ -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>