2 * GTK - The GIMP Toolkit
3 * Copyright (C) 1998, 1999 Red Hat, Inc.
6 * This Library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This Library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with the Gnome Library; see the file COPYING.LIB. If not,
18 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
23 * Author: James Henstridge <james@daa.com.au>
25 * Modified by the GTK+ Team and others 2003. See the AUTHORS
26 * file for a list of people on the GTK+ Team. See the ChangeLog
27 * files for a list of changes. These files are distributed with
28 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
34 #include "gtkuimanager.h"
35 #include "gtktoolbar.h"
36 #include "gtkseparatortoolitem.h"
37 #include "gtkmenushell.h"
39 #include "gtkmenubar.h"
40 #include "gtkseparatormenuitem.h"
41 #include "gtktearoffmenuitem.h"
44 #undef DEBUG_UI_MANAGER
48 GTK_UI_MANAGER_UNDECIDED,
50 GTK_UI_MANAGER_MENUBAR,
52 GTK_UI_MANAGER_TOOLBAR,
53 GTK_UI_MANAGER_MENU_PLACEHOLDER,
54 GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER,
56 GTK_UI_MANAGER_MENUITEM,
57 GTK_UI_MANAGER_TOOLITEM,
58 GTK_UI_MANAGER_SEPARATOR,
59 } GtkUIManagerNodeType;
62 typedef struct _GtkUIManagerNode GtkUIManagerNode;
64 struct _GtkUIManagerNode {
65 GtkUIManagerNodeType type;
72 GtkWidget *extra; /*GtkMenu for submenus, second separator for placeholders*/
79 #define GTK_UI_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_UI_MANAGER, GtkUIManagerPrivate))
81 struct _GtkUIManagerPrivate
83 GtkAccelGroup *accel_group;
92 gboolean add_tearoffs;
95 #define NODE_INFO(node) ((GtkUIManagerNode *)node->data)
97 typedef struct _NodeUIReference NodeUIReference;
99 struct _NodeUIReference
105 static void gtk_ui_manager_class_init (GtkUIManagerClass *class);
106 static void gtk_ui_manager_init (GtkUIManager *self);
107 static void gtk_ui_manager_set_property (GObject *object,
111 static void gtk_ui_manager_get_property (GObject *object,
115 static void gtk_ui_manager_queue_update (GtkUIManager *self);
116 static void gtk_ui_manager_dirty_all (GtkUIManager *self);
118 static GNode *get_child_node (GtkUIManager *self,
120 const gchar *childname,
121 gint childname_length,
122 GtkUIManagerNodeType node_type,
125 static GNode *gtk_ui_manager_get_node (GtkUIManager *self,
127 GtkUIManagerNodeType node_type,
129 static guint gtk_ui_manager_next_merge_id (GtkUIManager *self);
131 static void gtk_ui_manager_node_prepend_ui_reference (GtkUIManagerNode *node,
133 GQuark action_quark);
134 static void gtk_ui_manager_node_remove_ui_reference (GtkUIManagerNode *node,
136 static void gtk_ui_manager_ensure_update (GtkUIManager *self);
152 static guint merge_signals[LAST_SIGNAL] = { 0 };
154 static GMemChunk *merge_node_chunk = NULL;
157 gtk_ui_manager_get_type (void)
159 static GtkType type = 0;
163 static const GTypeInfo type_info =
165 sizeof (GtkUIManagerClass),
166 (GBaseInitFunc) NULL,
167 (GBaseFinalizeFunc) NULL,
168 (GClassInitFunc) gtk_ui_manager_class_init,
169 (GClassFinalizeFunc) NULL,
172 sizeof (GtkUIManager),
174 (GInstanceInitFunc) gtk_ui_manager_init,
177 type = g_type_register_static (G_TYPE_OBJECT,
185 gtk_ui_manager_class_init (GtkUIManagerClass *klass)
187 GObjectClass *gobject_class;
189 gobject_class = G_OBJECT_CLASS (klass);
191 if (!merge_node_chunk)
192 merge_node_chunk = g_mem_chunk_create (GtkUIManagerNode, 64,
195 gobject_class->set_property = gtk_ui_manager_set_property;
196 gobject_class->get_property = gtk_ui_manager_get_property;
199 * GtkUIManager:add-tearoffs:
201 * The add-tearoffs property controls whether generated menus
202 * have tearoff menu items.
204 * Note that this only affects regular menus. Generated popup
205 * menus never have tearoff menu items.
209 g_object_class_install_property (gobject_class,
211 g_param_spec_boolean ("add_tearoffs",
212 _("Add tearoffs to menus"),
213 _("Whether tearoff menu items should be added to menus"),
215 G_PARAM_READABLE | G_PARAM_WRITABLE));
218 * GtkUIManager::add-widget:
219 * @merge: a #GtkUIManager
220 * @widget: the added widget
222 * The add_widget signal is emitted for each generated menubar and toolbar.
223 * It is not emitted for generated popup menus, which can be obtained by
224 * gtk_ui_manager_get_widget().
228 merge_signals[ADD_WIDGET] =
229 g_signal_new ("add_widget",
230 G_OBJECT_CLASS_TYPE (klass),
231 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
232 G_STRUCT_OFFSET (GtkUIManagerClass, add_widget), NULL, NULL,
233 g_cclosure_marshal_VOID__OBJECT,
238 * GtkUIManager::changed:
239 * @merge: a #GtkUIManager
241 * The changed signal is emitted whenever the merged UI changes.
245 merge_signals[CHANGED] =
246 g_signal_new ("changed",
247 G_OBJECT_CLASS_TYPE (klass),
248 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
249 G_STRUCT_OFFSET (GtkUIManagerClass, changed), NULL, NULL,
250 g_cclosure_marshal_VOID__VOID,
253 g_type_class_add_private (gobject_class, sizeof (GtkUIManagerPrivate));
258 gtk_ui_manager_init (GtkUIManager *self)
263 self->private_data = GTK_UI_MANAGER_GET_PRIVATE (self);
265 self->private_data->accel_group = gtk_accel_group_new ();
267 self->private_data->root_node = NULL;
268 self->private_data->action_groups = NULL;
270 self->private_data->last_merge_id = 0;
271 self->private_data->add_tearoffs = FALSE;
274 merge_id = gtk_ui_manager_next_merge_id (self);
275 node = get_child_node (self, NULL, "ui", 4,
276 GTK_UI_MANAGER_ROOT, TRUE, FALSE);
277 gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (node), merge_id, 0);
281 gtk_ui_manager_set_property (GObject *object,
286 GtkUIManager *self = GTK_UI_MANAGER (object);
290 case PROP_ADD_TEAROFFS:
291 gtk_ui_manager_set_add_tearoffs (self, g_value_get_boolean (value));
294 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
300 gtk_ui_manager_get_property (GObject *object,
305 GtkUIManager *self = GTK_UI_MANAGER (object);
309 case PROP_ADD_TEAROFFS:
310 g_value_set_boolean (value, self->private_data->add_tearoffs);
313 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
320 * gtk_ui_manager_new:
322 * Creates a new ui manager object.
324 * Return value: a new ui manager object.
329 gtk_ui_manager_new (void)
331 return g_object_new (GTK_TYPE_UI_MANAGER, NULL);
336 * gtk_ui_manager_get_add_tearoffs:
337 * @self: a #GtkUIManager
339 * Returns whether menus generated by this #GtkUIManager
340 * will have tearoff menu items.
342 * Return value: whether tearoff menu items are added
347 gtk_ui_manager_get_add_tearoffs (GtkUIManager *self)
349 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), FALSE);
351 return self->private_data->add_tearoffs;
356 * gtk_ui_manager_set_add_tearoffs:
357 * @self: a #GtkUIManager
358 * @add_tearoffs: whether tearoff menu items are added
360 * Sets the add_tearoffs property, which controls whether menus
361 * generated by this #GtkUIManager will have tearoff menu items.
363 * Note that this only affects regular menus. Generated popup
364 * menus never have tearoff menu items.
369 gtk_ui_manager_set_add_tearoffs (GtkUIManager *self,
370 gboolean add_tearoffs)
372 g_return_if_fail (GTK_IS_UI_MANAGER (self));
374 add_tearoffs = add_tearoffs != FALSE;
376 if (add_tearoffs != self->private_data->add_tearoffs)
378 self->private_data->add_tearoffs = add_tearoffs;
380 /* dirty all nodes */
381 gtk_ui_manager_dirty_all (self);
383 g_object_notify (G_OBJECT (self), "add_tearoffs");
388 * gtk_ui_manager_insert_action_group:
389 * @self: a #GtkUIManager object
390 * @action_group: the action group to be inserted
391 * @pos: the position at which the group will be inserted.
393 * Inserts an action group into the list of action groups associated
399 gtk_ui_manager_insert_action_group (GtkUIManager *self,
400 GtkActionGroup *action_group,
403 g_return_if_fail (GTK_IS_UI_MANAGER (self));
404 g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
405 g_return_if_fail (g_list_find (self->private_data->action_groups,
406 action_group) == NULL);
408 g_object_ref (action_group);
409 self->private_data->action_groups =
410 g_list_insert (self->private_data->action_groups, action_group, pos);
412 /* dirty all nodes, as action bindings may change */
413 gtk_ui_manager_dirty_all (self);
417 * gtk_ui_manager_remove_action_group:
418 * @self: a #GtkUIManager object
419 * @action_group: the action group to be removed
421 * Removes an action group from the list of action groups associated
427 gtk_ui_manager_remove_action_group (GtkUIManager *self,
428 GtkActionGroup *action_group)
430 g_return_if_fail (GTK_IS_UI_MANAGER (self));
431 g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
432 g_return_if_fail (g_list_find (self->private_data->action_groups,
433 action_group) != NULL);
435 self->private_data->action_groups =
436 g_list_remove (self->private_data->action_groups, action_group);
437 g_object_unref (action_group);
439 /* dirty all nodes, as action bindings may change */
440 gtk_ui_manager_dirty_all (self);
444 * gtk_ui_manager_get_action_groups:
445 * @self: a #GtkUIManager object
447 * Returns the list of action groups associated with @self.
449 * Return value: a #GList of action groups. The list is owned by GTK+
450 * and should not be modified.
455 gtk_ui_manager_get_action_groups (GtkUIManager *self)
457 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
459 return self->private_data->action_groups;
463 * gtk_ui_manager_get_accel_group:
464 * @self: a #GtkUIManager object
466 * Returns the #GtkAccelGroup associated with @self.
468 * Return value: the #GtkAccelGroup.
473 gtk_ui_manager_get_accel_group (GtkUIManager *self)
475 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
477 return self->private_data->accel_group;
481 * gtk_ui_manager_get_widget:
482 * @self: a #GtkUIManager
485 * Looks up a widget by following a path. The path consists of the names
486 * specified in the XML description of the UI. separated by '/'. Elements which
487 * don't have a name attribute in the XML (e.g. <popup>) can be addressed
488 * by their XML element name (e.g. "popup"). The root element (<ui>) can
489 * be omitted in the path.
491 * Return value: the widget found by following the path, or %NULL if no widget
497 gtk_ui_manager_get_widget (GtkUIManager *self,
502 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
504 /* ensure that there are no pending updates before we get the
506 gtk_ui_manager_ensure_update (self);
508 node = gtk_ui_manager_get_node (self, path, GTK_UI_MANAGER_UNDECIDED, FALSE);
513 return NODE_INFO (node)->proxy;
517 * gtk_ui_manager_get_action:
518 * @self: a #GtkUIManager
521 * Looks up an action by following a path. The path consists of the names
522 * specified in the XML description of the UI. separated by '/'. Elements
523 * which don't have a name attribute in the XML (e.g. <popup>) can be
524 * addressed by their XML element name (e.g. "popup"). The root element
525 * (<ui>) can be omitted in the path.
527 * Return value: the action whose proxy widget is found by following the path,
528 * or %NULL if no widget was found.
533 gtk_ui_manager_get_action (GtkUIManager *self,
538 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
540 /* ensure that there are no pending updates before we get
542 gtk_ui_manager_ensure_update (self);
544 node = gtk_ui_manager_get_node (self, path, GTK_UI_MANAGER_UNDECIDED, FALSE);
549 return NODE_INFO (node)->action;
553 get_child_node (GtkUIManager *self,
555 const gchar *childname,
556 gint childname_length,
557 GtkUIManagerNodeType node_type,
563 g_return_val_if_fail (parent == NULL ||
564 (NODE_INFO (parent)->type != GTK_UI_MANAGER_MENUITEM &&
565 NODE_INFO (parent)->type != GTK_UI_MANAGER_TOOLITEM),
572 for (child = parent->children; child != NULL; child = child->next)
574 if (strlen (NODE_INFO (child)->name) == childname_length &&
575 !strncmp (NODE_INFO (child)->name, childname, childname_length))
577 /* if undecided about node type, set it */
578 if (NODE_INFO (child)->type == GTK_UI_MANAGER_UNDECIDED)
579 NODE_INFO (child)->type = node_type;
581 /* warn about type mismatch */
582 if (NODE_INFO (child)->type != GTK_UI_MANAGER_UNDECIDED &&
583 node_type != GTK_UI_MANAGER_UNDECIDED &&
584 NODE_INFO (child)->type != node_type)
585 g_warning ("node type doesn't match %d (%s is type %d)",
587 NODE_INFO (child)->name,
588 NODE_INFO (child)->type);
594 if (!child && create)
596 GtkUIManagerNode *mnode;
598 mnode = g_chunk_new0 (GtkUIManagerNode, merge_node_chunk);
599 mnode->type = node_type;
600 mnode->name = g_strndup (childname, childname_length);
604 child = g_node_prepend_data (parent, mnode);
606 child = g_node_append_data (parent, mnode);
611 /* handle root node */
612 if (self->private_data->root_node)
614 child = self->private_data->root_node;
615 if (strncmp (NODE_INFO (child)->name, childname, childname_length) != 0)
616 g_warning ("root node name '%s' doesn't match '%s'",
617 childname, NODE_INFO (child)->name);
618 if (NODE_INFO (child)->type != GTK_UI_MANAGER_ROOT)
619 g_warning ("base element must be of type ROOT");
623 GtkUIManagerNode *mnode;
625 mnode = g_chunk_new0 (GtkUIManagerNode, merge_node_chunk);
626 mnode->type = node_type;
627 mnode->name = g_strndup (childname, childname_length);
630 child = self->private_data->root_node = g_node_new (mnode);
638 gtk_ui_manager_get_node (GtkUIManager *self,
640 GtkUIManagerNodeType node_type,
643 const gchar *pos, *end;
644 GNode *parent, *node;
646 end = path + strlen (path);
648 parent = node = NULL;
654 slash = strchr (pos, '/');
656 length = slash - pos;
658 length = strlen (pos);
660 node = get_child_node (self, parent, pos, length, GTK_UI_MANAGER_UNDECIDED,
665 pos += length + 1; /* move past the node name and the slash too */
669 if (NODE_INFO (node)->type == GTK_UI_MANAGER_UNDECIDED)
670 NODE_INFO (node)->type = node_type;
675 gtk_ui_manager_next_merge_id (GtkUIManager *self)
677 self->private_data->last_merge_id++;
679 return self->private_data->last_merge_id;
683 gtk_ui_manager_node_prepend_ui_reference (GtkUIManagerNode *node,
687 NodeUIReference *reference;
689 reference = g_new (NodeUIReference, 1);
690 reference->action_quark = action_quark;
691 reference->merge_id = merge_id;
693 /* Prepend the reference */
694 node->uifiles = g_list_prepend (node->uifiles, reference);
700 gtk_ui_manager_node_remove_ui_reference (GtkUIManagerNode *node,
705 for (p = node->uifiles; p != NULL; p = p->next)
707 NodeUIReference *reference = p->data;
709 if (reference->merge_id == merge_id)
711 node->uifiles = g_list_remove_link (node->uifiles, p);
720 /* -------------------- The UI file parser -------------------- */
733 typedef struct _ParseContext ParseContext;
737 ParseState prev_state;
747 start_element_handler (GMarkupParseContext *context,
748 const gchar *element_name,
749 const gchar **attribute_names,
750 const gchar **attribute_values,
754 ParseContext *ctx = user_data;
755 GtkUIManager *self = ctx->self;
758 const gchar *node_name;
763 gboolean raise_error = TRUE;
764 gchar *error_attr = NULL;
770 for (i = 0; attribute_names[i] != NULL; i++)
772 if (!strcmp (attribute_names[i], "name"))
774 node_name = attribute_values[i];
776 else if (!strcmp (attribute_names[i], "action"))
778 action = attribute_values[i];
779 action_quark = g_quark_from_string (attribute_values[i]);
781 else if (!strcmp (attribute_names[i], "pos"))
783 top = !strcmp (attribute_values[i], "top");
787 /* Work out a name for this node. Either the name attribute, or
788 * the action, or the element name */
789 if (node_name == NULL)
794 node_name = element_name;
797 switch (element_name[0])
800 if (ctx->state == STATE_START && !strcmp (element_name, "ui"))
802 ctx->state = STATE_ROOT;
803 ctx->current = self->private_data->root_node;
806 gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current),
807 ctx->merge_id, action_quark);
811 if (ctx->state == STATE_ROOT && !strcmp (element_name, "menubar"))
813 ctx->state = STATE_MENU;
814 ctx->current = get_child_node (self, ctx->current,
815 node_name, strlen (node_name),
816 GTK_UI_MANAGER_MENUBAR,
818 if (NODE_INFO (ctx->current)->action_name == 0)
819 NODE_INFO (ctx->current)->action_name = action_quark;
821 gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current),
822 ctx->merge_id, action_quark);
823 NODE_INFO (ctx->current)->dirty = TRUE;
827 else if (ctx->state == STATE_MENU && !strcmp (element_name, "menu"))
829 ctx->current = get_child_node (self, ctx->current,
830 node_name, strlen (node_name),
833 if (NODE_INFO (ctx->current)->action_name == 0)
834 NODE_INFO (ctx->current)->action_name = action_quark;
836 gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current),
837 ctx->merge_id, action_quark);
838 NODE_INFO (ctx->current)->dirty = TRUE;
842 else if (ctx->state == STATE_MENU && !strcmp (element_name, "menuitem"))
846 ctx->state = STATE_MENUITEM;
847 node = get_child_node (self, ctx->current,
848 node_name, strlen (node_name),
849 GTK_UI_MANAGER_MENUITEM,
851 if (NODE_INFO (node)->action_name == 0)
852 NODE_INFO (node)->action_name = action_quark;
854 gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (node),
855 ctx->merge_id, action_quark);
856 NODE_INFO (node)->dirty = TRUE;
862 if (ctx->state == STATE_ROOT && !strcmp (element_name, "popup"))
864 ctx->state = STATE_MENU;
865 ctx->current = get_child_node (self, ctx->current,
866 node_name, strlen (node_name),
867 GTK_UI_MANAGER_POPUP,
869 if (NODE_INFO (ctx->current)->action_name == 0)
870 NODE_INFO (ctx->current)->action_name = action_quark;
872 gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current),
873 ctx->merge_id, action_quark);
874 NODE_INFO (ctx->current)->dirty = TRUE;
878 else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
879 !strcmp (element_name, "placeholder"))
881 if (ctx->state == STATE_TOOLBAR)
882 ctx->current = get_child_node (self, ctx->current,
883 node_name, strlen (node_name),
884 GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER,
887 ctx->current = get_child_node (self, ctx->current,
888 node_name, strlen (node_name),
889 GTK_UI_MANAGER_MENU_PLACEHOLDER,
892 gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current),
893 ctx->merge_id, action_quark);
894 NODE_INFO (ctx->current)->dirty = TRUE;
900 if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
901 !strcmp (element_name, "separator"))
905 if (ctx->state == STATE_TOOLBAR)
906 ctx->state = STATE_TOOLITEM;
908 ctx->state = STATE_MENUITEM;
909 node = get_child_node (self, ctx->current,
910 node_name, strlen (node_name),
911 GTK_UI_MANAGER_SEPARATOR,
913 if (NODE_INFO (node)->action_name == 0)
914 NODE_INFO (node)->action_name = action_quark;
916 gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (node),
917 ctx->merge_id, action_quark);
918 NODE_INFO (node)->dirty = TRUE;
924 if (ctx->state == STATE_ROOT && !strcmp (element_name, "toolbar"))
926 ctx->state = STATE_TOOLBAR;
927 ctx->current = get_child_node (self, ctx->current,
928 node_name, strlen (node_name),
929 GTK_UI_MANAGER_TOOLBAR,
931 if (NODE_INFO (ctx->current)->action_name == 0)
932 NODE_INFO (ctx->current)->action_name = action_quark;
934 gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (ctx->current),
935 ctx->merge_id, action_quark);
936 NODE_INFO (ctx->current)->dirty = TRUE;
940 else if (ctx->state == STATE_TOOLBAR && !strcmp (element_name, "toolitem"))
944 ctx->state = STATE_TOOLITEM;
945 node = get_child_node (self, ctx->current,
946 node_name, strlen (node_name),
947 GTK_UI_MANAGER_TOOLITEM,
949 if (NODE_INFO (node)->action_name == 0)
950 NODE_INFO (node)->action_name = action_quark;
952 gtk_ui_manager_node_prepend_ui_reference (NODE_INFO (node),
953 ctx->merge_id, action_quark);
954 NODE_INFO (node)->dirty = TRUE;
964 gint line_number, char_number;
966 g_markup_parse_context_get_position (context,
967 &line_number, &char_number);
971 G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
972 _("Unknown attribute '%s' on line %d char %d"),
974 line_number, char_number);
978 G_MARKUP_ERROR_UNKNOWN_ELEMENT,
979 _("Unknown tag '%s' on line %d char %d"),
981 line_number, char_number);
986 end_element_handler (GMarkupParseContext *context,
987 const gchar *element_name,
991 ParseContext *ctx = user_data;
992 GtkUIManager *self = ctx->self;
997 g_warning ("shouldn't get any end tags in start state");
998 /* should we GError here? */
1001 if (ctx->current != self->private_data->root_node)
1002 g_warning ("we are in STATE_ROOT, but the current node isn't the root");
1003 ctx->current = NULL;
1004 ctx->state = STATE_END;
1007 ctx->current = ctx->current->parent;
1008 if (NODE_INFO (ctx->current)->type == GTK_UI_MANAGER_ROOT)
1009 ctx->state = STATE_ROOT;
1010 /* else, stay in same state */
1013 ctx->current = ctx->current->parent;
1014 /* we conditionalise this test, in case we are closing off a
1016 if (NODE_INFO (ctx->current)->type == GTK_UI_MANAGER_ROOT)
1017 ctx->state = STATE_ROOT;
1018 /* else, stay in STATE_TOOLBAR state */
1020 case STATE_MENUITEM:
1021 ctx->state = STATE_MENU;
1023 case STATE_TOOLITEM:
1024 ctx->state = STATE_TOOLBAR;
1027 g_warning ("shouldn't get any end tags at this point");
1028 /* should do an error here */
1034 cleanup (GMarkupParseContext *context,
1038 ParseContext *ctx = user_data;
1039 GtkUIManager *self = ctx->self;
1041 ctx->current = NULL;
1042 /* should also walk through the tree and get rid of nodes related to
1043 * this UI file's tag */
1045 gtk_ui_manager_remove_ui (self, ctx->merge_id);
1048 static GMarkupParser ui_parser = {
1049 start_element_handler,
1050 end_element_handler,
1058 xml_isspace (char c)
1060 return c == ' ' || c == '\t' || c == '\n' || c == '\r';
1064 add_ui_from_string (GtkUIManager *self,
1065 const gchar *buffer,
1067 gboolean needs_root,
1070 ParseContext ctx = { 0 };
1071 GMarkupParseContext *context;
1073 ctx.state = STATE_START;
1076 ctx.merge_id = gtk_ui_manager_next_merge_id (self);
1078 context = g_markup_parse_context_new (&ui_parser, 0, &ctx, NULL);
1081 if (!g_markup_parse_context_parse (context, "<ui>", -1, error))
1084 if (!g_markup_parse_context_parse (context, buffer, length, error))
1088 if (!g_markup_parse_context_parse (context, "</ui>", -1, error))
1091 if (!g_markup_parse_context_end_parse (context, error))
1094 g_markup_parse_context_free (context);
1096 gtk_ui_manager_queue_update (self);
1098 g_signal_emit (self, merge_signals[CHANGED], 0);
1100 return ctx.merge_id;
1104 g_markup_parse_context_free (context);
1110 * gtk_ui_manager_add_ui_from_string:
1111 * @self: a #GtkUIManager object
1112 * @buffer: the string to parse
1113 * @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
1114 * @error: return location for an error
1116 * Parses a string containing a <link linkend="XML-UI">UI description</link> and
1117 * merges it with the current contents of @self. An enclosing <ui> </ui>
1118 * element is added if it is missing.
1120 * Return value: The merge id for the merged UI. The merge id can be used
1121 * to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1122 * the return value is 0.
1127 gtk_ui_manager_add_ui_from_string (GtkUIManager *self,
1128 const gchar *buffer,
1132 gboolean needs_root = TRUE;
1136 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), FALSE);
1137 g_return_val_if_fail (buffer != NULL, FALSE);
1140 length = strlen (buffer);
1143 end = buffer + length;
1144 while (p != end && xml_isspace (*p))
1147 if (end - p >= 4 && strncmp (p, "<ui>", 4) == 0)
1150 return add_ui_from_string (self, buffer, length, needs_root, error);
1154 * gtk_ui_manager_add_ui_from_file:
1155 * @self: a #GtkUIManager object
1156 * @filename: the name of the file to parse
1157 * @error: return location for an error
1159 * Parses a file containing a <link linkend="XML-UI">UI description</link> and
1160 * merges it with the current contents of @self.
1162 * Return value: The merge id for the merged UI. The merge id can be used
1163 * to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1164 * the return value is 0.
1169 gtk_ui_manager_add_ui_from_file (GtkUIManager *self,
1170 const gchar *filename,
1177 if (!g_file_get_contents (filename, &buffer, &length, error))
1180 res = add_ui_from_string (self, buffer, length, FALSE, error);
1187 remove_ui (GNode *node,
1190 guint merge_id = GPOINTER_TO_UINT (user_data);
1192 gtk_ui_manager_node_remove_ui_reference (NODE_INFO (node), merge_id);
1194 return FALSE; /* continue */
1198 * gtk_ui_manager_remove_ui:
1199 * @self: a #GtkUIManager object
1200 * @merge_id: a merge id as returned by gtk_ui_manager_add_ui_from_string()
1202 * Unmerges the part of @self<!-- -->s content identified by @merge_id.
1207 gtk_ui_manager_remove_ui (GtkUIManager *self,
1210 g_node_traverse (self->private_data->root_node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
1211 remove_ui, GUINT_TO_POINTER (merge_id));
1213 gtk_ui_manager_queue_update (self);
1215 g_signal_emit (self, merge_signals[CHANGED], 0);
1218 /* -------------------- Updates -------------------- */
1222 get_action_by_name (GtkUIManager *merge,
1223 const char *action_name)
1231 for (tmp = merge->private_data->action_groups; tmp != NULL; tmp = tmp->next)
1233 GtkActionGroup *action_group = tmp->data;
1236 action = gtk_action_group_get_action (action_group, action_name);
1246 find_menu_position (GNode *node,
1247 GtkWidget **menushell_p,
1250 GtkWidget *menushell;
1253 g_return_val_if_fail (node != NULL, FALSE);
1254 g_return_val_if_fail (NODE_INFO (node)->type == GTK_UI_MANAGER_MENU ||
1255 NODE_INFO (node)->type == GTK_UI_MANAGER_POPUP ||
1256 NODE_INFO (node)->type == GTK_UI_MANAGER_MENU_PLACEHOLDER ||
1257 NODE_INFO (node)->type == GTK_UI_MANAGER_MENUITEM ||
1258 NODE_INFO (node)->type == GTK_UI_MANAGER_SEPARATOR,
1261 /* first sibling -- look at parent */
1262 if (node->prev == NULL)
1267 parent = node->parent;
1268 switch (NODE_INFO (parent)->type)
1270 case GTK_UI_MANAGER_MENUBAR:
1271 case GTK_UI_MANAGER_POPUP:
1272 menushell = NODE_INFO (parent)->proxy;
1275 case GTK_UI_MANAGER_MENU:
1276 menushell = NODE_INFO (parent)->proxy;
1277 if (GTK_IS_MENU_ITEM (menushell))
1278 menushell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menushell));
1279 siblings = gtk_container_get_children (GTK_CONTAINER (menushell));
1280 if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1285 case GTK_UI_MANAGER_MENU_PLACEHOLDER:
1286 menushell = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1287 g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1288 pos = g_list_index (GTK_MENU_SHELL (menushell)->children,
1289 NODE_INFO (parent)->proxy) + 1;
1292 g_warning("%s: bad parent node type %d", G_STRLOC,
1293 NODE_INFO (parent)->type);
1299 GtkWidget *prev_child;
1302 sibling = node->prev;
1303 if (NODE_INFO (sibling)->type == GTK_UI_MANAGER_MENU_PLACEHOLDER)
1304 prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1306 prev_child = NODE_INFO (sibling)->proxy;
1308 g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
1309 menushell = gtk_widget_get_parent (prev_child);
1310 g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1312 pos = g_list_index (GTK_MENU_SHELL (menushell)->children, prev_child) + 1;
1316 *menushell_p = menushell;
1324 find_toolbar_position (GNode *node,
1325 GtkWidget **toolbar_p,
1331 g_return_val_if_fail (node != NULL, FALSE);
1332 g_return_val_if_fail (NODE_INFO (node)->type == GTK_UI_MANAGER_TOOLBAR ||
1333 NODE_INFO (node)->type == GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER ||
1334 NODE_INFO (node)->type == GTK_UI_MANAGER_TOOLITEM ||
1335 NODE_INFO (node)->type == GTK_UI_MANAGER_SEPARATOR,
1338 /* first sibling -- look at parent */
1339 if (node->prev == NULL)
1343 parent = node->parent;
1344 switch (NODE_INFO (parent)->type)
1346 case GTK_UI_MANAGER_TOOLBAR:
1347 toolbar = NODE_INFO (parent)->proxy;
1350 case GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER:
1351 toolbar = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1352 g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
1353 pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
1354 GTK_TOOL_ITEM (NODE_INFO (parent)->proxy)) + 1;
1357 g_warning ("%s: bad parent node type %d", G_STRLOC,
1358 NODE_INFO (parent)->type);
1364 GtkWidget *prev_child;
1367 sibling = node->prev;
1368 if (NODE_INFO (sibling)->type == GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER)
1369 prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1371 prev_child = NODE_INFO (sibling)->proxy;
1373 g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
1374 toolbar = gtk_widget_get_parent (prev_child);
1375 g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
1377 pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
1378 GTK_TOOL_ITEM (prev_child)) + 1;
1382 *toolbar_p = toolbar;
1390 update_node (GtkUIManager *self,
1392 gboolean add_tearoffs)
1394 GtkUIManagerNode *info;
1397 #ifdef DEBUG_UI_MANAGER
1401 g_return_if_fail (node != NULL);
1402 g_return_if_fail (NODE_INFO (node) != NULL);
1404 info = NODE_INFO (node);
1406 #ifdef DEBUG_UI_MANAGER
1407 g_print ("update_node name=%s dirty=%d (", info->name, info->dirty);
1408 for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next)
1410 NodeUIReference *ref = tmp->data;
1411 g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id);
1420 const gchar *action_name;
1421 NodeUIReference *ref;
1423 if (info->uifiles == NULL) {
1424 /* We may need to remove this node.
1425 * This must be done in post order
1427 goto recurse_children;
1430 ref = info->uifiles->data;
1431 action_name = g_quark_to_string (ref->action_quark);
1432 action = get_action_by_name (self, action_name);
1434 info->dirty = FALSE;
1436 /* Check if the node doesn't have an action and must have an action */
1437 if (action == NULL &&
1438 info->type != GTK_UI_MANAGER_MENUBAR &&
1439 info->type != GTK_UI_MANAGER_TOOLBAR &&
1440 info->type != GTK_UI_MANAGER_SEPARATOR &&
1441 info->type != GTK_UI_MANAGER_MENU_PLACEHOLDER &&
1442 info->type != GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER)
1444 /* FIXME: Should we warn here? */
1445 goto recurse_children;
1448 /* If the widget already has a proxy and the action hasn't changed, then
1449 * we only have to update the tearoff menu items.
1451 if (info->proxy != NULL && action == info->action)
1453 if (info->type == GTK_UI_MANAGER_MENU)
1458 menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
1459 siblings = gtk_container_get_children (GTK_CONTAINER (menu));
1460 if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1461 g_object_set (G_OBJECT (siblings->data), "visible", add_tearoffs, 0);
1464 goto recurse_children;
1468 g_object_unref (info->action);
1469 info->action = action;
1471 g_object_ref (info->action);
1475 case GTK_UI_MANAGER_MENUBAR:
1476 if (info->proxy == NULL)
1478 info->proxy = gtk_menu_bar_new ();
1479 gtk_widget_show (info->proxy);
1480 g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
1483 case GTK_UI_MANAGER_POPUP:
1484 if (info->proxy == NULL)
1486 info->proxy = gtk_menu_new ();
1487 gtk_menu_set_accel_group (GTK_MENU (info->proxy),
1488 self->private_data->accel_group);
1491 case GTK_UI_MANAGER_MENU:
1493 GtkWidget *prev_submenu = NULL;
1496 /* remove the proxy if it is of the wrong type ... */
1497 if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
1498 GTK_ACTION_GET_CLASS (info->action)->menu_item_type)
1500 prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
1503 g_object_ref (prev_submenu);
1504 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
1506 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1510 /* create proxy if needed ... */
1511 if (info->proxy == NULL)
1513 GtkWidget *menushell;
1516 if (find_menu_position (node, &menushell, &pos))
1518 info->proxy = gtk_action_create_menu_item (info->action);
1519 menu = gtk_menu_new ();
1520 GtkWidget *tearoff = gtk_tearoff_menu_item_new ();
1521 gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff);
1522 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu);
1523 gtk_menu_set_accel_group (GTK_MENU (menu), self->private_data->accel_group);
1524 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos);
1528 gtk_action_connect_proxy (info->action, info->proxy);
1531 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),
1533 g_object_unref (prev_submenu);
1535 menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
1536 siblings = gtk_container_get_children (GTK_CONTAINER (menu));
1537 if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1538 g_object_set (G_OBJECT (siblings->data), "visible", add_tearoffs, 0);
1541 case GTK_UI_MANAGER_UNDECIDED:
1542 g_warning ("found 'undecided node!");
1544 case GTK_UI_MANAGER_ROOT:
1546 case GTK_UI_MANAGER_TOOLBAR:
1547 if (info->proxy == NULL)
1549 info->proxy = gtk_toolbar_new ();
1550 gtk_widget_show (info->proxy);
1551 g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
1554 case GTK_UI_MANAGER_MENU_PLACEHOLDER:
1555 /* create menu items for placeholders if necessary ... */
1556 if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) ||
1557 !GTK_IS_SEPARATOR_MENU_ITEM (info->extra))
1560 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1563 gtk_container_remove (GTK_CONTAINER (info->extra->parent),
1568 if (info->proxy == NULL)
1570 GtkWidget *menushell;
1573 if (find_menu_position (node, &menushell, &pos))
1575 NODE_INFO (node)->proxy = gtk_separator_menu_item_new ();
1576 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
1577 NODE_INFO (node)->proxy, pos);
1579 NODE_INFO (node)->extra = gtk_separator_menu_item_new ();
1580 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
1581 NODE_INFO (node)->extra, pos+1);
1585 case GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER:
1586 /* create toolbar items for placeholders if necessary ... */
1587 if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) ||
1588 !GTK_IS_SEPARATOR_TOOL_ITEM (info->extra))
1591 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1594 gtk_container_remove (GTK_CONTAINER (info->extra->parent),
1599 if (info->proxy == NULL)
1604 if (find_toolbar_position (node, &toolbar, &pos))
1608 item = gtk_separator_tool_item_new ();
1609 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
1610 NODE_INFO(node)->proxy = GTK_WIDGET (item);
1612 item = gtk_separator_tool_item_new ();
1613 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1);
1614 NODE_INFO (node)->extra = GTK_WIDGET (item);
1618 case GTK_UI_MANAGER_MENUITEM:
1619 /* remove the proxy if it is of the wrong type ... */
1620 if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
1621 GTK_ACTION_GET_CLASS (info->action)->menu_item_type)
1623 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1627 /* create proxy if needed ... */
1628 if (info->proxy == NULL)
1630 GtkWidget *menushell;
1633 if (find_menu_position (node, &menushell, &pos))
1635 info->proxy = gtk_action_create_menu_item (info->action);
1637 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
1643 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
1644 gtk_action_connect_proxy (info->action, info->proxy);
1647 case GTK_UI_MANAGER_TOOLITEM:
1648 /* remove the proxy if it is of the wrong type ... */
1649 if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
1650 GTK_ACTION_GET_CLASS (info->action)->toolbar_item_type)
1652 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1656 /* create proxy if needed ... */
1657 if (info->proxy == NULL)
1662 if (find_toolbar_position (node, &toolbar, &pos))
1664 info->proxy = gtk_action_create_tool_item (info->action);
1666 gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
1667 GTK_TOOL_ITEM (info->proxy), pos);
1672 gtk_action_connect_proxy (info->action, info->proxy);
1675 case GTK_UI_MANAGER_SEPARATOR:
1676 if (NODE_INFO (node->parent)->type == GTK_UI_MANAGER_TOOLBAR ||
1677 NODE_INFO (node->parent)->type == GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER)
1682 if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy))
1684 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1689 if (find_toolbar_position (node, &toolbar, &pos))
1691 GtkToolItem *item = gtk_separator_tool_item_new ();
1692 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
1693 info->proxy = GTK_WIDGET (item);
1694 gtk_widget_show (info->proxy);
1699 GtkWidget *menushell;
1702 if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy))
1704 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1709 if (find_menu_position (node, &menushell, &pos))
1711 info->proxy = gtk_separator_menu_item_new ();
1712 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
1714 gtk_widget_show (info->proxy);
1720 /* if this node has a widget, but it is the wrong type, remove it */
1724 /* process children */
1725 child = node->children;
1731 child = current->next;
1732 update_node (self, current, add_tearoffs && (info->type != GTK_UI_MANAGER_POPUP));
1735 /* handle cleanup of dead nodes */
1736 if (node->children == NULL && info->uifiles == NULL)
1739 gtk_widget_destroy (info->proxy);
1740 if ((info->type == GTK_UI_MANAGER_MENU_PLACEHOLDER ||
1741 info->type == GTK_UI_MANAGER_TOOLBAR_PLACEHOLDER) &&
1743 gtk_widget_destroy (info->extra);
1744 g_chunk_free (info, merge_node_chunk);
1745 g_node_destroy (node);
1750 do_updates (GtkUIManager *self)
1752 /* this function needs to check through the tree for dirty nodes.
1753 * For such nodes, it needs to do the following:
1755 * 1) check if they are referenced by any loaded UI files anymore.
1756 * In which case, the proxy widget should be destroyed, unless
1757 * there are any subnodes.
1759 * 2) lookup the action for this node again. If it is different to
1760 * the current one (or if no previous action has been looked up),
1761 * the proxy is reconnected to the new action (or a new proxy widget
1762 * is created and added to the parent container).
1764 update_node (self, self->private_data->root_node,
1765 self->private_data->add_tearoffs);
1767 self->private_data->update_tag = 0;
1773 gtk_ui_manager_queue_update (GtkUIManager *self)
1775 if (self->private_data->update_tag != 0)
1778 self->private_data->update_tag = g_idle_add ((GSourceFunc)do_updates, self);
1782 gtk_ui_manager_ensure_update (GtkUIManager *self)
1784 if (self->private_data->update_tag != 0)
1786 g_source_remove (self->private_data->update_tag);
1792 dirty_traverse_func (GNode *node,
1795 NODE_INFO (node)->dirty = TRUE;
1800 gtk_ui_manager_dirty_all (GtkUIManager *self)
1802 g_node_traverse (self->private_data->root_node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1803 dirty_traverse_func, NULL);
1804 gtk_ui_manager_queue_update (self);
1807 static const gchar *open_tag_format[] = {
1810 "%*s<menubar name=\"%s\">\n",
1811 "%*s<menu name='%s' action=\"%s\">\n",
1812 "%*s<toolbar name=\"%s\">\n",
1813 "%*s<placeholder name=\"%s\">\n",
1814 "%*s<placeholder name=\"%s\">\n",
1815 "%*s<popup name='%s' action=\"%s\">\n",
1816 "%*s<menuitem name=\"%s\" action=\"%s\"/>\n",
1817 "%*s<toolitem name=\"%s\" action=\"%s\"/>\n",
1818 "%*s<separator/>\n",
1821 static const gchar *close_tag_format[] = {
1822 "%*s</UNDECIDED>\n",
1827 "%*s</placeholder>\n",
1828 "%*s</placeholder>\n",
1836 print_node (GtkUIManager *self,
1841 GtkUIManagerNode *mnode;
1846 g_string_append_printf (buffer, open_tag_format[mnode->type],
1849 g_quark_to_string (mnode->action_name));
1851 for (child = node->children; child != NULL; child = child->next)
1852 print_node (self, child, indent_level + 2, buffer);
1854 g_string_append_printf (buffer, close_tag_format[mnode->type],
1860 * gtk_ui_manager_get_ui:
1861 * @self: a #GtkUIManager
1863 * Creates an XML representation of the merged ui.
1865 * Return value: A newly allocated string containing an XML representation of
1871 gtk_ui_manager_get_ui (GtkUIManager *self)
1875 buffer = g_string_new (NULL);
1877 gtk_ui_manager_ensure_update (self);
1879 print_node (self, self->private_data->root_node, 0, buffer);
1881 return g_string_free (buffer, FALSE);