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 *context;
 | 
						|
 | 
						|
          if (COLLECT (STRING,             "name", &name,
 | 
						|
                       OPTIONAL | BOOLEAN, "translatable", &state->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_strdup (name);
 | 
						|
              state->context = g_strdup (context);
 | 
						|
 | 
						|
              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);
 | 
						|
}
 |