398 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			398 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright © 2011, 2012 Canonical Ltd.
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU Lesser General Public License as
 | |
|  * published by the Free Software Foundation; either version 2 of the
 | |
|  * licence, or (at your option) any later version.
 | |
|  *
 | |
|  * This library is distributed in the hope that it will be useful, but
 | |
|  * WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * Lesser General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General Public
 | |
|  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  * Author: Ryan Lortie <desrt@desrt.ca>
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| 
 | |
| #include "gtkbuilderprivate.h"
 | |
| #include "gtkintl.h"
 | |
| 
 | |
| #include <gio/gio.h>
 | |
| #include <string.h>
 | |
| 
 | |
| struct frame
 | |
| {
 | |
|   GMenu        *menu;
 | |
|   GMenuItem    *item;
 | |
|   struct frame *prev;
 | |
| };
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
|   ParserData *parser_data;
 | |
|   struct frame frame;
 | |
| 
 | |
|   /* attributes */
 | |
|   gchar        *attribute;
 | |
|   GVariantType *type;
 | |
|   GString      *string;
 | |
| 
 | |
|   /* translation */
 | |
|   gchar        *context;
 | |
|   gboolean      translatable;
 | |
| } GtkBuilderMenuState;
 | |
| 
 | |
| static void
 | |
| gtk_builder_menu_push_frame (GtkBuilderMenuState *state,
 | |
|                              GMenu               *menu,
 | |
|                              GMenuItem           *item)
 | |
