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/.
35 #include "gtkmarshalers.h"
37 #include "gtkmenubar.h"
38 #include "gtkmenushell.h"
39 #include "gtkseparatormenuitem.h"
40 #include "gtkseparatortoolitem.h"
41 #include "gtktearoffmenuitem.h"
42 #include "gtktoolbar.h"
43 #include "gtkuimanager.h"
45 #undef DEBUG_UI_MANAGER
54 NODE_TYPE_MENU_PLACEHOLDER,
55 NODE_TYPE_TOOLBAR_PLACEHOLDER,
63 typedef struct _Node Node;
73 GtkWidget *extra; /* second separator for placeholders */
80 #define GTK_UI_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_UI_MANAGER, GtkUIManagerPrivate))
82 struct _GtkUIManagerPrivate
84 GtkAccelGroup *accel_group;
93 gboolean add_tearoffs;
96 #define NODE_INFO(node) ((Node *)node->data)
98 typedef struct _NodeUIReference NodeUIReference;
100 struct _NodeUIReference
106 static void gtk_ui_manager_class_init (GtkUIManagerClass *class);
107 static void gtk_ui_manager_init (GtkUIManager *self);
108 static void gtk_ui_manager_finalize (GObject *object);
109 static void gtk_ui_manager_set_property (GObject *object,
113 static void gtk_ui_manager_get_property (GObject *object,
117 static void queue_update (GtkUIManager *self);
118 static void dirty_all_nodes (GtkUIManager *self);
119 static GNode * get_child_node (GtkUIManager *self,
121 const gchar *childname,
122 gint childname_length,
126 static GNode * get_node (GtkUIManager *self,
130 static gboolean free_node (GNode *node);
131 static void node_prepend_ui_reference (Node *node,
133 GQuark action_quark);
134 static void node_remove_ui_reference (Node *node,
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 (Node, 64,
195 gobject_class->finalize = gtk_ui_manager_finalize;
196 gobject_class->set_property = gtk_ui_manager_set_property;
197 gobject_class->get_property = gtk_ui_manager_get_property;
200 * GtkUIManager:add-tearoffs:
202 * The "add-tearoffs" property controls whether generated menus
203 * have tearoff menu items.
205 * Note that this only affects regular menus. Generated popup
206 * menus never have tearoff menu items.
210 g_object_class_install_property (gobject_class,
212 g_param_spec_boolean ("add_tearoffs",
213 _("Add tearoffs to menus"),
214 _("Whether tearoff menu items should be added to menus"),
218 g_object_class_install_property (gobject_class,
220 g_param_spec_string ("ui",
221 _("Merged UI definition"),
222 _("An XML string describing the merged UI"),
228 * GtkUIManager::add-widget:
229 * @merge: a #GtkUIManager
230 * @widget: the added widget
232 * The add_widget signal is emitted for each generated menubar and toolbar.
233 * It is not emitted for generated popup menus, which can be obtained by
234 * gtk_ui_manager_get_widget().
238 merge_signals[ADD_WIDGET] =
239 g_signal_new ("add_widget",
240 G_OBJECT_CLASS_TYPE (klass),
241 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
242 G_STRUCT_OFFSET (GtkUIManagerClass, add_widget), NULL, NULL,
243 g_cclosure_marshal_VOID__OBJECT,
248 * GtkUIManager::actions-changed:
249 * @merge: a #GtkUIManager
251 * The "actions-changed" signal is emitted whenever the set of actions
256 merge_signals[ACTIONS_CHANGED] =
257 g_signal_new ("actions_changed",
258 G_OBJECT_CLASS_TYPE (klass),
259 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
260 G_STRUCT_OFFSET (GtkUIManagerClass, actions_changed),
262 g_cclosure_marshal_VOID__VOID,
265 g_type_class_add_private (gobject_class, sizeof (GtkUIManagerPrivate));
270 gtk_ui_manager_init (GtkUIManager *self)
275 self->private_data = GTK_UI_MANAGER_GET_PRIVATE (self);
277 self->private_data->accel_group = gtk_accel_group_new ();
279 self->private_data->root_node = NULL;
280 self->private_data->action_groups = NULL;
282 self->private_data->last_merge_id = 0;
283 self->private_data->add_tearoffs = FALSE;
285 merge_id = gtk_ui_manager_new_merge_id (self);
286 node = get_child_node (self, NULL, "ui", 2,
287 NODE_TYPE_ROOT, TRUE, FALSE);
288 node_prepend_ui_reference (NODE_INFO (node), merge_id, 0);
292 gtk_ui_manager_finalize (GObject *object)
294 GtkUIManager *self = GTK_UI_MANAGER (object);
296 if (self->private_data->update_tag != 0)
298 g_source_remove (self->private_data->update_tag);
299 self->private_data->update_tag = 0;
302 g_node_traverse (self->private_data->root_node,
303 G_POST_ORDER, G_TRAVERSE_ALL, -1,
304 (GNodeTraverseFunc)free_node, 0);
305 g_node_destroy (self->private_data->root_node);
306 self->private_data->root_node = NULL;
308 g_list_foreach (self->private_data->action_groups,
309 (GFunc) g_object_unref, NULL);
310 g_list_free (self->private_data->action_groups);
311 self->private_data->action_groups = NULL;
313 g_object_unref (self->private_data->accel_group);
314 self->private_data->accel_group = NULL;
318 gtk_ui_manager_set_property (GObject *object,
323 GtkUIManager *self = GTK_UI_MANAGER (object);
327 case PROP_ADD_TEAROFFS:
328 gtk_ui_manager_set_add_tearoffs (self, g_value_get_boolean (value));
331 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
337 gtk_ui_manager_get_property (GObject *object,
342 GtkUIManager *self = GTK_UI_MANAGER (object);
346 case PROP_ADD_TEAROFFS:
347 g_value_set_boolean (value, self->private_data->add_tearoffs);
350 g_value_set_string (value, gtk_ui_manager_get_ui (self));
353 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
360 * gtk_ui_manager_new:
362 * Creates a new ui manager object.
364 * Return value: a new ui manager object.
369 gtk_ui_manager_new (void)
371 return g_object_new (GTK_TYPE_UI_MANAGER, NULL);
376 * gtk_ui_manager_get_add_tearoffs:
377 * @self: a #GtkUIManager
379 * Returns whether menus generated by this #GtkUIManager
380 * will have tearoff menu items.
382 * Return value: whether tearoff menu items are added
387 gtk_ui_manager_get_add_tearoffs (GtkUIManager *self)
389 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), FALSE);
391 return self->private_data->add_tearoffs;
396 * gtk_ui_manager_set_add_tearoffs:
397 * @self: a #GtkUIManager
398 * @add_tearoffs: whether tearoff menu items are added
400 * Sets the "add_tearoffs" property, which controls whether menus
401 * generated by this #GtkUIManager will have tearoff menu items.
403 * Note that this only affects regular menus. Generated popup
404 * menus never have tearoff menu items.
409 gtk_ui_manager_set_add_tearoffs (GtkUIManager *self,
410 gboolean add_tearoffs)
412 g_return_if_fail (GTK_IS_UI_MANAGER (self));
414 add_tearoffs = add_tearoffs != FALSE;
416 if (add_tearoffs != self->private_data->add_tearoffs)
418 self->private_data->add_tearoffs = add_tearoffs;
420 dirty_all_nodes (self);
422 g_object_notify (G_OBJECT (self), "add_tearoffs");
427 * gtk_ui_manager_insert_action_group:
428 * @self: a #GtkUIManager object
429 * @action_group: the action group to be inserted
430 * @pos: the position at which the group will be inserted.
432 * Inserts an action group into the list of action groups associated
433 * with @self. Actions in earlier groups hide actions with the same
434 * name in later groups.
439 gtk_ui_manager_insert_action_group (GtkUIManager *self,
440 GtkActionGroup *action_group,
443 g_return_if_fail (GTK_IS_UI_MANAGER (self));
444 g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
445 g_return_if_fail (g_list_find (self->private_data->action_groups,
446 action_group) == NULL);
448 g_object_ref (action_group);
449 self->private_data->action_groups =
450 g_list_insert (self->private_data->action_groups, action_group, pos);
452 /* dirty all nodes, as action bindings may change */
453 dirty_all_nodes (self);
455 g_signal_emit (self, merge_signals[ACTIONS_CHANGED], 0);
459 * gtk_ui_manager_remove_action_group:
460 * @self: a #GtkUIManager object
461 * @action_group: the action group to be removed
463 * Removes an action group from the list of action groups associated
469 gtk_ui_manager_remove_action_group (GtkUIManager *self,
470 GtkActionGroup *action_group)
472 g_return_if_fail (GTK_IS_UI_MANAGER (self));
473 g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
474 g_return_if_fail (g_list_find (self->private_data->action_groups,
475 action_group) != NULL);
477 self->private_data->action_groups =
478 g_list_remove (self->private_data->action_groups, action_group);
479 g_object_unref (action_group);
481 /* dirty all nodes, as action bindings may change */
482 dirty_all_nodes (self);
484 g_signal_emit (self, merge_signals[ACTIONS_CHANGED], 0);
488 * gtk_ui_manager_get_action_groups:
489 * @self: a #GtkUIManager object
491 * Returns the list of action groups associated with @self.
493 * Return value: a #GList of action groups. The list is owned by GTK+
494 * and should not be modified.
499 gtk_ui_manager_get_action_groups (GtkUIManager *self)
501 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
503 return self->private_data->action_groups;
507 * gtk_ui_manager_get_accel_group:
508 * @self: a #GtkUIManager object
510 * Returns the #GtkAccelGroup associated with @self.
512 * Return value: the #GtkAccelGroup.
517 gtk_ui_manager_get_accel_group (GtkUIManager *self)
519 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
521 return self->private_data->accel_group;
525 * gtk_ui_manager_get_widget:
526 * @self: a #GtkUIManager
529 * Looks up a widget by following a path.
530 * The path consists of the names specified in the XML description of the UI.
531 * separated by '/'. Elements which don't have a name or action attribute in
532 * the XML (e.g. <popup>) can be addressed by their XML element name
533 * (e.g. "popup"). The root element ("/ui") can be omitted in the path.
535 * Return value: the widget found by following the path, or %NULL if no widget
541 gtk_ui_manager_get_widget (GtkUIManager *self,
546 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
547 g_return_val_if_fail (path != NULL, NULL);
549 /* ensure that there are no pending updates before we get the
551 gtk_ui_manager_ensure_update (self);
553 node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
558 return NODE_INFO (node)->proxy;
562 * gtk_ui_manager_get_action:
563 * @self: a #GtkUIManager
566 * Looks up an action by following a path. See gtk_ui_manager_get_widget()
567 * for more information about paths.
569 * Return value: the action whose proxy widget is found by following the path,
570 * or %NULL if no widget was found.
575 gtk_ui_manager_get_action (GtkUIManager *self,
580 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
581 g_return_val_if_fail (path != NULL, NULL);
583 /* ensure that there are no pending updates before we get
585 gtk_ui_manager_ensure_update (self);
587 node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
592 return NODE_INFO (node)->action;
596 get_child_node (GtkUIManager *self,
598 const gchar *childname,
599 gint childname_length,
606 g_return_val_if_fail (parent == NULL ||
607 (NODE_INFO (parent)->type != NODE_TYPE_MENUITEM &&
608 NODE_INFO (parent)->type != NODE_TYPE_TOOLITEM),
615 for (child = parent->children; child != NULL; child = child->next)
617 if (strlen (NODE_INFO (child)->name) == childname_length &&
618 !strncmp (NODE_INFO (child)->name, childname, childname_length))
620 /* if undecided about node type, set it */
621 if (NODE_INFO (child)->type == NODE_TYPE_UNDECIDED)
622 NODE_INFO (child)->type = node_type;
624 /* warn about type mismatch */
625 if (NODE_INFO (child)->type != NODE_TYPE_UNDECIDED &&
626 node_type != NODE_TYPE_UNDECIDED &&
627 NODE_INFO (child)->type != node_type)
628 g_warning ("node type doesn't match %d (%s is type %d)",
630 NODE_INFO (child)->name,
631 NODE_INFO (child)->type);
637 if (!child && create)
641 mnode = g_chunk_new0 (Node, merge_node_chunk);
642 mnode->type = node_type;
643 mnode->name = g_strndup (childname, childname_length);
647 child = g_node_prepend_data (parent, mnode);
649 child = g_node_append_data (parent, mnode);
654 /* handle root node */
655 if (self->private_data->root_node)
657 child = self->private_data->root_node;
658 if (strncmp (NODE_INFO (child)->name, childname, childname_length) != 0)
659 g_warning ("root node name '%s' doesn't match '%s'",
660 childname, NODE_INFO (child)->name);
661 if (NODE_INFO (child)->type != NODE_TYPE_ROOT)
662 g_warning ("base element must be of type ROOT");
668 mnode = g_chunk_new0 (Node, merge_node_chunk);
669 mnode->type = node_type;
670 mnode->name = g_strndup (childname, childname_length);
673 child = self->private_data->root_node = g_node_new (mnode);
681 get_node (GtkUIManager *self,
686 const gchar *pos, *end;
687 GNode *parent, *node;
689 end = path + strlen (path);
691 parent = node = NULL;
697 slash = strchr (pos, '/');
699 length = slash - pos;
701 length = strlen (pos);
703 node = get_child_node (self, parent, pos, length, NODE_TYPE_UNDECIDED,
708 pos += length + 1; /* move past the node name and the slash too */
712 if (node != NULL && NODE_INFO (node)->type == NODE_TYPE_UNDECIDED)
713 NODE_INFO (node)->type = node_type;
719 free_node (GNode *node)
721 Node *info = NODE_INFO (node);
723 g_list_foreach (info->uifiles, (GFunc) g_free, NULL);
724 g_list_free (info->uifiles);
727 g_object_unref (info->action);
729 g_chunk_free (info, merge_node_chunk);
735 * gtk_ui_manager_new_merge_id:
736 * @self: a #GtkUIManager
738 * Returns an unused merge id, suitable for use with
739 * gtk_ui_manager_add_ui().
741 * Return value: an unused merge id.
746 gtk_ui_manager_new_merge_id (GtkUIManager *self)
748 self->private_data->last_merge_id++;
750 return self->private_data->last_merge_id;
754 node_prepend_ui_reference (Node *node,
758 NodeUIReference *reference;
760 reference = g_new (NodeUIReference, 1);
761 reference->action_quark = action_quark;
762 reference->merge_id = merge_id;
764 /* Prepend the reference */
765 node->uifiles = g_list_prepend (node->uifiles, reference);
771 node_remove_ui_reference (Node *node,
776 for (p = node->uifiles; p != NULL; p = p->next)
778 NodeUIReference *reference = p->data;
780 if (reference->merge_id == merge_id)
782 node->uifiles = g_list_remove_link (node->uifiles, p);
791 /* -------------------- The UI file parser -------------------- */
804 typedef struct _ParseContext ParseContext;
808 ParseState prev_state;
818 start_element_handler (GMarkupParseContext *context,
819 const gchar *element_name,
820 const gchar **attribute_names,
821 const gchar **attribute_values,
825 ParseContext *ctx = user_data;
826 GtkUIManager *self = ctx->self;
829 const gchar *node_name;
834 gboolean raise_error = TRUE;
841 for (i = 0; attribute_names[i] != NULL; i++)
843 if (!strcmp (attribute_names[i], "name"))
845 node_name = attribute_values[i];
847 else if (!strcmp (attribute_names[i], "action"))
849 action = attribute_values[i];
850 action_quark = g_quark_from_string (attribute_values[i]);
852 else if (!strcmp (attribute_names[i], "position"))
854 top = !strcmp (attribute_values[i], "top");
858 gint line_number, char_number;
860 g_markup_parse_context_get_position (context,
861 &line_number, &char_number);
864 G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
865 _("Unknown attribute '%s' on line %d char %d"),
867 line_number, char_number);
872 /* Work out a name for this node. Either the name attribute, or
873 * the action, or the element name */
874 if (node_name == NULL)
879 node_name = element_name;
882 switch (element_name[0])
885 if (ctx->state == STATE_ROOT && !strcmp (element_name, "accelerator"))
887 ctx->state = STATE_ROOT;
888 ctx->current = get_child_node (self, ctx->current,
889 node_name, strlen (node_name),
890 NODE_TYPE_ACCELERATOR,
892 if (NODE_INFO (ctx->current)->action_name == 0)
893 NODE_INFO (ctx->current)->action_name = action_quark;
895 node_prepend_ui_reference (NODE_INFO (ctx->current),
896 ctx->merge_id, action_quark);
897 NODE_INFO (ctx->current)->dirty = TRUE;
903 if (ctx->state == STATE_START && !strcmp (element_name, "ui"))
905 ctx->state = STATE_ROOT;
906 ctx->current = self->private_data->root_node;
909 node_prepend_ui_reference (NODE_INFO (ctx->current),
910 ctx->merge_id, action_quark);
914 if (ctx->state == STATE_ROOT && !strcmp (element_name, "menubar"))
916 ctx->state = STATE_MENU;
917 ctx->current = get_child_node (self, ctx->current,
918 node_name, strlen (node_name),
921 if (NODE_INFO (ctx->current)->action_name == 0)
922 NODE_INFO (ctx->current)->action_name = action_quark;
924 node_prepend_ui_reference (NODE_INFO (ctx->current),
925 ctx->merge_id, action_quark);
926 NODE_INFO (ctx->current)->dirty = TRUE;
930 else if (ctx->state == STATE_MENU && !strcmp (element_name, "menu"))
932 ctx->current = get_child_node (self, ctx->current,
933 node_name, strlen (node_name),
936 if (NODE_INFO (ctx->current)->action_name == 0)
937 NODE_INFO (ctx->current)->action_name = action_quark;
939 node_prepend_ui_reference (NODE_INFO (ctx->current),
940 ctx->merge_id, action_quark);
941 NODE_INFO (ctx->current)->dirty = TRUE;
945 else if (ctx->state == STATE_MENU && !strcmp (element_name, "menuitem"))
949 ctx->state = STATE_MENUITEM;
950 node = get_child_node (self, ctx->current,
951 node_name, strlen (node_name),
954 if (NODE_INFO (node)->action_name == 0)
955 NODE_INFO (node)->action_name = action_quark;
957 node_prepend_ui_reference (NODE_INFO (node),
958 ctx->merge_id, action_quark);
959 NODE_INFO (node)->dirty = TRUE;
965 if (ctx->state == STATE_ROOT && !strcmp (element_name, "popup"))
967 ctx->state = STATE_MENU;
968 ctx->current = get_child_node (self, ctx->current,
969 node_name, strlen (node_name),
972 if (NODE_INFO (ctx->current)->action_name == 0)
973 NODE_INFO (ctx->current)->action_name = action_quark;
975 node_prepend_ui_reference (NODE_INFO (ctx->current),
976 ctx->merge_id, action_quark);
977 NODE_INFO (ctx->current)->dirty = TRUE;
981 else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
982 !strcmp (element_name, "placeholder"))
984 if (ctx->state == STATE_TOOLBAR)
985 ctx->current = get_child_node (self, ctx->current,
986 node_name, strlen (node_name),
987 NODE_TYPE_TOOLBAR_PLACEHOLDER,
990 ctx->current = get_child_node (self, ctx->current,
991 node_name, strlen (node_name),
992 NODE_TYPE_MENU_PLACEHOLDER,
995 node_prepend_ui_reference (NODE_INFO (ctx->current),
996 ctx->merge_id, action_quark);
997 NODE_INFO (ctx->current)->dirty = TRUE;
1003 if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
1004 !strcmp (element_name, "separator"))
1008 if (ctx->state == STATE_TOOLBAR)
1009 ctx->state = STATE_TOOLITEM;
1011 ctx->state = STATE_MENUITEM;
1012 node = get_child_node (self, ctx->current,
1013 node_name, strlen (node_name),
1014 NODE_TYPE_SEPARATOR,
1016 if (NODE_INFO (node)->action_name == 0)
1017 NODE_INFO (node)->action_name = action_quark;
1019 node_prepend_ui_reference (NODE_INFO (node),
1020 ctx->merge_id, action_quark);
1021 NODE_INFO (node)->dirty = TRUE;
1023 raise_error = FALSE;
1027 if (ctx->state == STATE_ROOT && !strcmp (element_name, "toolbar"))
1029 ctx->state = STATE_TOOLBAR;
1030 ctx->current = get_child_node (self, ctx->current,
1031 node_name, strlen (node_name),
1034 if (NODE_INFO (ctx->current)->action_name == 0)
1035 NODE_INFO (ctx->current)->action_name = action_quark;
1037 node_prepend_ui_reference (NODE_INFO (ctx->current),
1038 ctx->merge_id, action_quark);
1039 NODE_INFO (ctx->current)->dirty = TRUE;
1041 raise_error = FALSE;
1043 else if (ctx->state == STATE_TOOLBAR && !strcmp (element_name, "toolitem"))
1047 ctx->state = STATE_TOOLITEM;
1048 node = get_child_node (self, ctx->current,
1049 node_name, strlen (node_name),
1052 if (NODE_INFO (node)->action_name == 0)
1053 NODE_INFO (node)->action_name = action_quark;
1055 node_prepend_ui_reference (NODE_INFO (node),
1056 ctx->merge_id, action_quark);
1057 NODE_INFO (node)->dirty = TRUE;
1059 raise_error = FALSE;
1067 gint line_number, char_number;
1069 g_markup_parse_context_get_position (context,
1070 &line_number, &char_number);
1073 G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1074 _("Unexpected start tag '%s' on line %d char %d"),
1076 line_number, char_number);
1081 end_element_handler (GMarkupParseContext *context,
1082 const gchar *element_name,
1086 ParseContext *ctx = user_data;
1092 /* no need to GError here, GMarkup already catches this */
1095 ctx->current = NULL;
1096 ctx->state = STATE_END;
1100 ctx->current = ctx->current->parent;
1101 if (NODE_INFO (ctx->current)->type == NODE_TYPE_ROOT)
1102 ctx->state = STATE_ROOT;
1103 /* else, stay in same state */
1105 case STATE_MENUITEM:
1106 ctx->state = STATE_MENU;
1108 case STATE_TOOLITEM:
1109 ctx->state = STATE_TOOLBAR;
1115 cleanup (GMarkupParseContext *context,
1119 ParseContext *ctx = user_data;
1121 ctx->current = NULL;
1122 /* should also walk through the tree and get rid of nodes related to
1123 * this UI file's tag */
1125 gtk_ui_manager_remove_ui (ctx->self, ctx->merge_id);
1129 xml_isspace (char c)
1131 return c == ' ' || c == '\t' || c == '\n' || c == '\r';
1135 text_handler (GMarkupParseContext *context,
1145 end = text + text_len;
1146 while (p != end && xml_isspace (*p))
1151 gint line_number, char_number;
1153 g_markup_parse_context_get_position (context,
1154 &line_number, &char_number);
1157 G_MARKUP_ERROR_INVALID_CONTENT,
1158 _("Unexpected character data on line %d char %d"),
1159 line_number, char_number);
1164 static GMarkupParser ui_parser = {
1165 start_element_handler,
1166 end_element_handler,
1173 add_ui_from_string (GtkUIManager *self,
1174 const gchar *buffer,
1176 gboolean needs_root,
1179 ParseContext ctx = { 0 };
1180 GMarkupParseContext *context;
1182 ctx.state = STATE_START;
1185 ctx.merge_id = gtk_ui_manager_new_merge_id (self);
1187 context = g_markup_parse_context_new (&ui_parser, 0, &ctx, NULL);
1190 if (!g_markup_parse_context_parse (context, "<ui>", -1, error))
1193 if (!g_markup_parse_context_parse (context, buffer, length, error))
1197 if (!g_markup_parse_context_parse (context, "</ui>", -1, error))
1200 if (!g_markup_parse_context_end_parse (context, error))
1203 g_markup_parse_context_free (context);
1205 queue_update (self);
1207 g_object_notify (G_OBJECT (self), "ui");
1209 return ctx.merge_id;
1213 g_markup_parse_context_free (context);
1219 * gtk_ui_manager_add_ui_from_string:
1220 * @self: a #GtkUIManager object
1221 * @buffer: the string to parse
1222 * @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
1223 * @error: return location for an error
1225 * Parses a string containing a <link linkend="XML-UI">UI definition</link> and
1226 * merges it with the current contents of @self. An enclosing <ui>
1227 * element is added if it is missing.
1229 * Return value: The merge id for the merged UI. The merge id can be used
1230 * to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1231 * the return value is 0.
1236 gtk_ui_manager_add_ui_from_string (GtkUIManager *self,
1237 const gchar *buffer,
1241 gboolean needs_root = TRUE;
1245 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), 0);
1246 g_return_val_if_fail (buffer != NULL, 0);
1249 length = strlen (buffer);
1252 end = buffer + length;
1253 while (p != end && xml_isspace (*p))
1256 if (end - p >= 4 && strncmp (p, "<ui>", 4) == 0)
1259 return add_ui_from_string (self, buffer, length, needs_root, error);
1263 * gtk_ui_manager_add_ui_from_file:
1264 * @self: a #GtkUIManager object
1265 * @filename: the name of the file to parse
1266 * @error: return location for an error
1268 * Parses a file containing a <link linkend="XML-UI">UI definition</link> and
1269 * merges it with the current contents of @self.
1271 * Return value: The merge id for the merged UI. The merge id can be used
1272 * to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1273 * the return value is 0.
1278 gtk_ui_manager_add_ui_from_file (GtkUIManager *self,
1279 const gchar *filename,
1286 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), 0);
1288 if (!g_file_get_contents (filename, &buffer, &length, error))
1291 res = add_ui_from_string (self, buffer, length, FALSE, error);
1298 * gtk_ui_manager_add_ui:
1299 * @self: a #GtkUIManager
1300 * @merge_id: the merge id for the merged UI, see gtk_ui_manager_new_merge_id()
1302 * @name: the name for the added UI element
1303 * @action: the name of the action to be proxied, or %NULL to add a separator
1304 * @type: the type of UI element to add.
1305 * @top: if %TRUE, the UI element is added before its siblings, otherwise it
1306 * is added after its siblings.
1308 * Adds a UI element to the current contents of @self.
1310 * If @type is %GTK_UI_MANAGER_AUTO, GTK+ inserts a menuitem, toolitem or
1311 * separator if such an element can be inserted at the place determined by
1312 * @path. Otherwise @type must indicate an element that can be inserted at
1313 * the place determined by @path.
1318 gtk_ui_manager_add_ui (GtkUIManager *self,
1322 const gchar *action,
1323 GtkUIManagerItemType type,
1329 GQuark action_quark = 0;
1331 g_return_if_fail (GTK_IS_UI_MANAGER (self));
1332 g_return_if_fail (merge_id > 0);
1333 g_return_if_fail (name != NULL);
1335 node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
1340 node_type = NODE_TYPE_UNDECIDED;
1342 switch (NODE_INFO (node)->type)
1344 case NODE_TYPE_MENUBAR:
1345 case NODE_TYPE_MENU:
1346 case NODE_TYPE_POPUP:
1347 case NODE_TYPE_MENU_PLACEHOLDER:
1350 case GTK_UI_MANAGER_AUTO:
1352 node_type = NODE_TYPE_MENUITEM;
1354 node_type = NODE_TYPE_SEPARATOR;
1356 case GTK_UI_MANAGER_MENU:
1357 node_type = NODE_TYPE_MENU;
1359 case GTK_UI_MANAGER_MENUITEM:
1360 node_type = NODE_TYPE_MENUITEM;
1362 case GTK_UI_MANAGER_SEPARATOR:
1363 node_type = NODE_TYPE_SEPARATOR;
1365 case GTK_UI_MANAGER_PLACEHOLDER:
1366 node_type = NODE_TYPE_MENU_PLACEHOLDER;
1372 case NODE_TYPE_TOOLBAR:
1373 case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1376 case GTK_UI_MANAGER_AUTO:
1378 node_type = NODE_TYPE_TOOLITEM;
1380 node_type = NODE_TYPE_SEPARATOR;
1382 case GTK_UI_MANAGER_TOOLITEM:
1383 node_type = NODE_TYPE_TOOLITEM;
1385 case GTK_UI_MANAGER_SEPARATOR:
1386 node_type = NODE_TYPE_SEPARATOR;
1388 case GTK_UI_MANAGER_PLACEHOLDER:
1389 node_type = NODE_TYPE_MENU_PLACEHOLDER;
1395 case NODE_TYPE_ROOT:
1398 case GTK_UI_MANAGER_MENUBAR:
1399 node_type = NODE_TYPE_MENUBAR;
1401 case GTK_UI_MANAGER_TOOLBAR:
1402 node_type = NODE_TYPE_TOOLBAR;
1404 case GTK_UI_MANAGER_POPUP:
1405 node_type = NODE_TYPE_POPUP;
1407 case GTK_UI_MANAGER_ACCELERATOR:
1408 node_type = NODE_TYPE_ACCELERATOR;
1418 if (node_type == NODE_TYPE_UNDECIDED)
1421 child = get_child_node (self, node,
1422 name, strlen (name),
1423 node_type, TRUE, top);
1426 action_quark = g_quark_from_string (action);
1428 node_prepend_ui_reference (NODE_INFO (child),
1429 merge_id, action_quark);
1431 if (NODE_INFO (node)->action_name == 0)
1432 NODE_INFO (child)->action_name = action_quark;
1434 NODE_INFO (child)->dirty = TRUE;
1436 queue_update (self);
1438 g_object_notify (G_OBJECT (self), "ui");
1442 remove_ui (GNode *node,
1445 guint merge_id = GPOINTER_TO_UINT (user_data);
1447 node_remove_ui_reference (NODE_INFO (node), merge_id);
1449 return FALSE; /* continue */
1453 * gtk_ui_manager_remove_ui:
1454 * @self: a #GtkUIManager object
1455 * @merge_id: a merge id as returned by gtk_ui_manager_add_ui_from_string()
1457 * Unmerges the part of @self<!-- -->s content identified by @merge_id.
1462 gtk_ui_manager_remove_ui (GtkUIManager *self,
1465 g_node_traverse (self->private_data->root_node,
1466 G_POST_ORDER, G_TRAVERSE_ALL, -1,
1467 remove_ui, GUINT_TO_POINTER (merge_id));
1469 queue_update (self);
1471 g_object_notify (G_OBJECT (self), "ui");
1474 /* -------------------- Updates -------------------- */
1478 get_action_by_name (GtkUIManager *merge,
1479 const gchar *action_name)
1487 for (tmp = merge->private_data->action_groups; tmp != NULL; tmp = tmp->next)
1489 GtkActionGroup *action_group = tmp->data;
1492 action = gtk_action_group_get_action (action_group, action_name);
1502 find_menu_position (GNode *node,
1503 GtkWidget **menushell_p,
1506 GtkWidget *menushell;
1509 g_return_val_if_fail (node != NULL, FALSE);
1510 g_return_val_if_fail (NODE_INFO (node)->type == NODE_TYPE_MENU ||
1511 NODE_INFO (node)->type == NODE_TYPE_POPUP ||
1512 NODE_INFO (node)->type == NODE_TYPE_MENU_PLACEHOLDER ||
1513 NODE_INFO (node)->type == NODE_TYPE_MENUITEM ||
1514 NODE_INFO (node)->type == NODE_TYPE_SEPARATOR,
1517 /* first sibling -- look at parent */
1518 if (node->prev == NULL)
1523 parent = node->parent;
1524 switch (NODE_INFO (parent)->type)
1526 case NODE_TYPE_MENUBAR:
1527 case NODE_TYPE_POPUP:
1528 menushell = NODE_INFO (parent)->proxy;
1531 case NODE_TYPE_MENU:
1532 menushell = NODE_INFO (parent)->proxy;
1533 if (GTK_IS_MENU_ITEM (menushell))
1534 menushell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menushell));
1535 siblings = gtk_container_get_children (GTK_CONTAINER (menushell));
1536 if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1541 case NODE_TYPE_MENU_PLACEHOLDER:
1542 menushell = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1543 g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1544 pos = g_list_index (GTK_MENU_SHELL (menushell)->children,
1545 NODE_INFO (parent)->proxy) + 1;
1548 g_warning("%s: bad parent node type %d", G_STRLOC,
1549 NODE_INFO (parent)->type);
1555 GtkWidget *prev_child;
1558 sibling = node->prev;
1559 if (NODE_INFO (sibling)->type == NODE_TYPE_MENU_PLACEHOLDER)
1560 prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1562 prev_child = NODE_INFO (sibling)->proxy;
1564 g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
1565 menushell = gtk_widget_get_parent (prev_child);
1566 g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1568 pos = g_list_index (GTK_MENU_SHELL (menushell)->children, prev_child) + 1;
1572 *menushell_p = menushell;
1580 find_toolbar_position (GNode *node,
1581 GtkWidget **toolbar_p,
1587 g_return_val_if_fail (node != NULL, FALSE);
1588 g_return_val_if_fail (NODE_INFO (node)->type == NODE_TYPE_TOOLBAR ||
1589 NODE_INFO (node)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER ||
1590 NODE_INFO (node)->type == NODE_TYPE_TOOLITEM ||
1591 NODE_INFO (node)->type == NODE_TYPE_SEPARATOR,
1594 /* first sibling -- look at parent */
1595 if (node->prev == NULL)
1599 parent = node->parent;
1600 switch (NODE_INFO (parent)->type)
1602 case NODE_TYPE_TOOLBAR:
1603 toolbar = NODE_INFO (parent)->proxy;
1606 case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1607 toolbar = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1608 g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
1609 pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
1610 GTK_TOOL_ITEM (NODE_INFO (parent)->proxy)) + 1;
1613 g_warning ("%s: bad parent node type %d", G_STRLOC,
1614 NODE_INFO (parent)->type);
1620 GtkWidget *prev_child;
1623 sibling = node->prev;
1624 if (NODE_INFO (sibling)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
1625 prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1627 prev_child = NODE_INFO (sibling)->proxy;
1629 g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
1630 toolbar = gtk_widget_get_parent (prev_child);
1631 g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
1633 pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
1634 GTK_TOOL_ITEM (prev_child)) + 1;
1638 *toolbar_p = toolbar;
1646 * _gtk_menu_is_empty:
1647 * @menu: a #GtkMenu or %NULL
1649 * Determines whether @menu is empty. A menu is considered empty if it
1650 * the only visible children are tearoff menu items or "filler" menu
1651 * items which were inserted to mark the menu as empty.
1653 * This function is used by #GtkAction.
1655 * Return value: whether @menu is empty.
1658 _gtk_menu_is_empty (GtkWidget *menu)
1660 GList *children, *cur;
1662 g_return_val_if_fail (menu == NULL || GTK_IS_MENU (menu), TRUE);
1667 children = gtk_container_get_children (GTK_CONTAINER (menu));
1672 if (GTK_WIDGET_VISIBLE (cur->data))
1674 if (!GTK_IS_TEAROFF_MENU_ITEM (cur->data) &&
1675 !g_object_get_data (cur->data, "gtk-empty-menu-item"))
1685 SEPARATOR_MODE_SMART,
1686 SEPARATOR_MODE_VISIBLE,
1687 SEPARATOR_MODE_HIDDEN
1690 void _gtk_action_sync_menu_visible (GtkAction *action,
1695 update_smart_separators (GtkWidget *proxy)
1697 GtkWidget *parent = NULL;
1699 if (GTK_IS_MENU (proxy) || GTK_IS_TOOLBAR (proxy))
1701 else if (GTK_IS_MENU_ITEM (proxy) || GTK_IS_TOOL_ITEM (proxy))
1702 parent = gtk_widget_get_parent (proxy);
1708 GList *children, *cur, *last;
1711 children = gtk_container_get_children (GTK_CONTAINER (parent));
1721 if (g_object_get_data (cur->data, "gtk-empty-menu-item"))
1724 if (GTK_IS_SEPARATOR_MENU_ITEM (cur->data) ||
1725 GTK_IS_SEPARATOR_TOOL_ITEM (cur->data))
1728 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cur->data),
1729 "gtk-separator-mode"));
1732 case SEPARATOR_MODE_VISIBLE:
1733 gtk_widget_show (GTK_WIDGET (cur->data));
1737 case SEPARATOR_MODE_HIDDEN:
1738 gtk_widget_hide (GTK_WIDGET (cur->data));
1740 case SEPARATOR_MODE_SMART:
1743 gtk_widget_show (GTK_WIDGET (cur->data));
1748 gtk_widget_hide (GTK_WIDGET (cur->data));
1752 else if (GTK_WIDGET_VISIBLE (cur->data))
1755 if (GTK_IS_TEAROFF_MENU_ITEM (cur->data) || cur->data == filler)
1768 gtk_widget_hide (GTK_WIDGET (last->data));
1770 if (GTK_IS_MENU (parent))
1774 item = gtk_menu_get_attach_widget (GTK_MENU (parent));
1775 _gtk_action_sync_menu_visible (NULL, item, empty);
1776 g_object_set (G_OBJECT (filler), "visible", empty, NULL);
1782 update_node (GtkUIManager *self,
1784 gboolean add_tearoffs)
1790 #ifdef DEBUG_UI_MANAGER
1794 g_return_if_fail (node != NULL);
1795 g_return_if_fail (NODE_INFO (node) != NULL);
1797 info = NODE_INFO (node);
1799 #ifdef DEBUG_UI_MANAGER
1800 g_print ("update_node name=%s dirty=%d (", info->name, info->dirty);
1801 for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next)
1803 NodeUIReference *ref = tmp->data;
1804 g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id);
1813 const gchar *action_name;
1814 NodeUIReference *ref;
1816 if (info->uifiles == NULL) {
1817 /* We may need to remove this node.
1818 * This must be done in post order
1820 goto recurse_children;
1823 ref = info->uifiles->data;
1824 action_name = g_quark_to_string (ref->action_quark);
1825 action = get_action_by_name (self, action_name);
1827 info->dirty = FALSE;
1829 /* Check if the node doesn't have an action and must have an action */
1830 if (action == NULL &&
1831 info->type != NODE_TYPE_ROOT &&
1832 info->type != NODE_TYPE_MENUBAR &&
1833 info->type != NODE_TYPE_TOOLBAR &&
1834 info->type != NODE_TYPE_POPUP &&
1835 info->type != NODE_TYPE_SEPARATOR &&
1836 info->type != NODE_TYPE_MENU_PLACEHOLDER &&
1837 info->type != NODE_TYPE_TOOLBAR_PLACEHOLDER)
1839 g_warning ("%s: missing action", info->name);
1841 goto recurse_children;
1845 gtk_action_set_accel_group (action, self->private_data->accel_group);
1847 /* If the widget already has a proxy and the action hasn't changed, then
1848 * we only have to update the tearoff menu items.
1850 if (info->proxy != NULL && action == info->action)
1852 if (info->type == NODE_TYPE_MENU)
1857 menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
1858 siblings = gtk_container_get_children (GTK_CONTAINER (menu));
1859 if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1860 g_object_set (G_OBJECT (siblings->data), "visible", add_tearoffs, 0);
1863 goto recurse_children;
1868 case NODE_TYPE_MENUBAR:
1869 if (info->proxy == NULL)
1871 info->proxy = gtk_menu_bar_new ();
1872 gtk_widget_show (info->proxy);
1873 g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
1876 case NODE_TYPE_POPUP:
1877 if (info->proxy == NULL)
1878 info->proxy = gtk_menu_new ();
1880 case NODE_TYPE_MENU:
1882 GtkWidget *prev_submenu = NULL;
1885 /* remove the proxy if it is of the wrong type ... */
1886 if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
1887 GTK_ACTION_GET_CLASS (action)->menu_item_type)
1889 prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
1892 g_object_ref (prev_submenu);
1893 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
1895 gtk_action_disconnect_proxy (info->action, info->proxy);
1896 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1900 /* create proxy if needed ... */
1901 if (info->proxy == NULL)
1903 GtkWidget *menushell;
1906 if (find_menu_position (node, &menushell, &pos))
1911 info->proxy = gtk_action_create_menu_item (action);
1912 menu = gtk_menu_new ();
1913 tearoff = gtk_tearoff_menu_item_new ();
1914 gtk_widget_set_no_show_all (tearoff, TRUE);
1915 gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff);
1916 filler = gtk_menu_item_new_with_label (_("Empty"));
1917 g_object_set_data (G_OBJECT (filler),
1918 "gtk-empty-menu-item",
1919 GINT_TO_POINTER (TRUE));
1920 gtk_widget_set_sensitive (filler, FALSE);
1921 gtk_widget_set_no_show_all (filler, TRUE);
1922 gtk_menu_shell_append (GTK_MENU_SHELL (menu), filler);
1923 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu);
1924 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos);
1928 gtk_action_connect_proxy (action, info->proxy);
1932 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),
1934 g_object_unref (prev_submenu);
1936 menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
1937 siblings = gtk_container_get_children (GTK_CONTAINER (menu));
1938 if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1939 g_object_set (G_OBJECT (siblings->data), "visible", add_tearoffs, 0);
1942 case NODE_TYPE_UNDECIDED:
1943 g_warning ("found 'undecided node!");
1945 case NODE_TYPE_ROOT:
1947 case NODE_TYPE_TOOLBAR:
1948 if (info->proxy == NULL)
1950 info->proxy = gtk_toolbar_new ();
1951 gtk_widget_show (info->proxy);
1952 g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
1955 case NODE_TYPE_MENU_PLACEHOLDER:
1956 /* create menu items for placeholders if necessary ... */
1957 if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) ||
1958 !GTK_IS_SEPARATOR_MENU_ITEM (info->extra))
1962 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1968 gtk_container_remove (GTK_CONTAINER (info->extra->parent),
1973 if (info->proxy == NULL)
1975 GtkWidget *menushell;
1978 if (find_menu_position (node, &menushell, &pos))
1980 info->proxy = gtk_separator_menu_item_new ();
1981 g_object_set_data (G_OBJECT (info->proxy),
1982 "gtk-separator-mode",
1983 GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
1984 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
1985 NODE_INFO (node)->proxy, pos);
1987 info->extra = gtk_separator_menu_item_new ();
1988 g_object_set_data (G_OBJECT (info->extra),
1989 "gtk-separator-mode",
1990 GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
1991 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
1992 NODE_INFO (node)->extra, pos+1);
1996 case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1997 /* create toolbar items for placeholders if necessary ... */
1998 if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) ||
1999 !GTK_IS_SEPARATOR_TOOL_ITEM (info->extra))
2003 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2009 gtk_container_remove (GTK_CONTAINER (info->extra->parent),
2014 if (info->proxy == NULL)
2019 if (find_toolbar_position (node, &toolbar, &pos))
2023 item = gtk_separator_tool_item_new ();
2024 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2025 info->proxy = GTK_WIDGET (item);
2026 g_object_set_data (G_OBJECT (info->proxy),
2027 "gtk-separator-mode",
2028 GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2030 item = gtk_separator_tool_item_new ();
2031 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1);
2032 info->extra = GTK_WIDGET (item);
2033 g_object_set_data (G_OBJECT (info->extra),
2034 "gtk-separator-mode",
2035 GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2039 case NODE_TYPE_MENUITEM:
2040 /* remove the proxy if it is of the wrong type ... */
2041 if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
2042 GTK_ACTION_GET_CLASS (action)->menu_item_type)
2044 g_signal_handlers_disconnect_by_func (info->proxy,
2045 G_CALLBACK (update_smart_separators),
2047 gtk_action_disconnect_proxy (info->action, info->proxy);
2048 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2052 /* create proxy if needed ... */
2053 if (info->proxy == NULL)
2055 GtkWidget *menushell;
2058 if (find_menu_position (node, &menushell, &pos))
2060 info->proxy = gtk_action_create_menu_item (action);
2062 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2068 g_signal_handlers_disconnect_by_func (info->proxy,
2069 G_CALLBACK (update_smart_separators),
2071 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
2072 gtk_action_connect_proxy (action, info->proxy);
2074 g_signal_connect (info->proxy, "notify::visible",
2075 G_CALLBACK (update_smart_separators), 0);
2077 case NODE_TYPE_TOOLITEM:
2078 /* remove the proxy if it is of the wrong type ... */
2079 if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
2080 GTK_ACTION_GET_CLASS (action)->toolbar_item_type)
2082 g_signal_handlers_disconnect_by_func (info->proxy,
2083 G_CALLBACK (update_smart_separators),
2085 gtk_action_disconnect_proxy (info->action, info->proxy);
2086 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2090 /* create proxy if needed ... */
2091 if (info->proxy == NULL)
2096 if (find_toolbar_position (node, &toolbar, &pos))
2098 info->proxy = gtk_action_create_tool_item (action);
2100 gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
2101 GTK_TOOL_ITEM (info->proxy), pos);
2103 /* FIXME: we must trigger the notify::tooltip handler, since
2104 * tooltips on toolitems can't be set before the toolitem
2105 * is added to the toolbar.
2107 g_object_get (G_OBJECT (action), "tooltip", &tooltip, NULL);
2108 g_object_set (G_OBJECT (action), "tooltip", tooltip, NULL);
2114 g_signal_handlers_disconnect_by_func (info->proxy,
2115 G_CALLBACK (update_smart_separators),
2117 gtk_action_connect_proxy (action, info->proxy);
2119 g_signal_connect (info->proxy, "notify::visible",
2120 G_CALLBACK (update_smart_separators), 0);
2122 case NODE_TYPE_SEPARATOR:
2123 if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR ||
2124 NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
2129 if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy))
2131 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2136 if (find_toolbar_position (node, &toolbar, &pos))
2138 GtkToolItem *item = gtk_separator_tool_item_new ();
2139 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2140 info->proxy = GTK_WIDGET (item);
2141 gtk_widget_set_no_show_all (info->proxy, TRUE);
2142 g_object_set_data (G_OBJECT (info->proxy),
2143 "gtk-separator-mode",
2144 GINT_TO_POINTER (SEPARATOR_MODE_SMART));
2145 gtk_widget_show (info->proxy);
2150 GtkWidget *menushell;
2153 if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy))
2155 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2160 if (find_menu_position (node, &menushell, &pos))
2162 info->proxy = gtk_separator_menu_item_new ();
2163 gtk_widget_set_no_show_all (info->proxy, TRUE);
2164 g_object_set_data (G_OBJECT (info->proxy),
2165 "gtk-separator-mode",
2166 GINT_TO_POINTER (SEPARATOR_MODE_SMART));
2167 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2169 gtk_widget_show (info->proxy);
2173 case NODE_TYPE_ACCELERATOR:
2174 gtk_action_connect_accelerator (action);
2179 g_object_ref (action);
2181 g_object_unref (info->action);
2182 info->action = action;
2186 /* process children */
2187 child = node->children;
2193 child = current->next;
2194 update_node (self, current, add_tearoffs && (info->type != NODE_TYPE_POPUP));
2199 if (info->type == NODE_TYPE_MENU)
2200 update_smart_separators (gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy)));
2201 else if (info->type == NODE_TYPE_TOOLBAR)
2202 update_smart_separators (info->proxy);
2205 /* handle cleanup of dead nodes */
2206 if (node->children == NULL && info->uifiles == NULL)
2209 gtk_widget_destroy (info->proxy);
2211 gtk_widget_destroy (info->extra);
2212 if (info->type == NODE_TYPE_ACCELERATOR)
2213 gtk_action_disconnect_accelerator (info->action);
2215 g_node_destroy (node);
2220 do_updates (GtkUIManager *self)
2222 /* this function needs to check through the tree for dirty nodes.
2223 * For such nodes, it needs to do the following:
2225 * 1) check if they are referenced by any loaded UI files anymore.
2226 * In which case, the proxy widget should be destroyed, unless
2227 * there are any subnodes.
2229 * 2) lookup the action for this node again. If it is different to
2230 * the current one (or if no previous action has been looked up),
2231 * the proxy is reconnected to the new action (or a new proxy widget
2232 * is created and added to the parent container).
2234 update_node (self, self->private_data->root_node,
2235 self->private_data->add_tearoffs);
2237 self->private_data->update_tag = 0;
2243 queue_update (GtkUIManager *self)
2245 if (self->private_data->update_tag != 0)
2248 self->private_data->update_tag = g_idle_add ((GSourceFunc)do_updates, self);
2253 * gtk_ui_manager_ensure_update:
2254 * @self: a #GtkUIManager
2256 * Makes sure that all pending updates to the UI have been completed.
2258 * This may occasionally be necessary, since #GtkUIManager updates the
2259 * UI in an idle function. A typical example where this function is
2260 * useful is to enforce that the menubar and toolbar have been added to
2261 * the main window before showing it:
2264 * gtk_container_add (GTK_CONTAINER (window), vbox);
2265 * g_signal_connect (merge, "add_widget",
2266 * G_CALLBACK (add_widget), vbox);
2267 * gtk_ui_manager_add_ui_from_file (merge, "my-menus");
2268 * gtk_ui_manager_add_ui_from_file (merge, "my-toolbars");
2269 * gtk_ui_manager_ensure_update (merge);
2270 * gtk_widget_show (window);
2272 * </informalexample>
2277 gtk_ui_manager_ensure_update (GtkUIManager *self)
2279 if (self->private_data->update_tag != 0)
2281 g_source_remove (self->private_data->update_tag);
2287 dirty_traverse_func (GNode *node,
2290 NODE_INFO (node)->dirty = TRUE;
2295 dirty_all_nodes (GtkUIManager *self)
2297 g_node_traverse (self->private_data->root_node,
2298 G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2299 dirty_traverse_func, NULL);
2300 queue_update (self);
2303 static const gchar *open_tag_format[] = {
2306 "%*s<menubar name=\"%s\">\n",
2307 "%*s<menu name=\"%s\" action=\"%s\">\n",
2308 "%*s<toolbar name=\"%s\">\n",
2309 "%*s<placeholder name=\"%s\">\n",
2310 "%*s<placeholder name=\"%s\">\n",
2311 "%*s<popup name=\"%s\">\n",
2312 "%*s<menuitem name=\"%s\" action=\"%s\"/>\n",
2313 "%*s<toolitem name=\"%s\" action=\"%s\"/>\n",
2314 "%*s<separator name=\"%s\"/>\n",
2315 "%*s<accelerator name=\"%s\" action=\"%s\"/>\n",
2318 static const gchar *close_tag_format[] = {
2319 "%*s</UNDECIDED>\n",
2324 "%*s</placeholder>\n",
2325 "%*s</placeholder>\n",
2334 print_node (GtkUIManager *self,
2344 g_string_append_printf (buffer, open_tag_format[mnode->type],
2347 g_quark_to_string (mnode->action_name));
2349 for (child = node->children; child != NULL; child = child->next)
2350 print_node (self, child, indent_level + 2, buffer);
2352 g_string_append_printf (buffer, close_tag_format[mnode->type],
2358 * gtk_ui_manager_get_ui:
2359 * @self: a #GtkUIManager
2361 * Creates a <link linkend="XML-UI">UI definition</link> of the merged UI.
2363 * Return value: A newly allocated string containing an XML representation of
2369 gtk_ui_manager_get_ui (GtkUIManager *self)
2373 buffer = g_string_new (NULL);
2375 gtk_ui_manager_ensure_update (self);
2377 print_node (self, self->private_data->root_node, 0, buffer);
2379 return g_string_free (buffer, FALSE);