diff options
author | Matthias Clasen <matthiasc@src.gnome.org> | 2003-08-25 23:13:47 +0000 |
---|---|---|
committer | Matthias Clasen <matthiasc@src.gnome.org> | 2003-08-25 23:13:47 +0000 |
commit | 77af2d637c944f1e61d290ede8ae61be95c5abb7 (patch) | |
tree | 1b75baadf5c443edc0ae00fde5a963cb03c2e8e8 /gtk/gtkuimanager.c | |
parent | 3cb958de19162d57df4c04d383f7fd52e4d818a1 (diff) | |
download | gtk+-77af2d637c944f1e61d290ede8ae61be95c5abb7.tar.gz |
Rename GtkMenuMerge to GtkUIManager.
Diffstat (limited to 'gtk/gtkuimanager.c')
-rw-r--r-- | gtk/gtkuimanager.c | 1656 |
1 files changed, 1656 insertions, 0 deletions
diff --git a/gtk/gtkuimanager.c b/gtk/gtkuimanager.c new file mode 100644 index 0000000000..5f7b3e78ea --- /dev/null +++ b/gtk/gtkuimanager.c @@ -0,0 +1,1656 @@ +/* + * GTK - The GIMP Toolkit + * Copyright (C) 1998, 1999 Red Hat, Inc. + * All rights reserved. + * + * This Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Author: James Henstridge <james@daa.com.au> + * + * Modified by the GTK+ Team and others 2003. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#include <config.h> + +#include <string.h> +#include "gtkuimanager.h" +#include "gtktoolbar.h" +#include "gtkseparatortoolitem.h" +#include "gtkmenushell.h" +#include "gtkmenu.h" +#include "gtkmenubar.h" +#include "gtkseparatormenuitem.h" +#include "gtkintl.h" + +#undef DEBUG_UI_MANAGER + +typedef enum +{ + GTK_UI_MANAGER_UNDECIDED, + GTK_UI_MANAGER_ROOT, + GTK_UI_MANAGER_MENUBAR, + GTK_UI_MANAGER_MENU, + GTK_UI_MANAGER_TOOLBAR, + GTK_UI_MANAGER_MENU_PLACEHOLDER, + GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER, + GTK_UI_MANAGER_POPUPS, + GTK_UI_MANAGER_MENUITEM, + GTK_UI_MANAGER_TOOLITEM, + GTK_UI_MANAGER_SEPARATOR, +} GtkUIManagerNodeType; + + +typedef struct _GtkUIManagerNode GtkUIManagerNode; + +struct _GtkUIManagerNode { + GtkUIManagerNodeType type; + + const gchar *name; + + GQuark action_name; + GtkAction *action; + GtkWidget *proxy; + GtkWidget *extra; /*GtkMenu for submenus, second separator for placeholders*/ + + GList *uifiles; + + guint dirty : 1; +}; + +#define GTK_UI_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_UI_MANAGER, GtkUIManagerPrivate)) + +struct _GtkUIManagerPrivate +{ + GtkAccelGroup *accel_group; + + GNode *root_node; + GList *action_groups; + + guint last_merge_id; + + guint update_tag; +}; + +#define NODE_INFO(node) ((GtkUIManagerNode *)node->data) + +typedef struct _NodeUIReference NodeUIReference; + +struct _NodeUIReference +{ + guint merge_id; + GQuark action_quark; +}; + +static void gtk_ui_manager_class_init (GtkUIManagerClass *class); +static void gtk_ui_manager_init (GtkUIManager *merge); + +static void gtk_ui_manager_queue_update (GtkUIManager *self); +static void gtk_ui_manager_dirty_all (GtkUIManager *self); + +static GNode *get_child_node (GtkUIManager *self, GNode *parent, + const gchar *childname, + gint childname_length, + GtkUIManagerNodeType node_type, + gboolean create, gboolean top); +static GNode *gtk_ui_manager_get_node (GtkUIManager *self, + const gchar *path, + GtkUIManagerNodeType node_type, + gboolean create); +static guint gtk_ui_manager_next_merge_id (GtkUIManager *self); + +static void gtk_ui_manager_node_prepend_ui_reference (GtkUIManagerNode *node, + guint merge_id, + GQuark action_quark); +static void gtk_ui_manager_node_remove_ui_reference (GtkUIManagerNode *node, + guint merge_id); +static void gtk_ui_manager_ensure_update (GtkUIManager *self); + + +enum +{ + ADD_WIDGET, + REMOVE_WIDGET, + LAST_SIGNAL +}; + +static guint merge_signals[LAST_SIGNAL] = { 0 }; + +static GMemChunk *merge_node_chunk = NULL; + +GType +gtk_ui_manager_get_type (void) +{ + static GtkType type = 0; + + if (!type) + { + static const GTypeInfo type_info = + { + sizeof (GtkUIManagerClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gtk_ui_manager_class_init, + (GClassFinalizeFunc) NULL, + NULL, + + sizeof (GtkUIManager), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_ui_manager_init, + }; + + type = g_type_register_static (G_TYPE_OBJECT, + "GtkUIManager", + &type_info, 0); + } + return type; +} + +static void +gtk_ui_manager_class_init (GtkUIManagerClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + + if (!merge_node_chunk) + merge_node_chunk = g_mem_chunk_create (GtkUIManagerNode, 64, + G_ALLOC_AND_FREE); + + merge_signals[ADD_WIDGET] = + g_signal_new ("add_widget", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, + G_STRUCT_OFFSET (GtkUIManagerClass, add_widget), NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_WIDGET); + merge_signals[REMOVE_WIDGET] = + g_signal_new ("remove_widget", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, + G_STRUCT_OFFSET (GtkUIManagerClass, remove_widget), NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_WIDGET); + + g_type_class_add_private (gobject_class, sizeof (GtkUIManagerPrivate)); +} + + +static void +gtk_ui_manager_init (GtkUIManager *self) +{ + guint merge_id; + GNode *node; + + self->private_data = GTK_UI_MANAGER_GET_PRIVATE (self); + + self->private_data->accel_group = gtk_accel_group_new (); + + self->private_data->root_node = NULL; + self->private_data->action_groups = NULL; + + self->private_data->last_merge_id = 0; + + + merge_id = gtk_ui_manager_next_merge_id (self); + node = get_child_node (self, NULL, "Root", 4, + GTK_UI_MANAGER_ROOT, TRUE, FALSE); + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (node), merge_id, 0); + node = get_child_node (self, self->private_data->root_node, "popups", 6, + GTK_UI_MANAGER_POPUPS, TRUE, FALSE); + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (node), merge_id, 0); +} + + +/** + * gtk_ui_manager_new: + * + * Creates a new menu merge object. + * + * Return value: a new menu merge object. + * + * Since: 2.4 + **/ +GtkUIManager* +gtk_ui_manager_new (void) +{ + return g_object_new (GTK_TYPE_UI_MANAGER, NULL); +} + + +/** + * gtk_ui_manager_insert_action_group: + * @self: a #GtkUIManager object + * @action_group: the action group to be inserted + * @pos: the position at which the group will be inserted + * + * Inserts an action group into the list of action groups associated with @self. + * + * Since: 2.4 + **/ +void +gtk_ui_manager_insert_action_group (GtkUIManager *self, + GtkActionGroup *action_group, + gint pos) +{ + g_return_if_fail (GTK_IS_UI_MANAGER (self)); + g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); + g_return_if_fail (g_list_find (self->private_data->action_groups, action_group) == NULL); + + g_object_ref (action_group); + self->private_data->action_groups = g_list_insert (self->private_data->action_groups, action_group, pos); + + /* dirty all nodes, as action bindings may change */ + gtk_ui_manager_dirty_all (self); +} + +/** + * gtk_ui_manager_remove_action_group: + * @self: a #GtkUIManager object + * @action_group: the action group to be removed + * + * Removes an action group from the list of action groups associated with @self. + * + * Since: 2.4 + **/ +void +gtk_ui_manager_remove_action_group (GtkUIManager *self, + GtkActionGroup *action_group) +{ + g_return_if_fail (GTK_IS_UI_MANAGER (self)); + g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); + g_return_if_fail (g_list_find (self->private_data->action_groups, + action_group) != NULL); + + self->private_data->action_groups = + g_list_remove (self->private_data->action_groups, action_group); + g_object_unref (action_group); + + /* dirty all nodes, as action bindings may change */ + gtk_ui_manager_dirty_all (self); +} + +/** + * gtk_ui_manager_get_action_groups: + * @self: a #GtkUIManager object + * + * Returns the list of action groups associated with @self. + * + * Return value: a #GList of action groups. The list is owned by GTK+ + * and should not be modified. + * + * Since: 2.4 + **/ +GList * +gtk_ui_manager_get_action_groups (GtkUIManager *self) +{ + g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL); + + return self->private_data->action_groups; +} + +/** + * gtk_ui_manager_get_accel_group: + * @self: a #GtkUIManager object + * + * Returns the #GtkAccelGroup associated with @self. + * + * Return value: the #GtkAccelGroup. + * + * Since: 2.4 + **/ +GtkAccelGroup * +gtk_ui_manager_get_accel_group (GtkUIManager *self) +{ + g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL); + + return self->private_data->accel_group; +} + +/** + * gtk_ui_manager_get_widget: + * @self: a #GtkUIManager + * @path: a path + * + * Looks up a widget by following a path. The path consists of the names specified + * in the XML description of the UI. separated by '/'. Elements which don't have + * a name attribute in the XML (e.g. <popups>) can be addressed by their + * XML element name (e.g. "popups"). + * + * Return value: the widget found by following the path, or %NULL if no widget + * was found. + * + * Since: 2.4 + **/ +GtkWidget * +gtk_ui_manager_get_widget (GtkUIManager *self, + const gchar *path) +{ + GNode *node; + + /* ensure that there are no pending updates before we get the + * widget */ + gtk_ui_manager_ensure_update (self); + + node = gtk_ui_manager_get_node (self, path, GTK_UI_MANAGER_UNDECIDED, FALSE); + + if (node == NULL) + return NULL; + + return NODE_INFO (node)->proxy; +} + +static GNode * +get_child_node (GtkUIManager *self, + GNode *parent, + const gchar *childname, + gint childname_length, + GtkUIManagerNodeType node_type, + gboolean create, + gboolean top) +{ + GNode *child = NULL; + + g_return_val_if_fail (parent == NULL || + (NODE_INFO (parent)->type != GTK_UI_MANAGER_MENUITEM && + NODE_INFO (parent)->type != GTK_UI_MANAGER_TOOLITEM), + NULL); + + if (parent) + { + if (childname) + { + for (child = parent->children; child != NULL; child = child->next) + { + if (strlen (NODE_INFO (child)->name) == childname_length && + !strncmp (NODE_INFO (child)->name, childname, childname_length)) + { + /* if undecided about node type, set it */ + if (NODE_INFO (child)->type == GTK_UI_MANAGER_UNDECIDED) + NODE_INFO (child)->type = node_type; + + /* warn about type mismatch */ + if (NODE_INFO (child)->type != GTK_UI_MANAGER_UNDECIDED && + node_type != GTK_UI_MANAGER_UNDECIDED && + NODE_INFO (child)->type != node_type) + g_warning ("node type doesn't match %d (%s is type %d)", + node_type, + NODE_INFO (child)->name, + NODE_INFO (child)->type); + + return child; + } + } + } + if (!child && create) + { + GtkUIManagerNode *mnode; + + mnode = g_chunk_new0 (GtkUIManagerNode, merge_node_chunk); + mnode->type = node_type; + mnode->name = g_strndup (childname, childname_length); + mnode->dirty = TRUE; + + if (top) + child = g_node_prepend_data (parent, mnode); + else + child = g_node_append_data (parent, mnode); + } + } + else + { + /* handle root node */ + if (self->private_data->root_node) + { + child = self->private_data->root_node; + if (strncmp (NODE_INFO (child)->name, childname, childname_length) != 0) + g_warning ("root node name '%s' doesn't match '%s'", + childname, NODE_INFO (child)->name); + if (NODE_INFO (child)->type != GTK_UI_MANAGER_ROOT) + g_warning ("base element must be of type ROOT"); + } + else if (create) + { + GtkUIManagerNode *mnode; + + mnode = g_chunk_new0 (GtkUIManagerNode, merge_node_chunk); + mnode->type = node_type; + mnode->name = g_strndup (childname, childname_length); + mnode->dirty = TRUE; + + child = self->private_data->root_node = g_node_new (mnode); + } + } + + return child; +} + +static GNode * +gtk_ui_manager_get_node (GtkUIManager *self, + const gchar *path, + GtkUIManagerNodeType node_type, + gboolean create) +{ + const gchar *pos, *end; + GNode *parent, *node; + + end = path + strlen (path); + pos = path; + parent = node = NULL; + while (pos < end) + { + const gchar *slash; + gsize length; + + slash = strchr (pos, '/'); + if (slash) + length = slash - pos; + else + length = strlen (pos); + + node = get_child_node (self, parent, pos, length, GTK_UI_MANAGER_UNDECIDED, + create, FALSE); + if (!node) + return NULL; + + pos += length + 1; /* move past the node name and the slash too */ + parent = node; + } + + if (NODE_INFO (node)->type == GTK_UI_MANAGER_UNDECIDED) + NODE_INFO (node)->type = node_type; + return node; +} + +static guint +gtk_ui_manager_next_merge_id (GtkUIManager *self) +{ + self->private_data->last_merge_id++; + + return self->private_data->last_merge_id; +} + +static void +gtk_ui_manager_node_prepend_ui_reference (GtkUIManagerNode *node, + guint merge_id, + GQuark action_quark) +{ + NodeUIReference *reference; + + reference = g_new (NodeUIReference, 1); + reference->action_quark = action_quark; + reference->merge_id = merge_id; + + /* Prepend the reference */ + node->uifiles = g_list_prepend (node->uifiles, reference); + + node->dirty = TRUE; +} + +static void +gtk_ui_manager_node_remove_ui_reference (GtkUIManagerNode *node, + guint merge_id) +{ + GList *p; + + for (p = node->uifiles; p != NULL; p = p->next) + { + NodeUIReference *reference = p->data; + + if (reference->merge_id == merge_id) + { + node->uifiles = g_list_remove_link (node->uifiles, p); + node->dirty = TRUE; + g_free (reference); + + break; + } + } +} + +/* -------------------- The UI file parser -------------------- */ + +typedef enum +{ + STATE_START, + STATE_ROOT, + STATE_MENU, + STATE_TOOLBAR, + STATE_POPUPS, + STATE_MENUITEM, + STATE_TOOLITEM, + STATE_END +} ParseState; + +typedef struct _ParseContext ParseContext; +struct _ParseContext +{ + ParseState state; + ParseState prev_state; + + GtkUIManager *self; + + GNode *current; + + guint merge_id; +}; + +static void +start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + ParseContext *ctx = user_data; + GtkUIManager *self = ctx->self; + + gint i; + const gchar *node_name; + GQuark verb_quark; + gboolean top; + + gboolean raise_error = TRUE; + gchar *error_attr = NULL; + + /* work out a name for this node. Either the name attribute, or + * element name */ + node_name = element_name; + verb_quark = 0; + top = FALSE; + for (i = 0; attribute_names[i] != NULL; i++) + { + if (!strcmp (attribute_names[i], "name")) + { + node_name = attribute_values[i]; + } + else if (!strcmp (attribute_names[i], "verb")) + { + verb_quark = g_quark_from_string (attribute_values[i]); + } + else if (!strcmp (attribute_names[i], "pos")) + { + top = !strcmp (attribute_values[i], "top"); + } + } + /* if no verb, then set it to the node's name */ + if (verb_quark == 0) + verb_quark = g_quark_from_string (node_name); + + switch (element_name[0]) + { + case 'R': + if (ctx->state == STATE_START && !strcmp (element_name, "Root")) + { + ctx->state = STATE_ROOT; + ctx->current = self->private_data->root_node; + raise_error = FALSE; + + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current), + ctx->merge_id, verb_quark); + } + break; + case 'm': + if (ctx->state == STATE_ROOT && !strcmp (element_name, "menu")) + { + ctx->state = STATE_MENU; + ctx->current = get_child_node (self, ctx->current, + node_name, strlen (node_name), + GTK_UI_MANAGER_MENUBAR, + TRUE, FALSE); + if (NODE_INFO (ctx->current)->action_name == 0) + NODE_INFO (ctx->current)->action_name = verb_quark; + + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current), + ctx->merge_id, verb_quark); + NODE_INFO (ctx->current)->dirty = TRUE; + + raise_error = FALSE; + } + else if (ctx->state == STATE_MENU && !strcmp (element_name, "menuitem")) + { + GNode *node; + + ctx->state = STATE_MENUITEM; + node = get_child_node (self, ctx->current, + node_name, strlen (node_name), + GTK_UI_MANAGER_MENUITEM, + TRUE, top); + if (NODE_INFO (node)->action_name == 0) + NODE_INFO (node)->action_name = verb_quark; + + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (node), + ctx->merge_id, verb_quark); + NODE_INFO (node)->dirty = TRUE; + + raise_error = FALSE; + } + break; + case 'd': + if (ctx->state == STATE_ROOT && !strcmp (element_name, "dockitem")) + { + ctx->state = STATE_TOOLBAR; + ctx->current = get_child_node (self, ctx->current, + node_name, strlen (node_name), + GTK_UI_MANAGER_TOOLBAR, + TRUE, FALSE); + if (NODE_INFO (ctx->current)->action_name == 0) + NODE_INFO (ctx->current)->action_name = verb_quark; + + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current), + ctx->merge_id, verb_quark); + NODE_INFO (ctx->current)->dirty = TRUE; + + raise_error = FALSE; + } + break; + case 'p': + if (ctx->state == STATE_ROOT && !strcmp (element_name, "popups")) + { + ctx->state = STATE_POPUPS; + ctx->current = get_child_node (self, ctx->current, + node_name, strlen (node_name), + GTK_UI_MANAGER_POPUPS, + TRUE, FALSE); + + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current), + ctx->merge_id, verb_quark); + NODE_INFO (ctx->current)->dirty = TRUE; + + raise_error = FALSE; + } + else if (ctx->state == STATE_POPUPS && !strcmp (element_name, "popup")) + { + ctx->state = STATE_MENU; + ctx->current = get_child_node (self, ctx->current, + node_name, strlen (node_name), + GTK_UI_MANAGER_MENU, + TRUE, FALSE); + if (NODE_INFO (ctx->current)->action_name == 0) + NODE_INFO (ctx->current)->action_name = verb_quark; + + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current), + ctx->merge_id, verb_quark); + NODE_INFO (ctx->current)->dirty = TRUE; + + raise_error = FALSE; + } + else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) && + !strcmp (element_name, "placeholder")) + { + if (ctx->state == STATE_MENU) + ctx->current = get_child_node (self, ctx->current, + node_name, strlen (node_name), + GTK_UI_MANAGER_MENU_PLACEHOLDER, + TRUE, top); + else + ctx->current = get_child_node (self, ctx->current, + node_name, strlen (node_name), + GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER, + TRUE, top); + + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current), + ctx->merge_id, verb_quark); + NODE_INFO (ctx->current)->dirty = TRUE; + + raise_error = FALSE; + } + break; + case 's': + if (ctx->state == STATE_MENU && !strcmp (element_name, "submenu")) + { + ctx->state = STATE_MENU; + ctx->current = get_child_node (self, ctx->current, + node_name, strlen (node_name), + GTK_UI_MANAGER_MENU, + TRUE, top); + if (NODE_INFO (ctx->current)->action_name == 0) + NODE_INFO (ctx->current)->action_name = verb_quark; + + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current), + ctx->merge_id, verb_quark); + NODE_INFO (ctx->current)->dirty = TRUE; + + raise_error = FALSE; + } + else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) && + !strcmp (element_name, "separator")) + { + GNode *node; + + if (ctx->state == STATE_MENU) + ctx->state = STATE_MENUITEM; + else + ctx->state = STATE_TOOLITEM; + node = get_child_node (self, ctx->current, + node_name, strlen (node_name), + GTK_UI_MANAGER_SEPARATOR, + TRUE, top); + if (NODE_INFO (node)->action_name == 0) + NODE_INFO (node)->action_name = verb_quark; + + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (node), + ctx->merge_id, verb_quark); + NODE_INFO (node)->dirty = TRUE; + + raise_error = FALSE; + } + break; + case 't': + if (ctx->state == STATE_TOOLBAR && !strcmp (element_name, "toolitem")) + { + GNode *node; + + ctx->state = STATE_TOOLITEM; + node = get_child_node (self, ctx->current, + node_name, strlen (node_name), + GTK_UI_MANAGER_TOOLITEM, + TRUE, top); + if (NODE_INFO (node)->action_name == 0) + NODE_INFO (node)->action_name = verb_quark; + + gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (node), + ctx->merge_id, verb_quark); + NODE_INFO (node)->dirty = TRUE; + + raise_error = FALSE; + } + break; + default: + break; + } + if (raise_error) + { + gint line_number, char_number; + + g_markup_parse_context_get_position (context, + &line_number, &char_number); + if (error_attr) + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, + _("Unknown attribute '%s' on line %d char %d"), + error_attr, + line_number, char_number); + else + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ELEMENT, + _("Unknown tag '%s' on line %d char %d"), + element_name, + line_number, char_number); + } +} + +static void +end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseContext *ctx = user_data; + GtkUIManager *self = ctx->self; + + switch (ctx->state) + { + case STATE_START: + g_warning ("shouldn't get any end tags in start state"); + /* should we GError here? */ + break; + case STATE_ROOT: + if (ctx->current != self->private_data->root_node) + g_warning ("we are in STATE_ROOT, but the current node isn't the root"); + ctx->current = NULL; + ctx->state = STATE_END; + break; + case STATE_MENU: + ctx->current = ctx->current->parent; + if (NODE_INFO (ctx->current)->type == GTK_UI_MANAGER_ROOT) /* menubar */ + ctx->state = STATE_ROOT; + else if (NODE_INFO (ctx->current)->type == GTK_UI_MANAGER_POPUPS) /* popup */ + ctx->state = STATE_POPUPS; + /* else, stay in STATE_MENU state */ + break; + case STATE_TOOLBAR: + ctx->current = ctx->current->parent; + /* we conditionalise this test, in case we are closing off a + * placeholder */ + if (NODE_INFO (ctx->current)->type == GTK_UI_MANAGER_ROOT) + ctx->state = STATE_ROOT; + /* else, stay in STATE_TOOLBAR state */ + break; + case STATE_POPUPS: + ctx->current = ctx->current->parent; + ctx->state = STATE_ROOT; + break; + case STATE_MENUITEM: + ctx->state = STATE_MENU; + break; + case STATE_TOOLITEM: + ctx->state = STATE_TOOLBAR; + break; + case STATE_END: + g_warning ("shouldn't get any end tags at this point"); + /* should do an error here */ + break; + } +} + +static void +cleanup (GMarkupParseContext *context, + GError *error, + gpointer user_data) +{ + ParseContext *ctx = user_data; + GtkUIManager *self = ctx->self; + + ctx->current = NULL; + /* should also walk through the tree and get rid of nodes related to + * this UI file's tag */ + + gtk_ui_manager_remove_ui (self, ctx->merge_id); +} + +static GMarkupParser ui_parser = { + start_element_handler, + end_element_handler, + NULL, + NULL, + cleanup +}; + + +/** + * gtk_ui_manager_add_ui_from_string: + * @self: a #GtkUIManager object + * @buffer: the string to parse + * @length: the length of @buffer (may be -1 if @buffer is nul-terminated) + * @error: return location for an error + * + * Parses a string containing a UI description and merge it with the current + * contents of @self. FIXME: describe the XML format. + * + * Return value: The merge id for the merged UI. The merge id can be used + * to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred, + * the return value is 0. + * + * Since: 2.4 + **/ +guint +gtk_ui_manager_add_ui_from_string (GtkUIManager *self, + const gchar *buffer, + gsize length, + GError **error) +{ + ParseContext ctx = { 0 }; + GMarkupParseContext *context; + gboolean res = TRUE; + + g_return_val_if_fail (GTK_IS_UI_MANAGER (self), FALSE); + g_return_val_if_fail (buffer != NULL, FALSE); + + ctx.state = STATE_START; + ctx.self = self; + ctx.current = NULL; + ctx.merge_id = gtk_ui_manager_next_merge_id (self); + + context = g_markup_parse_context_new (&ui_parser, 0, &ctx, NULL); + if (length < 0) + length = strlen (buffer); + + if (g_markup_parse_context_parse (context, buffer, length, error)) + { + if (!g_markup_parse_context_end_parse (context, error)) + res = FALSE; + } + else + res = FALSE; + + g_markup_parse_context_free (context); + + gtk_ui_manager_queue_update (self); + + if (res) + return ctx.merge_id; + + return 0; +} + +/** + * gtk_ui_manager_add_ui_from_file: + * @self: a #GtkUIManager object + * @filename: the name of the file to parse + * @error: return location for an error + * + * Parses a file containing a UI description and merge it with the current + * contents of @self. See gtk_ui_manager_add_ui_from_file(). + * + * Return value: The merge id for the merged UI. The merge id can be used + * to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred, + * the return value is 0. + * + * Since: 2.4 + **/ +guint +gtk_ui_manager_add_ui_from_file (GtkUIManager *self, + const gchar *filename, + GError **error) +{ + gchar *buffer; + gint length; + guint res; + + if (!g_file_get_contents (filename, &buffer, &length, error)) + return 0; + + res = gtk_ui_manager_add_ui_from_string (self, buffer, length, error); + g_free (buffer); + + return res; +} + +static gboolean +remove_ui (GNode *node, + gpointer user_data) +{ + guint merge_id = GPOINTER_TO_UINT (user_data); + + gtk_ui_manager_node_remove_ui_reference (NODE_INFO (node), merge_id); + + return FALSE; /* continue */ +} + +/** + * gtk_ui_manager_remove_ui: + * @self: a #GtkUIManager object + * @merge_id: a merge id as returned by gtk_ui_manager_add_ui_from_string() + * + * Unmerges the part of @self<!-- -->s content identified by @merge_id. + * + * Since: 2.4 + **/ +void +gtk_ui_manager_remove_ui (GtkUIManager *self, + guint merge_id) +{ + g_node_traverse (self->private_data->root_node, G_POST_ORDER, G_TRAVERSE_ALL, -1, + remove_ui, GUINT_TO_POINTER (merge_id)); + + gtk_ui_manager_queue_update (self); +} + +/* -------------------- Updates -------------------- */ + + +static GtkAction * +get_action_by_name (GtkUIManager *merge, + const char *action_name) +{ + GList *tmp; + + if (!action_name) + return NULL; + + /* lookup name */ + for (tmp = merge->private_data->action_groups; tmp != NULL; tmp = tmp->next) + { + GtkActionGroup *action_group = tmp->data; + GtkAction *action; + + action = gtk_action_group_get_action (action_group, action_name); + + if (action) + return action; + } + + return NULL; +} + +static gboolean +find_menu_position (GNode *node, + GtkWidget **menushell_p, + gint *pos_p) +{ + GtkWidget *menushell; + gint pos; + + g_return_val_if_fail (node != NULL, FALSE); + g_return_val_if_fail (NODE_INFO (node)->type == GTK_UI_MANAGER_MENU || + NODE_INFO (node)->type == GTK_UI_MANAGER_MENU_PLACEHOLDER || + NODE_INFO (node)->type == GTK_UI_MANAGER_MENUITEM || + NODE_INFO (node)->type == GTK_UI_MANAGER_SEPARATOR, + FALSE); + + /* first sibling -- look at parent */ + if (node->prev == NULL) + { + GNode *parent; + + parent = node->parent; + switch (NODE_INFO (parent)->type) + { + case GTK_UI_MANAGER_MENUBAR: + menushell = NODE_INFO (parent)->proxy; + pos = 0; + break; + case GTK_UI_MANAGER_MENU: + menushell = NODE_INFO (parent)->proxy; + if (GTK_IS_MENU_ITEM (menushell)) + menushell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menushell)); + pos = 0; + break; + case GTK_UI_MANAGER_MENU_PLACEHOLDER: + menushell = gtk_widget_get_parent (NODE_INFO (parent)->proxy); + g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE); + pos = g_list_index (GTK_MENU_SHELL (menushell)->children, + NODE_INFO (parent)->proxy) + 1; + break; + default: + g_warning("%s: bad parent node type %d", G_STRLOC, + NODE_INFO (parent)->type); + return FALSE; + } + } + else + { + GtkWidget *prev_child; + GNode *sibling; + + sibling = node->prev; + if (NODE_INFO (sibling)->type == GTK_UI_MANAGER_MENU_PLACEHOLDER) + prev_child = NODE_INFO (sibling)->extra; /* second Separator */ + else + prev_child = NODE_INFO (sibling)->proxy; + + g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE); + menushell = gtk_widget_get_parent (prev_child); + g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE); + + pos = g_list_index (GTK_MENU_SHELL (menushell)->children, prev_child) + 1; + } + + if (menushell_p) + *menushell_p = menushell; + if (pos_p) + *pos_p = pos; + + return TRUE; +} + +static gboolean +find_toolbar_position (GNode *node, + GtkWidget **toolbar_p, + gint *pos_p) +{ + GtkWidget *toolbar; + gint pos; + + g_return_val_if_fail (node != NULL, FALSE); + g_return_val_if_fail (NODE_INFO (node)->type == GTK_UI_MANAGER_TOOLBAR || + NODE_INFO (node)->type == GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER || + NODE_INFO (node)->type == GTK_UI_MANAGER_TOOLITEM || + NODE_INFO (node)->type == GTK_UI_MANAGER_SEPARATOR, + FALSE); + + /* first sibling -- look at parent */ + if (node->prev == NULL) + { + GNode *parent; + + parent = node->parent; + switch (NODE_INFO (parent)->type) + { + case GTK_UI_MANAGER_TOOLBAR: + toolbar = NODE_INFO (parent)->proxy; + pos = 0; + break; + case GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER: + toolbar = gtk_widget_get_parent (NODE_INFO (parent)->proxy); + g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE); + pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar), + GTK_TOOL_ITEM (NODE_INFO (parent)->proxy)) + 1; + break; + default: + g_warning ("%s: bad parent node type %d", G_STRLOC, + NODE_INFO (parent)->type); + return FALSE; + } + } + else + { + GtkWidget *prev_child; + GNode *sibling; + + sibling = node->prev; + if (NODE_INFO (sibling)->type == GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER) + prev_child = NODE_INFO (sibling)->extra; /* second Separator */ + else + prev_child = NODE_INFO (sibling)->proxy; + + g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE); + toolbar = gtk_widget_get_parent (prev_child); + g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE); + + pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar), + GTK_TOOL_ITEM (prev_child)) + 1; + } + + if (toolbar_p) + *toolbar_p = toolbar; + if (pos_p) + *pos_p = pos; + + return TRUE; +} + +static void +update_node (GtkUIManager *self, + GNode *node) +{ + GtkUIManagerNode *info; + GNode *child; + GtkAction *action; +#ifdef DEBUG_UI_MANAGER + GList *tmp; +#endif + + g_return_if_fail (node != NULL); + g_return_if_fail (NODE_INFO (node) != NULL); + + info = NODE_INFO (node); + +#ifdef DEBUG_UI_MANAGER + g_print ("update_node name=%s dirty=%d (", info->name, info->dirty); + for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next) + { + NodeUIReference *ref = tmp->data; + g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id); + if (tmp->next) + g_print (", "); + } + g_print (")\n"); +#endif + + if (NODE_INFO (node)->dirty) + { + const gchar *action_name; + NodeUIReference *ref; + + if (info->uifiles == NULL) { + /* We may need to remove this node. + * This must be done in post order + */ + goto recurse_children; + } + + ref = info->uifiles->data; + action_name = g_quark_to_string (ref->action_quark); + action = get_action_by_name (self, action_name); + + NODE_INFO (node)->dirty = FALSE; + + /* Check if the node doesn't have an action and must have an action */ + if (action == NULL && + info->type != GTK_UI_MANAGER_MENUBAR && + info->type != GTK_UI_MANAGER_TOOLBAR && + info->type != GTK_UI_MANAGER_SEPARATOR && + info->type != GTK_UI_MANAGER_MENU_PLACEHOLDER && + info->type != GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER) + { + /* FIXME: Should we warn here? */ + goto recurse_children; + } + + /* If the widget already has a proxy and the action hasn't changed, then + * we don't have to do anything. + */ + if (info->proxy != NULL && + action == info->action) + { + goto recurse_children; + } + + if (info->action) + g_object_unref (info->action); + info->action = action; + if (info->action) + g_object_ref (info->action); + + switch (info->type) + { + case GTK_UI_MANAGER_MENUBAR: + if (info->proxy == NULL) + { + info->proxy = gtk_menu_bar_new (); + gtk_widget_show (info->proxy); + g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy); + } + break; + case GTK_UI_MANAGER_MENU: + if (NODE_INFO (node->parent)->type == GTK_UI_MANAGER_POPUPS) + { + if (info->proxy == NULL) + { + GtkWidget *menu; + menu = gtk_menu_new (); + gtk_menu_set_accel_group (GTK_MENU (menu), self->private_data->accel_group); + info->proxy = menu; + } + } + else + { + GtkWidget *prev_submenu = NULL; + /* remove the proxy if it is of the wrong type ... */ + if (info->proxy && G_OBJECT_TYPE (info->proxy) != + GTK_ACTION_GET_CLASS (info->action)->menu_item_type) + { + prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy)); + if (prev_submenu) + { + g_object_ref (prev_submenu); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),NULL); + } + gtk_container_remove (GTK_CONTAINER (info->proxy->parent), + info->proxy); + info->proxy = NULL; + } + /* create proxy if needed ... */ + if (info->proxy == NULL) + { + GtkWidget *menushell; + gint pos; + + if (find_menu_position (node, &menushell, &pos)) + { + GtkWidget *menu; + info->proxy = gtk_action_create_menu_item (info->action); + menu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu); + gtk_menu_set_accel_group (GTK_MENU (menu), self->private_data->accel_group); + gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos); + } + } + else + { + gtk_action_connect_proxy (info->action, info->proxy); + } + if (prev_submenu) + { + gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), + prev_submenu); + g_object_unref (prev_submenu); + } + } + break; + case GTK_UI_MANAGER_UNDECIDED: + g_warning ("found 'undecided node!"); + break; + case GTK_UI_MANAGER_ROOT: + break; + case GTK_UI_MANAGER_TOOLBAR: + if (info->proxy == NULL) + { + info->proxy = gtk_toolbar_new (); + gtk_widget_show (info->proxy); + g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy); + } + break; + case GTK_UI_MANAGER_MENU_PLACEHOLDER: + /* create menu items for placeholders if necessary ... */ + if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) || + !GTK_IS_SEPARATOR_MENU_ITEM (info->extra)) + { + if (info->proxy) + gtk_container_remove (GTK_CONTAINER (info->proxy->parent), + info->proxy); + if (info->extra) + gtk_container_remove (GTK_CONTAINER (info->extra->parent), + info->extra); + info->proxy = NULL; + info->extra = NULL; + } + if (info->proxy == NULL) + { + GtkWidget *menushell; + gint pos; + + if (find_menu_position (node, &menushell, &pos)) + { + NODE_INFO (node)->proxy = gtk_separator_menu_item_new (); + gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), + NODE_INFO (node)->proxy, pos); + + NODE_INFO (node)->extra = gtk_separator_menu_item_new (); + gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), + NODE_INFO (node)->extra, pos+1); + } + } + break; + case GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER: + /* create toolbar items for placeholders if necessary ... */ + if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) || + !GTK_IS_SEPARATOR_TOOL_ITEM (info->extra)) + { + if (info->proxy) + gtk_container_remove (GTK_CONTAINER (info->proxy->parent), + info->proxy); + if (info->extra) + gtk_container_remove (GTK_CONTAINER (info->extra->parent), + info->extra); + info->proxy = NULL; + info->extra = NULL; + } + if (info->proxy == NULL) + { + GtkWidget *toolbar; + gint pos; + + if (find_toolbar_position (node, &toolbar, &pos)) + { + GtkToolItem *item; + + item = gtk_separator_tool_item_new (); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos); + NODE_INFO(node)->proxy = GTK_WIDGET (item); + + item = gtk_separator_tool_item_new (); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1); + NODE_INFO (node)->extra = GTK_WIDGET (item); + } + } + break; + case GTK_UI_MANAGER_POPUPS: + break; + case GTK_UI_MANAGER_MENUITEM: + /* remove the proxy if it is of the wrong type ... */ + if (info->proxy && G_OBJECT_TYPE (info->proxy) != + GTK_ACTION_GET_CLASS (info->action)->menu_item_type) + { + gtk_container_remove (GTK_CONTAINER (info->proxy->parent), + info->proxy); + info->proxy = NULL; + } + /* create proxy if needed ... */ + if (info->proxy == NULL) + { + GtkWidget *menushell; + gint pos; + + if (find_menu_position (node, &menushell, &pos)) + { + info->proxy = gtk_action_create_menu_item (info->action); + + gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), + info->proxy, pos); + } + } + else + { + gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL); + gtk_action_connect_proxy (info->action, info->proxy); + } + break; + case GTK_UI_MANAGER_TOOLITEM: + /* remove the proxy if it is of the wrong type ... */ + if (info->proxy && G_OBJECT_TYPE (info->proxy) != + GTK_ACTION_GET_CLASS (info->action)->toolbar_item_type) + { + gtk_container_remove (GTK_CONTAINER (info->proxy->parent), + info->proxy); + info->proxy = NULL; + } + /* create proxy if needed ... */ + if (info->proxy == NULL) + { + GtkWidget *toolbar; + gint pos; + + if (find_toolbar_position (node, &toolbar, &pos)) + { + info->proxy = gtk_action_create_tool_item (info->action); + + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), + GTK_TOOL_ITEM (info->proxy), pos); + } + } + else + { + gtk_action_connect_proxy (info->action, info->proxy); + } + break; + case GTK_UI_MANAGER_SEPARATOR: + if (NODE_INFO (node->parent)->type == GTK_UI_MANAGER_TOOLBAR || + NODE_INFO (node->parent)->type == GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER) + { + GtkWidget *toolbar; + gint pos; + + if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy)) + { + gtk_container_remove (GTK_CONTAINER (info->proxy->parent), + info->proxy); + info->proxy = NULL; + } + + if (find_toolbar_position (node, &toolbar, &pos)) + { + GtkToolItem *item = gtk_separator_tool_item_new (); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos); + info->proxy = GTK_WIDGET (item); + gtk_widget_show (info->proxy); + } + } + else + { + GtkWidget *menushell; + gint pos; + + if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy)) + { + gtk_container_remove (GTK_CONTAINER (info->proxy->parent), + info->proxy); + info->proxy = NULL; + } + + if (find_menu_position (node, &menushell, &pos)) + { + info->proxy = gtk_separator_menu_item_new (); + gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), + info->proxy, pos); + gtk_widget_show (info->proxy); + } + } + break; + } + + /* if this node has a widget, but it is the wrong type, remove it */ + } + + recurse_children: + /* process children */ + child = node->children; + while (child) + { + GNode *current; + + current = child; + child = current->next; + update_node (self, current); + } + + /* handle cleanup of dead nodes */ + if (node->children == NULL && NODE_INFO (node)->uifiles == NULL) + { + if (NODE_INFO (node)->proxy) + gtk_widget_destroy (NODE_INFO (node)->proxy); + if ((NODE_INFO (node)->type == GTK_UI_MANAGER_MENU_PLACEHOLDER || + NODE_INFO (node)->type == GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER) && + NODE_INFO (node)->extra) + gtk_widget_destroy (NODE_INFO (node)->extra); + g_chunk_free (NODE_INFO (node), merge_node_chunk); + g_node_destroy (node); + } +} + +static gboolean +do_updates (GtkUIManager *self) +{ + /* this function needs to check through the tree for dirty nodes. + * For such nodes, it needs to do the following: + * + * 1) check if they are referenced by any loaded UI files anymore. + * In which case, the proxy widget should be destroyed, unless + * there are any subnodes. + * + * 2) lookup the action for this node again. If it is different to + * the current one (or if no previous action has been looked up), + * the proxy is reconnected to the new action (or a new proxy widget + * is created and added to the parent container). + */ + + update_node (self, self->private_data->root_node); + + self->private_data->update_tag = 0; + + return FALSE; +} + +static void +gtk_ui_manager_queue_update (GtkUIManager *self) +{ + if (self->private_data->update_tag != 0) + return; + + self->private_data->update_tag = g_idle_add ((GSourceFunc)do_updates, self); +} + +static void +gtk_ui_manager_ensure_update (GtkUIManager *self) +{ + if (self->private_data->update_tag != 0) + { + g_source_remove (self->private_data->update_tag); + do_updates (self); + } +} + +static gboolean +dirty_traverse_func (GNode *node, + gpointer data) +{ + NODE_INFO (node)->dirty = TRUE; + return FALSE; +} + +static void +gtk_ui_manager_dirty_all (GtkUIManager *self) +{ + g_node_traverse (self->private_data->root_node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + dirty_traverse_func, NULL); + gtk_ui_manager_queue_update (self); +} + +static const gchar *open_tag_format[] = { + "%*s<UNDECIDED>\n", + "%*s<Root>\n", + "%*s<menu name=\"%s\">\n", + "%*s<submenu name=\"%s\" verb=\"%s\">\n", + "%*s<dockitem name=\"%s\">\n", + "%*s<placeholder name=\"%s\">\n", + "%*s<placeholder name=\"%s\">\n", + "%*s<popups>\n", + "%*s<menuitem name=\"%s\" verb=\"%s\"/>\n", + "%*s<toolitem name=\"%s\" verb=\"%s\"/>\n", + "%*s<separator/>\n", + "%*s<popup name=\"%s\">\n" +}; + +static const gchar *close_tag_format[] = { + "%*s</UNDECIDED>\n", + "%*s</Root>\n", + "%*s</menu>\n", + "%*s</submenu>\n", + "%*s</dockitem>\n", + "%*s</placeholder>\n", + "%*s</placeholder>\n", + "%*s</popups>\n", + "", + "", + "", + "%*s</popup>\n" +}; + +static void +print_node (GtkUIManager *self, + GNode *node, + gint indent_level, + GString *buffer) +{ + GtkUIManagerNode *mnode; + GNode *child; + guint type; + + mnode = node->data; + if (mnode->type == GTK_UI_MANAGER_MENU && + NODE_INFO (node->parent)->type == GTK_UI_MANAGER_POPUPS) + type = GTK_UI_MANAGER_SEPARATOR + 1; + else + type = mnode->type; + + g_string_append_printf (buffer, open_tag_format[type], + indent_level, "", + mnode->name, + g_quark_to_string (mnode->action_name)); + + for (child = node->children; child != NULL; child = child->next) + print_node (self, child, indent_level + 2, buffer); + + g_string_append_printf (buffer, close_tag_format[type], + indent_level, ""); + +} + +/** + * gtk_ui_manager_get_ui: + * @self: a #GtkUIManager + * + * Creates an XML representation of the merged ui. + * + * Return value: A newly allocated string containing an XML representation of + * the merged ui. + * + * Since: 2.4 + **/ +gchar* +gtk_ui_manager_get_ui (GtkUIManager *self) +{ + GString *buffer; + + buffer = g_string_new (NULL); + + gtk_ui_manager_ensure_update (self); + + print_node (self, self->private_data->root_node, 0, buffer); + + return g_string_free (buffer, FALSE); +} |