| {
 | |
|   struct frame *new;
 | |
| 
 | |
|   new = g_slice_new (struct frame);
 | |
|   *new = state->frame;
 | |
| 
 | |
|   state->frame.menu = menu;
 | |
|   state->frame.item = item;
 | |
|   state->frame.prev = new;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_builder_menu_pop_frame (GtkBuilderMenuState *state)
 | |
| {
 | |
|   struct frame *prev = state->frame.prev;
 | |
| 
 | |
|   if (state->frame.item)
 | |
|     {
 | |
|       g_assert (prev->menu != NULL);
 | |
|       g_menu_append_item (prev->menu, state->frame.item);
 | |
|       g_object_unref (state->frame.item);
 | |
|     }
 | |
| 
 | |
|   state->frame = *prev;
 | |
| 
 | |
|   g_slice_free (struct frame, prev);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_builder_menu_start_element (GMarkupParseContext  *context,
 | |
|                                 const gchar          *element_name,
 | |
|                                 const gchar         **attribute_names,
 | |
|                                 const gchar         **attribute_values,
 | |
|                                 gpointer              user_data,
 | |
|                                 GError              **error)
 | |
| {
 | |
|   GtkBuilderMenuState *state = user_data;
 | |
| 
 | |
| #define COLLECT(first, ...) \
 | |
|   g_markup_collect_attributes (element_name,                                 \
 | |
|                                attribute_names, attribute_values, error,     \
 | |
|                                first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID)
 | |
| #define OPTIONAL   G_MARKUP_COLLECT_OPTIONAL
 | |
| #define BOOLEAN    G_MARKUP_COLLECT_BOOLEAN
 | |
| #define STRING     G_MARKUP_COLLECT_STRING
 | |
| 
 | |
|   if (state->frame.menu)
 | |
|     {
 | |
|       /* Can have '<item>', '<submenu>' or '<section>' here. */
 | |
|       if (g_str_equal (element_name, "item"))
 | |
|         {
 | |
|           GMenuItem *item;
 | |
| 
 | |
|           if (COLLECT (G_MARKUP_COLLECT_INVALID, NULL))
 | |
|             {
 | |
|               item = g_menu_item_new (NULL, NULL);
 | |
|               gtk_builder_menu_push_frame (state, NULL, item);
 | |
|             }
 | |
| 
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|       else if (g_str_equal (element_name, "submenu"))
 | |
|         {
 | |
|           const gchar *id;
 | |
| 
 | |
|           if (COLLECT (STRING | OPTIONAL, "id", &id))
 | |
|             {
 | |
|               GMenuItem *item;
 | |
|               GMenu *menu;
 | |
| 
 | |
|               menu = g_menu_new ();
 | |
|               item = g_menu_item_new_submenu (NULL, G_MENU_MODEL (menu));
 | |
|               gtk_builder_menu_push_frame (state, menu, item);
 | |
| 
 | |
|               if (id != NULL)
 | |
|                 _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
 | |
|               g_object_unref (menu);
 | |
|             }
 | |
| 
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|       else if (g_str_equal (element_name, "section"))
 | |
|         {
 | |
|           const gchar *id;
 | |
| 
 | |
|           if (COLLECT (STRING | OPTIONAL, "id", &id))
 | |
|             {
 | |
|               GMenuItem *item;
 | |
|               GMenu *menu;
 | |
| 
 | |
|               menu = g_menu_new ();
 | |
|               item = g_menu_item_new_section (NULL, G_MENU_MODEL (menu));
 | |
|               gtk_builder_menu_push_frame (state, menu, item);
 | |
| 
 | |
|               if (id != NULL)
 | |
|                 _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
 | |
|               g_object_unref (menu);
 | |
|             }
 | |
| 
 | |
|           return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   if (state->frame.item)
 | |
|     {
 | |
|       /* Can have '<attribute>' or '<link>' here. */
 | |
|       if (g_str_equal (element_name, "attribute"))
 | |
|         {
 | |
|           const gchar *typestr;
 | |
|           const gchar *name;
 | |
|           const gchar *ctxt;
 | |
| 
 | |
|           if (COLLECT (STRING,             "name", &name,
 | |
|                        OPTIONAL | BOOLEAN, "translatable", &state->translatable,
 | |
|                        OPTIONAL | STRING,  "context", &ctxt,
 | |
|                        OPTIONAL | STRING,  "comments", NULL, /* ignore, just for translators */
 | |
|                        OPTIONAL | STRING,  "type", &typestr))
 | |
|             {
 | |
|               if (typestr && !g_variant_type_string_is_valid (typestr))
 | |
|                 {
 | |
|                   g_set_error (error, G_VARIANT_PARSE_ERROR,
 | |
|                                G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING,
 | |
|                                "Invalid GVariant type string '%s'", typestr);
 | |
|                   return;
 | |
|                 }
 | |
| 
 | |
|               state->type = typestr ? g_variant_type_new (typestr) : NULL;
 | |
|               state->string = g_string_new (NULL);
 | |
|               state->attribute = g_strdup (name);
 | |
|               state->context = g_strdup (ctxt);
 | |
| 
 | |
|               gtk_builder_menu_push_frame (state, NULL, NULL);
 | |
|             }
 | |
| 
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|       if (g_str_equal (element_name, "link"))
 | |
|         {
 | |
|           const gchar *name;
 | |
|           const gchar *id;
 | |
| 
 | |
|           if (COLLECT (STRING,            "name", &name,
 | |
|                        STRING | OPTIONAL, "id",   &id))
 | |
|             {
 | |
|               GMenu *menu;
 | |
| 
 | |
|               menu = g_menu_new ();
 | |
|               g_menu_item_set_link (state->frame.item, name, G_MENU_MODEL (menu));
 | |
|               gtk_builder_menu_push_frame (state, menu, NULL);
 | |
| 
 | |
|               if (id != NULL)
 | |
|                 _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
 | |
|               g_object_unref (menu);
 | |
|             }
 | |
| 
 | |
|           return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   {
 | |
|     const GSList *element_stack;
 | |
| 
 | |
|     element_stack = g_markup_parse_context_get_element_stack (context);
 | |
| 
 | |
|     if (element_stack->next)
 | |
|       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
 | |
|                    _("Element <%s> not allowed inside <%s>"),
 | |
|                    element_name, (const gchar *) element_stack->next->data);
 | |
| 
 | |
|     else
 | |
|       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
 | |
|                    _("Element <%s> not allowed at toplevel"), element_name);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_builder_menu_end_element (GMarkupParseContext  *context,
 | |
|                               const gchar          *element_name,
 | |
|                               gpointer              user_data,
 | |
|                               GError              **error)
 | |
| {
 | |
|   GtkBuilderMenuState *state = user_data;
 | |
| 
 | |
|   gtk_builder_menu_pop_frame (state);
 | |
| 
 | |
|   if (state->string)
 | |
|     {
 | |
|       GVariant *value;
 | |
|       gchar *text;
 | |
| 
 | |
|       text = g_string_free (state->string, FALSE);
 | |
|       state->string = NULL;
 | |
| 
 | |
|       /* do the translation if necessary */
 | |
|       if (state->translatable)
 | |
|         {
 | |
|           const gchar *translated;
 | |
| 
 | |
|           if (state->context)
 | |
|             translated = g_dpgettext2 (state->parser_data->domain, state->context, text);
 | |
|           else
 | |
|             translated = g_dgettext (state->parser_data->domain, text);
 | |
| 
 | |
|          if (translated != text)
 | |
|            {
 | |
|              /* it's safe because we know that translated != text */
 | |
|              g_free (text);
 | |
|              text = g_strdup (translated);
 | |
|            }
 | |
|         }
 | |
| 
 | |
|       if (state->type == NULL)
 | |
|         /* No type string specified -> it's a normal string. */
 | |
|         g_menu_item_set_attribute (state->frame.item, state->attribute, "s", text);
 | |
| 
 | |
|       /* Else, we try to parse it according to the type string.  If
 | |
|        * error is set here, it will follow us out, ending the parse.
 | |
|        *
 | |
|        * We still need to free everything, though, so ignore it here.
 | |
|        */
 | |
|       else if ((value = g_variant_parse (state->type, text, NULL, NULL, error)))
 | |
|         {
 | |
|           g_menu_item_set_attribute_value (state->frame.item, state->attribute, value);
 | |
|           g_variant_unref (value);
 | |
|         }
 | |
| 
 | |
|       if (state->type)
 | |
|         {
 | |
|           g_variant_type_free (state->type);
 | |
|           state->type = NULL;
 | |
|         }
 | |
| 
 | |
|       g_free (state->context);
 | |
|       state->context = NULL;
 | |
| 
 | |
|       g_free (state->attribute);
 | |
|       state->attribute = NULL;
 | |
| 
 | |
|       g_free (text);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_builder_menu_text (GMarkupParseContext  *context,
 | |
|                        const gchar          *text,
 | |
|                        gsize                 text_len,
 | |
|                        gpointer              user_data,
 | |
|                        GError              **error)
 | |
| {
 | |
|   GtkBuilderMenuState *state = user_data;
 | |
|   gint i;
 | |
| 
 | |
|   for (i = 0; i < text_len; i++)
 | |
|     if (!g_ascii_isspace (text[i]))
 | |
|       {
 | |
|         if (state->string)
 | |
|           g_string_append_len (state->string, text, text_len);
 | |
| 
 | |
|         else
 | |
|           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
 | |
|                        _("Text may not appear inside <%s>"),
 | |
|                        g_markup_parse_context_get_element (context));
 | |
|         break;
 | |
|       }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_builder_menu_error (GMarkupParseContext *context,
 | |
|                         GError              *error,
 | |
|                         gpointer             user_data)
 | |
| {
 | |
|   GtkBuilderMenuState *state = user_data;
 | |
| 
 | |
|   while (state->frame.prev)
 | |
|     {
 | |
|       struct frame *prev = state->frame.prev;
 | |
| 
 | |
|       state->frame = *prev;
 | |
| 
 | |
|       g_slice_free (struct frame, prev);
 | |
|     }
 | |
| 
 | |
|   if (state->string)
 | |
|     g_string_free (state->string, TRUE);
 | |
| 
 | |
|   if (state->type)
 | |
|     g_variant_type_free (state->type);
 | |
| 
 | |
|   g_free (state->attribute);
 | |
|   g_free (state->context);
 | |
| 
 | |
|   g_slice_free (GtkBuilderMenuState, state);
 | |
| }
 | |
| 
 | |
| static GMarkupParser gtk_builder_menu_subparser =
 | |
| {
 | |
|   gtk_builder_menu_start_element,
 | |
|   gtk_builder_menu_end_element,
 | |
|   gtk_builder_menu_text,
 | |
|   NULL,                            /* passthrough */
 | |
|   gtk_builder_menu_error
 | |
| };
 | |
| 
 | |
| void
 | |
| _gtk_builder_menu_start (ParserData   *parser_data,
 | |
|                          const gchar  *element_name,
 | |
|                          const gchar **attribute_names,
 | |
|                          const gchar **attribute_values,
 | |
|                          GError      **error)
 | |
| {
 | |
|   GtkBuilderMenuState *state;
 | |
|   gchar *id;
 | |
| 
 | |
|   state = g_slice_new0 (GtkBuilderMenuState);
 | |
|   state->parser_data = parser_data;
 | |
|   g_markup_parse_context_push (parser_data->ctx, >k_builder_menu_subparser, state);
 | |
| 
 | |
|   if (COLLECT (STRING, "id", &id))
 | |
|     {
 | |
|       GMenu *menu;
 | |
| 
 | |
|       menu = g_menu_new ();
 | |
|       _gtk_builder_add_object (state->parser_data->builder, id, G_OBJECT (menu));
 | |
|       gtk_builder_menu_push_frame (state, menu, NULL);
 | |
|       g_object_unref (menu);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| _gtk_builder_menu_end (ParserData *parser_data)
 | |
| {
 | |
|   GtkBuilderMenuState *state;
 | |
| 
 | |
|   state = g_markup_parse_context_pop (parser_data->ctx);
 | |
|   gtk_builder_menu_pop_frame (state);
 | |
| 
 | |
|   g_assert (state->frame.prev == NULL);
 | |
|   g_assert (state->frame.item == NULL);
 | |
|   g_assert (state->frame.menu == NULL);
 | |
|   g_slice_free (GtkBuilderMenuState, state);
 | |
| }
 | 
