2012-01-25 23:23:25 +00:00
|
|
|
/*
|
|
|
|
* 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
|
2012-02-27 13:01:10 +00:00
|
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
2012-01-25 23:23:25 +00:00
|
|
|
*
|
|
|
|
* Author: Ryan Lortie <desrt@desrt.ca>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include "gtkbuilderprivate.h"
|
2020-09-25 12:27:26 +00:00
|
|
|
#include "gtkbuildableprivate.h"
|
2012-01-25 23:23:25 +00:00
|
|
|
#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 */
|
2020-07-24 18:40:36 +00:00
|
|
|
char *attribute;
|
2012-01-25 23:23:25 +00:00
|
|
|
GVariantType *type;
|
|
|
|
GString *string;
|
|
|
|
|
|
|
|
/* translation */
|
2020-07-24 18:40:36 +00:00
|
|
|
char *context;
|
2012-01-25 23:23:25 +00:00
|
|
|
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
|
2019-08-29 13:21:20 +00:00
|
|
|
gtk_builder_menu_start_element (GtkBuildableParseContext *context,
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *element_name,
|
|
|
|
const char **attribute_names,
|
|
|
|
const char **attribute_values,
|
2019-08-29 13:21:20 +00:00
|
|
|
gpointer user_data,
|
|
|
|
GError **error)
|
2012-01-25 23:23:25 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2012-01-31 05:07:52 +00:00
|
|
|
if (COLLECT (G_MARKUP_COLLECT_INVALID, NULL))
|
|
|
|
{
|
|
|
|
item = g_menu_item_new (NULL, NULL);
|
|
|
|
gtk_builder_menu_push_frame (state, NULL, item);
|
|
|
|
}
|
|
|
|
|
2012-01-25 23:23:25 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (g_str_equal (element_name, "submenu"))
|
|
|
|
{
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *id;
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
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));
|
2014-06-29 03:19:09 +00:00
|
|
|
g_object_unref (menu);
|
2012-01-25 23:23:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (g_str_equal (element_name, "section"))
|
|
|
|
{
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *id;
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
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));
|
2014-06-29 03:19:09 +00:00
|
|
|
g_object_unref (menu);
|
2012-01-25 23:23:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->frame.item)
|
|
|
|
{
|
|
|
|
/* Can have '<attribute>' or '<link>' here. */
|
|
|
|
if (g_str_equal (element_name, "attribute"))
|
|
|
|
{
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *typestr;
|
|
|
|
const char *name;
|
|
|
|
const char *ctxt;
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
if (COLLECT (STRING, "name", &name,
|
|
|
|
OPTIONAL | BOOLEAN, "translatable", &state->translatable,
|
2015-05-31 01:24:03 +00:00
|
|
|
OPTIONAL | STRING, "context", &ctxt,
|
2012-01-25 23:23:25 +00:00
|
|
|
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);
|
2015-05-31 01:24:03 +00:00
|
|
|
state->context = g_strdup (ctxt);
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
gtk_builder_menu_push_frame (state, NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (g_str_equal (element_name, "link"))
|
|
|
|
{
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *name;
|
|
|
|
const char *id;
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
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));
|
2014-06-29 03:19:09 +00:00
|
|
|
g_object_unref (menu);
|
2012-01-25 23:23:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2019-08-29 13:21:20 +00:00
|
|
|
GPtrArray *element_stack;
|
2012-01-25 23:23:25 +00:00
|
|
|
|
2019-08-29 13:21:20 +00:00
|
|
|
element_stack = gtk_buildable_parse_context_get_element_stack (context);
|
2012-01-25 23:23:25 +00:00
|
|
|
|
2019-08-29 13:21:20 +00:00
|
|
|
if (element_stack->len > 1)
|
2012-01-25 23:23:25 +00:00
|
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
|
|
|
|
_("Element <%s> not allowed inside <%s>"),
|
2019-08-29 13:21:20 +00:00
|
|
|
element_name,
|
2020-07-24 18:40:36 +00:00
|
|
|
(const char *) g_ptr_array_index (element_stack, element_stack->len - 2));
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
else
|
|
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
|
|
|
|
_("Element <%s> not allowed at toplevel"), element_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2019-08-29 13:21:20 +00:00
|
|
|
gtk_builder_menu_end_element (GtkBuildableParseContext *context,
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *element_name,
|
2019-08-29 13:21:20 +00:00
|
|
|
gpointer user_data,
|
|
|
|
GError **error)
|
2012-01-25 23:23:25 +00:00
|
|
|
{
|
|
|
|
GtkBuilderMenuState *state = user_data;
|
|
|
|
|
|
|
|
gtk_builder_menu_pop_frame (state);
|
|
|
|
|
|
|
|
if (state->string)
|
|
|
|
{
|
|
|
|
GVariant *value;
|
2020-07-24 18:40:36 +00:00
|
|
|
char *text;
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
text = g_string_free (state->string, FALSE);
|
|
|
|
state->string = NULL;
|
|
|
|
|
|
|
|
/* do the translation if necessary */
|
2012-02-08 08:32:07 +00:00
|
|
|
if (state->translatable)
|
2012-01-25 23:23:25 +00:00
|
|
|
{
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *translated;
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
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
|
2019-08-29 13:21:20 +00:00
|
|
|
gtk_builder_menu_text (GtkBuildableParseContext *context,
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *text,
|
2019-08-29 13:21:20 +00:00
|
|
|
gsize text_len,
|
|
|
|
gpointer user_data,
|
|
|
|
GError **error)
|
2012-01-25 23:23:25 +00:00
|
|
|
{
|
|
|
|
GtkBuilderMenuState *state = user_data;
|
2020-07-24 13:54:49 +00:00
|
|
|
int i;
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
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,
|
2014-08-22 15:04:07 +00:00
|
|
|
_("Text may not appear inside <%s>"),
|
2019-08-29 13:21:20 +00:00
|
|
|
gtk_buildable_parse_context_get_element (context));
|
2012-01-25 23:23:25 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2019-08-29 13:21:20 +00:00
|
|
|
gtk_builder_menu_error (GtkBuildableParseContext *context,
|
|
|
|
GError *error,
|
|
|
|
gpointer user_data)
|
2012-01-25 23:23:25 +00:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-08-29 13:21:20 +00:00
|
|
|
static GtkBuildableParser gtk_builder_menu_subparser =
|
2012-01-25 23:23:25 +00:00
|
|
|
{
|
|
|
|
gtk_builder_menu_start_element,
|
|
|
|
gtk_builder_menu_end_element,
|
|
|
|
gtk_builder_menu_text,
|
|
|
|
gtk_builder_menu_error
|
|
|
|
};
|
|
|
|
|
|
|
|
void
|
|
|
|
_gtk_builder_menu_start (ParserData *parser_data,
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *element_name,
|
|
|
|
const char **attribute_names,
|
|
|
|
const char **attribute_values,
|
2012-01-25 23:23:25 +00:00
|
|
|
GError **error)
|
|
|
|
{
|
|
|
|
GtkBuilderMenuState *state;
|
2020-07-24 18:40:36 +00:00
|
|
|
char *id;
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
state = g_slice_new0 (GtkBuilderMenuState);
|
|
|
|
state->parser_data = parser_data;
|
2019-08-29 13:21:20 +00:00
|
|
|
gtk_buildable_parse_context_push (&parser_data->ctx, >k_builder_menu_subparser, state);
|
2012-01-25 23:23:25 +00:00
|
|
|
|
|
|
|
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);
|
2014-06-29 00:21:32 +00:00
|
|
|
g_object_unref (menu);
|
2012-01-25 23:23:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gtk_builder_menu_end (ParserData *parser_data)
|
|
|
|
{
|
|
|
|
GtkBuilderMenuState *state;
|
|
|
|
|
2019-08-29 13:21:20 +00:00
|
|
|
state = gtk_buildable_parse_context_pop (&parser_data->ctx);
|
2012-01-25 23:23:25 +00:00
|
|
|
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);
|
|
|
|
}
|