From: Matthias Clasen Date: Sun, 27 Nov 2011 03:00:48 +0000 (-0500) Subject: Add GMenu markup X-Git-Tag: 2.31.4~96 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=6b40d4eb6bf2a974e52be34e7c25b9f9f98242db;p=platform%2Fupstream%2Fglib.git Add GMenu markup These functions serialize and deserialize a GMenuModel to and from XML. --- 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 @@ + Extending GIO 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 + +
+gmenumarkup +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 +
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 #include #include +#include #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 + */ + +#include "gmenumarkup.h" + +#include + +/** + * 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 + * menu element, which contains one or more + * item elements. Each item + * element contains attribute and link + * elements with a mandatory name attribute. + * link elements have the same content + * model as menu. + * + * Here is the XML for : + * |[FIXME: MISSING XINCLUDE CONTENT]| + * + * The parser also understands a somewhat less verbose format, in which + * attributes are encoded as actual XML attributes of item + * elements, and link elements are replaced by + * section and submenu elements. + * + * Here is how the example looks in this format: + * |[FIXME: MISSING XINCLUDE CONTENT]| + * + * The parser can obtaing translations for attribute values using gettext. + * To make use of this, the menu element must + * have a domain attribute which specifies the gettext domain to use, and + * attribute elements can be marked for translation + * with a translatable="yes" 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: + * |[FIXME: MISSING XINCLUDE CONTENT]| + * + * 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 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 '', '' or '
' 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 '' or '' 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 + * menu 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 ("%s\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 ("\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, "\n"); + g_object_unref (menu); + } + g_object_unref (link_iter); + + if (contents->str[0]) + { + indent_string (string, indent); + g_string_append_printf (string, "\n", attrs->str); + g_string_append (string, contents->str); + indent_string (string, indent); + g_string_append (string, "\n"); + need_nl = TRUE; + } + + else + { + if (need_nl) + g_string_append_c (string, '\n'); + + indent_string (string, indent); + g_string_append_printf (string, "\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 ("\n"); + g_menu_markup_print_string (string, model, 2, 2); + g_printerr ("%s\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 + */ + +#ifndef __G_MENU_MARKUP_H__ +#define __G_MENU_MARKUP_H__ + +#include + +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 @@ + + + + + + + + + + + + + + + + + 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 @@ + + + + +
+ + +
+
+ +
+
+ +
+ + +
+
+ + +
+
+
+
+ +
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 @@ + + + File + + + Edit + + + View + + + + + Toolbar + toolbar + + + Statusbar + statusbar + + + + + + + Fullscreen + fullscreen + + + + + + + Highlight Mode + + + Sources + + + Vala + sources + vala + + + Python + sources + python + + + + + Markup + + + asciidoc + markup + asciidoc + + + HTML + markup + html + + + + + + + + + + + Help + +