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 "gtkmenutoolbutton.h"
40 #include "gtkseparatormenuitem.h"
41 #include "gtkseparatortoolitem.h"
42 #include "gtktearoffmenuitem.h"
43 #include "gtktoolbar.h"
44 #include "gtkuimanager.h"
45 #include "gtkprivate.h"
48 #undef DEBUG_UI_MANAGER
57 NODE_TYPE_MENU_PLACEHOLDER,
58 NODE_TYPE_TOOLBAR_PLACEHOLDER,
66 typedef struct _Node Node;
76 GtkWidget *extra; /* second separator for placeholders */
81 guint expand : 1; /* used for separators */
84 #define GTK_UI_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_UI_MANAGER, GtkUIManagerPrivate))
86 struct _GtkUIManagerPrivate
88 GtkAccelGroup *accel_group;
97 gboolean add_tearoffs;
100 #define NODE_INFO(node) ((Node *)node->data)
102 typedef struct _NodeUIReference NodeUIReference;
104 struct _NodeUIReference
110 static void gtk_ui_manager_class_init (GtkUIManagerClass *class);
111 static void gtk_ui_manager_init (GtkUIManager *self);
112 static void gtk_ui_manager_finalize (GObject *object);
113 static void gtk_ui_manager_set_property (GObject *object,
117 static void gtk_ui_manager_get_property (GObject *object,
121 static GtkWidget * gtk_ui_manager_real_get_widget (GtkUIManager *manager,
123 static GtkAction * gtk_ui_manager_real_get_action (GtkUIManager *manager,
125 static void queue_update (GtkUIManager *self);
126 static void dirty_all_nodes (GtkUIManager *self);
127 static void mark_node_dirty (GNode *node);
128 static GNode * get_child_node (GtkUIManager *self,
131 const gchar *childname,
132 gint childname_length,
136 static GNode * get_node (GtkUIManager *self,
140 static gboolean free_node (GNode *node);
141 static void node_prepend_ui_reference (GNode *node,
143 GQuark action_quark);
144 static void node_remove_ui_reference (GNode *node,
166 static GObjectClass *parent_class = NULL;
167 static guint ui_manager_signals[LAST_SIGNAL] = { 0 };
169 static GMemChunk *merge_node_chunk = NULL;
172 gtk_ui_manager_get_type (void)
174 static GtkType type = 0;
178 static const GTypeInfo type_info =
180 sizeof (GtkUIManagerClass),
181 (GBaseInitFunc) NULL,
182 (GBaseFinalizeFunc) NULL,
183 (GClassInitFunc) gtk_ui_manager_class_init,
184 (GClassFinalizeFunc) NULL,
187 sizeof (GtkUIManager),
189 (GInstanceInitFunc) gtk_ui_manager_init,
192 type = g_type_register_static (G_TYPE_OBJECT,
200 gtk_ui_manager_class_init (GtkUIManagerClass *klass)
202 GObjectClass *gobject_class;
204 parent_class = g_type_class_peek_parent (klass);
206 gobject_class = G_OBJECT_CLASS (klass);
208 if (!merge_node_chunk)
209 merge_node_chunk = g_mem_chunk_create (Node, 64,
212 gobject_class->finalize = gtk_ui_manager_finalize;
213 gobject_class->set_property = gtk_ui_manager_set_property;
214 gobject_class->get_property = gtk_ui_manager_get_property;
215 klass->get_widget = gtk_ui_manager_real_get_widget;
216 klass->get_action = gtk_ui_manager_real_get_action;
219 * GtkUIManager:add-tearoffs:
221 * The "add-tearoffs" property controls whether generated menus
222 * have tearoff menu items.
224 * Note that this only affects regular menus. Generated popup
225 * menus never have tearoff menu items.
229 g_object_class_install_property (gobject_class,
231 g_param_spec_boolean ("add-tearoffs",
232 P_("Add tearoffs to menus"),
233 P_("Whether tearoff menu items should be added to menus"),
235 GTK_PARAM_READWRITE));
237 g_object_class_install_property (gobject_class,
239 g_param_spec_string ("ui",
240 P_("Merged UI definition"),
241 P_("An XML string describing the merged UI"),
243 GTK_PARAM_READABLE));
247 * GtkUIManager::add-widget:
248 * @merge: a #GtkUIManager
249 * @widget: the added widget
251 * The add_widget signal is emitted for each generated menubar and toolbar.
252 * It is not emitted for generated popup menus, which can be obtained by
253 * gtk_ui_manager_get_widget().
257 ui_manager_signals[ADD_WIDGET] =
258 g_signal_new ("add_widget",
259 G_OBJECT_CLASS_TYPE (klass),
260 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
261 G_STRUCT_OFFSET (GtkUIManagerClass, add_widget),
263 g_cclosure_marshal_VOID__OBJECT,
268 * GtkUIManager::actions-changed:
269 * @merge: a #GtkUIManager
271 * The "actions-changed" signal is emitted whenever the set of actions
276 ui_manager_signals[ACTIONS_CHANGED] =
277 g_signal_new ("actions_changed",
278 G_OBJECT_CLASS_TYPE (klass),
279 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
280 G_STRUCT_OFFSET (GtkUIManagerClass, actions_changed),
282 g_cclosure_marshal_VOID__VOID,
286 * GtkUIManager::connect-proxy:
287 * @uimanager: the ui manager
288 * @action: the action
291 * The connect_proxy signal is emitted after connecting a proxy to
292 * an action in the group.
294 * This is intended for simple customizations for which a custom action
295 * class would be too clumsy, e.g. showing tooltips for menuitems in the
300 ui_manager_signals[CONNECT_PROXY] =
301 g_signal_new ("connect_proxy",
302 G_OBJECT_CLASS_TYPE (klass),
303 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
304 G_STRUCT_OFFSET (GtkUIManagerClass, connect_proxy),
306 _gtk_marshal_VOID__OBJECT_OBJECT,
312 * GtkUIManager::disconnect-proxy:
313 * @uimanager: the ui manager
314 * @action: the action
317 * The disconnect_proxy signal is emitted after disconnecting a proxy
318 * from an action in the group.
322 ui_manager_signals[DISCONNECT_PROXY] =
323 g_signal_new ("disconnect_proxy",
324 G_OBJECT_CLASS_TYPE (klass),
325 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
326 G_STRUCT_OFFSET (GtkUIManagerClass, disconnect_proxy),
328 _gtk_marshal_VOID__OBJECT_OBJECT,
334 * GtkUIManager::pre-activate:
335 * @uimanager: the ui manager
336 * @action: the action
338 * The pre_activate signal is emitted just before the @action
341 * This is intended for applications to get notification
342 * just before any action is activated.
346 ui_manager_signals[PRE_ACTIVATE] =
347 g_signal_new ("pre_activate",
348 G_OBJECT_CLASS_TYPE (klass),
349 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
350 G_STRUCT_OFFSET (GtkUIManagerClass, pre_activate),
352 _gtk_marshal_VOID__OBJECT,
357 * GtkUIManager::post-activate:
358 * @uimanager: the ui manager
359 * @action: the action
361 * The post_activate signal is emitted just after the @action
364 * This is intended for applications to get notification
365 * just after any action is activated.
369 ui_manager_signals[POST_ACTIVATE] =
370 g_signal_new ("post_activate",
371 G_OBJECT_CLASS_TYPE (klass),
372 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
373 G_STRUCT_OFFSET (GtkUIManagerClass, post_activate),
375 _gtk_marshal_VOID__OBJECT,
379 klass->add_widget = NULL;
380 klass->actions_changed = NULL;
381 klass->connect_proxy = NULL;
382 klass->disconnect_proxy = NULL;
383 klass->pre_activate = NULL;
384 klass->post_activate = NULL;
386 g_type_class_add_private (gobject_class, sizeof (GtkUIManagerPrivate));
391 gtk_ui_manager_init (GtkUIManager *self)
396 self->private_data = GTK_UI_MANAGER_GET_PRIVATE (self);
398 self->private_data->accel_group = gtk_accel_group_new ();
400 self->private_data->root_node = NULL;
401 self->private_data->action_groups = NULL;
403 self->private_data->last_merge_id = 0;
404 self->private_data->add_tearoffs = FALSE;
406 merge_id = gtk_ui_manager_new_merge_id (self);
407 node = get_child_node (self, NULL, NULL, "ui", 2,
408 NODE_TYPE_ROOT, TRUE, FALSE);
409 node_prepend_ui_reference (node, merge_id, 0);
413 gtk_ui_manager_finalize (GObject *object)
415 GtkUIManager *self = GTK_UI_MANAGER (object);
417 if (self->private_data->update_tag != 0)
419 g_source_remove (self->private_data->update_tag);
420 self->private_data->update_tag = 0;
423 g_node_traverse (self->private_data->root_node,
424 G_POST_ORDER, G_TRAVERSE_ALL, -1,
425 (GNodeTraverseFunc)free_node, NULL);
426 g_node_destroy (self->private_data->root_node);
427 self->private_data->root_node = NULL;
429 g_list_foreach (self->private_data->action_groups,
430 (GFunc) g_object_unref, NULL);
431 g_list_free (self->private_data->action_groups);
432 self->private_data->action_groups = NULL;
434 g_object_unref (self->private_data->accel_group);
435 self->private_data->accel_group = NULL;
437 G_OBJECT_CLASS (parent_class)->finalize (object);
441 gtk_ui_manager_set_property (GObject *object,
446 GtkUIManager *self = GTK_UI_MANAGER (object);
450 case PROP_ADD_TEAROFFS:
451 gtk_ui_manager_set_add_tearoffs (self, g_value_get_boolean (value));
454 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
460 gtk_ui_manager_get_property (GObject *object,
465 GtkUIManager *self = GTK_UI_MANAGER (object);
469 case PROP_ADD_TEAROFFS:
470 g_value_set_boolean (value, self->private_data->add_tearoffs);
473 g_value_set_string (value, gtk_ui_manager_get_ui (self));
476 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
482 gtk_ui_manager_real_get_widget (GtkUIManager *self,
487 /* ensure that there are no pending updates before we get the
489 gtk_ui_manager_ensure_update (self);
491 node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
496 return NODE_INFO (node)->proxy;
500 gtk_ui_manager_real_get_action (GtkUIManager *self,
505 /* ensure that there are no pending updates before we get
507 gtk_ui_manager_ensure_update (self);
509 node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
514 return NODE_INFO (node)->action;
519 * gtk_ui_manager_new:
521 * Creates a new ui manager object.
523 * Return value: a new ui manager object.
528 gtk_ui_manager_new (void)
530 return g_object_new (GTK_TYPE_UI_MANAGER, NULL);
535 * gtk_ui_manager_get_add_tearoffs:
536 * @self: a #GtkUIManager
538 * Returns whether menus generated by this #GtkUIManager
539 * will have tearoff menu items.
541 * Return value: whether tearoff menu items are added
546 gtk_ui_manager_get_add_tearoffs (GtkUIManager *self)
548 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), FALSE);
550 return self->private_data->add_tearoffs;
555 * gtk_ui_manager_set_add_tearoffs:
556 * @self: a #GtkUIManager
557 * @add_tearoffs: whether tearoff menu items are added
559 * Sets the "add_tearoffs" property, which controls whether menus
560 * generated by this #GtkUIManager will have tearoff menu items.
562 * Note that this only affects regular menus. Generated popup
563 * menus never have tearoff menu items.
568 gtk_ui_manager_set_add_tearoffs (GtkUIManager *self,
569 gboolean add_tearoffs)
571 g_return_if_fail (GTK_IS_UI_MANAGER (self));
573 add_tearoffs = add_tearoffs != FALSE;
575 if (add_tearoffs != self->private_data->add_tearoffs)
577 self->private_data->add_tearoffs = add_tearoffs;
579 dirty_all_nodes (self);
581 g_object_notify (G_OBJECT (self), "add-tearoffs");
586 cb_proxy_connect_proxy (GtkActionGroup *group,
591 g_signal_emit (self, ui_manager_signals[CONNECT_PROXY], 0, action, proxy);
595 cb_proxy_disconnect_proxy (GtkActionGroup *group,
600 g_signal_emit (self, ui_manager_signals[DISCONNECT_PROXY], 0, action, proxy);
604 cb_proxy_pre_activate (GtkActionGroup *group,
608 g_signal_emit (self, ui_manager_signals[PRE_ACTIVATE], 0, action);
612 cb_proxy_post_activate (GtkActionGroup *group,
616 g_signal_emit (self, ui_manager_signals[POST_ACTIVATE], 0, action);
620 * gtk_ui_manager_insert_action_group:
621 * @self: a #GtkUIManager object
622 * @action_group: the action group to be inserted
623 * @pos: the position at which the group will be inserted.
625 * Inserts an action group into the list of action groups associated
626 * with @self. Actions in earlier groups hide actions with the same
627 * name in later groups.
632 gtk_ui_manager_insert_action_group (GtkUIManager *self,
633 GtkActionGroup *action_group,
636 g_return_if_fail (GTK_IS_UI_MANAGER (self));
637 g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
638 g_return_if_fail (g_list_find (self->private_data->action_groups,
639 action_group) == NULL);
641 g_object_ref (action_group);
642 self->private_data->action_groups =
643 g_list_insert (self->private_data->action_groups, action_group, pos);
644 g_object_connect (action_group,
645 "object_signal::connect_proxy", G_CALLBACK (cb_proxy_connect_proxy), self,
646 "object_signal::disconnect_proxy", G_CALLBACK (cb_proxy_disconnect_proxy), self,
647 "object_signal::pre_activate", G_CALLBACK (cb_proxy_pre_activate), self,
648 "object_signal::post_activate", G_CALLBACK (cb_proxy_post_activate), self,
651 /* dirty all nodes, as action bindings may change */
652 dirty_all_nodes (self);
654 g_signal_emit (self, ui_manager_signals[ACTIONS_CHANGED], 0);
658 * gtk_ui_manager_remove_action_group:
659 * @self: a #GtkUIManager object
660 * @action_group: the action group to be removed
662 * Removes an action group from the list of action groups associated
668 gtk_ui_manager_remove_action_group (GtkUIManager *self,
669 GtkActionGroup *action_group)
671 g_return_if_fail (GTK_IS_UI_MANAGER (self));
672 g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
673 g_return_if_fail (g_list_find (self->private_data->action_groups,
674 action_group) != NULL);
676 self->private_data->action_groups =
677 g_list_remove (self->private_data->action_groups, action_group);
679 g_object_disconnect (action_group,
680 "any_signal::connect_proxy", G_CALLBACK (cb_proxy_connect_proxy), self,
681 "any_signal::disconnect_proxy", G_CALLBACK (cb_proxy_disconnect_proxy), self,
682 "any_signal::pre_activate", G_CALLBACK (cb_proxy_pre_activate), self,
683 "any_signal::post_activate", G_CALLBACK (cb_proxy_post_activate), self,
685 g_object_unref (action_group);
687 /* dirty all nodes, as action bindings may change */
688 dirty_all_nodes (self);
690 g_signal_emit (self, ui_manager_signals[ACTIONS_CHANGED], 0);
694 * gtk_ui_manager_get_action_groups:
695 * @self: a #GtkUIManager object
697 * Returns the list of action groups associated with @self.
699 * Return value: a #GList of action groups. The list is owned by GTK+
700 * and should not be modified.
705 gtk_ui_manager_get_action_groups (GtkUIManager *self)
707 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
709 return self->private_data->action_groups;
713 * gtk_ui_manager_get_accel_group:
714 * @self: a #GtkUIManager object
716 * Returns the #GtkAccelGroup associated with @self.
718 * Return value: the #GtkAccelGroup.
723 gtk_ui_manager_get_accel_group (GtkUIManager *self)
725 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
727 return self->private_data->accel_group;
731 * gtk_ui_manager_get_widget:
732 * @self: a #GtkUIManager
735 * Looks up a widget by following a path.
736 * The path consists of the names specified in the XML description of the UI.
737 * separated by '/'. Elements which don't have a name or action attribute in
738 * the XML (e.g. <popup>) can be addressed by their XML element name
739 * (e.g. "popup"). The root element ("/ui") can be omitted in the path.
741 * Note that the widget found by following a path that ends in a <menu>
742 * element is the menuitem to which the menu is attached, not the menu itself.
744 * Also note that the widgets constructed by a ui manager are not tied to
745 * the lifecycle of the ui manager. If you add the widgets returned by this
746 * function to some container or explicitly ref them, they will survive the
747 * destruction of the ui manager.
749 * Return value: the widget found by following the path, or %NULL if no widget
755 gtk_ui_manager_get_widget (GtkUIManager *self,
758 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
759 g_return_val_if_fail (path != NULL, NULL);
761 return GTK_UI_MANAGER_GET_CLASS (self)->get_widget (self, path);
765 collect_toplevels (GNode *node,
769 GtkUIManagerItemType types;
773 switch (NODE_INFO (node)->type) {
774 case NODE_TYPE_MENUBAR:
775 if (data->types & GTK_UI_MANAGER_MENUBAR)
776 data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
778 case NODE_TYPE_TOOLBAR:
779 if (data->types & GTK_UI_MANAGER_TOOLBAR)
780 data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
782 case NODE_TYPE_POPUP:
783 if (data->types & GTK_UI_MANAGER_POPUP)
784 data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
791 * gtk_ui_manager_get_toplevels:
792 * @self: a #GtkUIManager
793 * @types: specifies the types of toplevel widgets to include. Allowed
794 * types are #GTK_UI_MANAGER_MENUBAR, #GTK_UI_MANAGER_TOOLBAR and
795 * #GTK_UI_MANAGER_POPUP.
797 * Obtains a list of all toplevel widgets of the requested types.
799 * Return value: a newly-allocated of all toplevel widgets of the requested
805 gtk_ui_manager_get_toplevels (GtkUIManager *self,
806 GtkUIManagerItemType types)
809 GtkUIManagerItemType types;
813 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
814 g_return_val_if_fail ((~(GTK_UI_MANAGER_MENUBAR |
815 GTK_UI_MANAGER_TOOLBAR |
816 GTK_UI_MANAGER_POPUP) & types) == 0, NULL);
822 g_node_children_foreach (self->private_data->root_node,
824 collect_toplevels, &data);
831 * gtk_ui_manager_get_action:
832 * @self: a #GtkUIManager
835 * Looks up an action by following a path. See gtk_ui_manager_get_widget()
836 * for more information about paths.
838 * Return value: the action whose proxy widget is found by following the path,
839 * or %NULL if no widget was found.
844 gtk_ui_manager_get_action (GtkUIManager *self,
847 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
848 g_return_val_if_fail (path != NULL, NULL);
850 return GTK_UI_MANAGER_GET_CLASS (self)->get_action (self, path);
854 get_child_node (GtkUIManager *self,
857 const gchar *childname,
858 gint childname_length,
869 for (child = parent->children; child != NULL; child = child->next)
871 if (NODE_INFO (child)->name &&
872 strlen (NODE_INFO (child)->name) == childname_length &&
873 !strncmp (NODE_INFO (child)->name, childname, childname_length))
875 /* if undecided about node type, set it */
876 if (NODE_INFO (child)->type == NODE_TYPE_UNDECIDED)
877 NODE_INFO (child)->type = node_type;
879 /* warn about type mismatch */
880 if (NODE_INFO (child)->type != NODE_TYPE_UNDECIDED &&
881 node_type != NODE_TYPE_UNDECIDED &&
882 NODE_INFO (child)->type != node_type)
883 g_warning ("node type doesn't match %d (%s is type %d)",
885 NODE_INFO (child)->name,
886 NODE_INFO (child)->type);
892 if (!child && create)
896 mnode = g_chunk_new0 (Node, merge_node_chunk);
897 mnode->type = node_type;
898 mnode->name = g_strndup (childname, childname_length);
903 child = g_node_insert_before (parent, sibling,
906 child = g_node_insert_after (parent, sibling,
912 child = g_node_prepend_data (parent, mnode);
914 child = g_node_append_data (parent, mnode);
917 mark_node_dirty (child);
922 /* handle root node */
923 if (self->private_data->root_node)
925 child = self->private_data->root_node;
926 if (strncmp (NODE_INFO (child)->name, childname, childname_length) != 0)
927 g_warning ("root node name '%s' doesn't match '%s'",
928 childname, NODE_INFO (child)->name);
929 if (NODE_INFO (child)->type != NODE_TYPE_ROOT)
930 g_warning ("base element must be of type ROOT");
936 mnode = g_chunk_new0 (Node, merge_node_chunk);
937 mnode->type = node_type;
938 mnode->name = g_strndup (childname, childname_length);
941 child = self->private_data->root_node = g_node_new (mnode);
949 get_node (GtkUIManager *self,
954 const gchar *pos, *end;
955 GNode *parent, *node;
957 if (strncmp ("/ui", path, 3) == 0)
960 end = path + strlen (path);
962 parent = node = NULL;
968 slash = strchr (pos, '/');
970 length = slash - pos;
972 length = strlen (pos);
974 node = get_child_node (self, parent, NULL, pos, length, NODE_TYPE_UNDECIDED,
979 pos += length + 1; /* move past the node name and the slash too */
983 if (node != NULL && NODE_INFO (node)->type == NODE_TYPE_UNDECIDED)
984 NODE_INFO (node)->type = node_type;
990 free_node (GNode *node)
992 Node *info = NODE_INFO (node);
994 g_list_foreach (info->uifiles, (GFunc) g_free, NULL);
995 g_list_free (info->uifiles);
998 g_object_unref (info->action);
1000 g_object_unref (info->proxy);
1002 g_object_unref (info->extra);
1003 g_free (info->name);
1004 g_chunk_free (info, merge_node_chunk);
1010 * gtk_ui_manager_new_merge_id:
1011 * @self: a #GtkUIManager
1013 * Returns an unused merge id, suitable for use with
1014 * gtk_ui_manager_add_ui().
1016 * Return value: an unused merge id.
1021 gtk_ui_manager_new_merge_id (GtkUIManager *self)
1023 self->private_data->last_merge_id++;
1025 return self->private_data->last_merge_id;
1029 node_prepend_ui_reference (GNode *gnode,
1031 GQuark action_quark)
1033 Node *node = NODE_INFO (gnode);
1034 NodeUIReference *reference = NULL;
1036 if (node->uifiles &&
1037 ((NodeUIReference *)node->uifiles->data)->merge_id == merge_id)
1038 reference = node->uifiles->data;
1041 reference = g_new (NodeUIReference, 1);
1042 node->uifiles = g_list_prepend (node->uifiles, reference);
1045 reference->merge_id = merge_id;
1046 reference->action_quark = action_quark;
1048 mark_node_dirty (gnode);
1052 node_remove_ui_reference (GNode *gnode,
1055 Node *node = NODE_INFO (gnode);
1058 for (p = node->uifiles; p != NULL; p = p->next)
1060 NodeUIReference *reference = p->data;
1062 if (reference->merge_id == merge_id)
1064 if (p == node->uifiles)
1065 mark_node_dirty (gnode);
1066 node->uifiles = g_list_delete_link (node->uifiles, p);
1074 /* -------------------- The UI file parser -------------------- */
1088 typedef struct _ParseContext ParseContext;
1089 struct _ParseContext
1092 ParseState prev_state;
1102 start_element_handler (GMarkupParseContext *context,
1103 const gchar *element_name,
1104 const gchar **attribute_names,
1105 const gchar **attribute_values,
1109 ParseContext *ctx = user_data;
1110 GtkUIManager *self = ctx->self;
1113 const gchar *node_name;
1114 const gchar *action;
1115 GQuark action_quark;
1117 gboolean expand = FALSE;
1119 gboolean raise_error = TRUE;
1126 for (i = 0; attribute_names[i] != NULL; i++)
1128 if (!strcmp (attribute_names[i], "name"))
1130 node_name = attribute_values[i];
1132 else if (!strcmp (attribute_names[i], "action"))
1134 action = attribute_values[i];
1135 action_quark = g_quark_from_string (attribute_values[i]);
1137 else if (!strcmp (attribute_names[i], "position"))
1139 top = !strcmp (attribute_values[i], "top");
1141 else if (!strcmp (attribute_names[i], "expand"))
1143 expand = !strcmp (attribute_values[i], "true");
1147 gint line_number, char_number;
1149 g_markup_parse_context_get_position (context,
1150 &line_number, &char_number);
1153 G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1154 _("Unknown attribute '%s' on line %d char %d"),
1156 line_number, char_number);
1161 /* Work out a name for this node. Either the name attribute, or
1162 * the action, or the element name */
1163 if (node_name == NULL)
1168 node_name = element_name;
1171 switch (element_name[0])
1174 if (ctx->state == STATE_ROOT && !strcmp (element_name, "accelerator"))
1176 ctx->state = STATE_ACCELERATOR;
1177 ctx->current = get_child_node (self, ctx->current, NULL,
1178 node_name, strlen (node_name),
1179 NODE_TYPE_ACCELERATOR,
1181 if (NODE_INFO (ctx->current)->action_name == 0)
1182 NODE_INFO (ctx->current)->action_name = action_quark;
1184 node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1186 raise_error = FALSE;
1190 if (ctx->state == STATE_START && !strcmp (element_name, "ui"))
1192 ctx->state = STATE_ROOT;
1193 ctx->current = self->private_data->root_node;
1194 raise_error = FALSE;
1196 node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1200 if (ctx->state == STATE_ROOT && !strcmp (element_name, "menubar"))
1202 ctx->state = STATE_MENU;
1203 ctx->current = get_child_node (self, ctx->current, NULL,
1204 node_name, strlen (node_name),
1207 if (NODE_INFO (ctx->current)->action_name == 0)
1208 NODE_INFO (ctx->current)->action_name = action_quark;
1210 node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1211 mark_node_dirty (ctx->current);
1213 raise_error = FALSE;
1215 else if (ctx->state == STATE_MENU && !strcmp (element_name, "menu"))
1217 ctx->current = get_child_node (self, ctx->current, NULL,
1218 node_name, strlen (node_name),
1221 if (NODE_INFO (ctx->current)->action_name == 0)
1222 NODE_INFO (ctx->current)->action_name = action_quark;
1224 node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1226 raise_error = FALSE;
1228 else if (ctx->state == STATE_TOOLITEM && !strcmp (element_name, "menu"))
1230 ctx->state = STATE_MENU;
1232 ctx->current = get_child_node (self, g_node_last_child (ctx->current), NULL,
1233 node_name, strlen (node_name),
1236 if (NODE_INFO (ctx->current)->action_name == 0)
1237 NODE_INFO (ctx->current)->action_name = action_quark;
1239 node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1241 raise_error = FALSE;
1243 else if (ctx->state == STATE_MENU && !strcmp (element_name, "menuitem"))
1247 ctx->state = STATE_MENUITEM;
1248 node = get_child_node (self, ctx->current, NULL,
1249 node_name, strlen (node_name),
1252 if (NODE_INFO (node)->action_name == 0)
1253 NODE_INFO (node)->action_name = action_quark;
1255 node_prepend_ui_reference (node, ctx->merge_id, action_quark);
1257 raise_error = FALSE;
1261 if (ctx->state == STATE_ROOT && !strcmp (element_name, "popup"))
1263 ctx->state = STATE_MENU;
1264 ctx->current = get_child_node (self, ctx->current, NULL,
1265 node_name, strlen (node_name),
1268 if (NODE_INFO (ctx->current)->action_name == 0)
1269 NODE_INFO (ctx->current)->action_name = action_quark;
1271 node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1273 raise_error = FALSE;
1275 else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
1276 !strcmp (element_name, "placeholder"))
1278 if (ctx->state == STATE_TOOLBAR)
1279 ctx->current = get_child_node (self, ctx->current, NULL,
1280 node_name, strlen (node_name),
1281 NODE_TYPE_TOOLBAR_PLACEHOLDER,
1284 ctx->current = get_child_node (self, ctx->current, NULL,
1285 node_name, strlen (node_name),
1286 NODE_TYPE_MENU_PLACEHOLDER,
1289 node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1291 raise_error = FALSE;
1295 if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
1296 !strcmp (element_name, "separator"))
1301 if (ctx->state == STATE_TOOLBAR)
1302 ctx->state = STATE_TOOLITEM;
1304 ctx->state = STATE_MENUITEM;
1305 if (!strcmp (node_name, "separator"))
1311 length = strlen (node_name);
1312 node = get_child_node (self, ctx->current, NULL,
1314 NODE_TYPE_SEPARATOR,
1317 NODE_INFO (node)->expand = expand;
1319 if (NODE_INFO (node)->action_name == 0)
1320 NODE_INFO (node)->action_name = action_quark;
1322 node_prepend_ui_reference (node, ctx->merge_id, action_quark);
1324 raise_error = FALSE;
1328 if (ctx->state == STATE_ROOT && !strcmp (element_name, "toolbar"))
1330 ctx->state = STATE_TOOLBAR;
1331 ctx->current = get_child_node (self, ctx->current, NULL,
1332 node_name, strlen (node_name),
1335 if (NODE_INFO (ctx->current)->action_name == 0)
1336 NODE_INFO (ctx->current)->action_name = action_quark;
1338 node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1340 raise_error = FALSE;
1342 else if (ctx->state == STATE_TOOLBAR && !strcmp (element_name, "toolitem"))
1346 ctx->state = STATE_TOOLITEM;
1347 node = get_child_node (self, ctx->current, NULL,
1348 node_name, strlen (node_name),
1351 if (NODE_INFO (node)->action_name == 0)
1352 NODE_INFO (node)->action_name = action_quark;
1354 node_prepend_ui_reference (node, ctx->merge_id, action_quark);
1356 raise_error = FALSE;
1364 gint line_number, char_number;
1366 g_markup_parse_context_get_position (context,
1367 &line_number, &char_number);
1370 G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1371 _("Unexpected start tag '%s' on line %d char %d"),
1373 line_number, char_number);
1378 end_element_handler (GMarkupParseContext *context,
1379 const gchar *element_name,
1383 ParseContext *ctx = user_data;
1389 /* no need to GError here, GMarkup already catches this */
1392 ctx->current = NULL;
1393 ctx->state = STATE_END;
1397 case STATE_ACCELERATOR:
1398 ctx->current = ctx->current->parent;
1399 if (NODE_INFO (ctx->current)->type == NODE_TYPE_ROOT)
1400 ctx->state = STATE_ROOT;
1401 else if (NODE_INFO (ctx->current)->type == NODE_TYPE_TOOLITEM)
1403 ctx->current = ctx->current->parent;
1404 ctx->state = STATE_TOOLITEM;
1406 /* else, stay in same state */
1408 case STATE_MENUITEM:
1409 ctx->state = STATE_MENU;
1411 case STATE_TOOLITEM:
1412 ctx->state = STATE_TOOLBAR;
1418 cleanup (GMarkupParseContext *context,
1422 ParseContext *ctx = user_data;
1424 ctx->current = NULL;
1425 /* should also walk through the tree and get rid of nodes related to
1426 * this UI file's tag */
1428 gtk_ui_manager_remove_ui (ctx->self, ctx->merge_id);
1432 xml_isspace (char c)
1434 return c == ' ' || c == '\t' || c == '\n' || c == '\r';
1438 text_handler (GMarkupParseContext *context,
1448 end = text + text_len;
1449 while (p != end && xml_isspace (*p))
1454 gint line_number, char_number;
1456 g_markup_parse_context_get_position (context,
1457 &line_number, &char_number);
1460 G_MARKUP_ERROR_INVALID_CONTENT,
1461 _("Unexpected character data on line %d char %d"),
1462 line_number, char_number);
1467 static GMarkupParser ui_parser = {
1468 start_element_handler,
1469 end_element_handler,
1476 add_ui_from_string (GtkUIManager *self,
1477 const gchar *buffer,
1479 gboolean needs_root,
1482 ParseContext ctx = { 0 };
1483 GMarkupParseContext *context;
1485 ctx.state = STATE_START;
1488 ctx.merge_id = gtk_ui_manager_new_merge_id (self);
1490 context = g_markup_parse_context_new (&ui_parser, 0, &ctx, NULL);
1493 if (!g_markup_parse_context_parse (context, "<ui>", -1, error))
1496 if (!g_markup_parse_context_parse (context, buffer, length, error))
1500 if (!g_markup_parse_context_parse (context, "</ui>", -1, error))
1503 if (!g_markup_parse_context_end_parse (context, error))
1506 g_markup_parse_context_free (context);
1508 queue_update (self);
1510 g_object_notify (G_OBJECT (self), "ui");
1512 return ctx.merge_id;
1516 g_markup_parse_context_free (context);
1522 * gtk_ui_manager_add_ui_from_string:
1523 * @self: a #GtkUIManager object
1524 * @buffer: the string to parse
1525 * @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
1526 * @error: return location for an error
1528 * Parses a string containing a <link linkend="XML-UI">UI definition</link> and
1529 * merges it with the current contents of @self. An enclosing <ui>
1530 * element is added if it is missing.
1532 * Return value: The merge id for the merged UI. The merge id can be used
1533 * to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1534 * the return value is 0.
1539 gtk_ui_manager_add_ui_from_string (GtkUIManager *self,
1540 const gchar *buffer,
1544 gboolean needs_root = TRUE;
1548 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), 0);
1549 g_return_val_if_fail (buffer != NULL, 0);
1552 length = strlen (buffer);
1555 end = buffer + length;
1556 while (p != end && xml_isspace (*p))
1559 if (end - p >= 4 && strncmp (p, "<ui>", 4) == 0)
1562 return add_ui_from_string (self, buffer, length, needs_root, error);
1566 * gtk_ui_manager_add_ui_from_file:
1567 * @self: a #GtkUIManager object
1568 * @filename: the name of the file to parse
1569 * @error: return location for an error
1571 * Parses a file containing a <link linkend="XML-UI">UI definition</link> and
1572 * merges it with the current contents of @self.
1574 * Return value: The merge id for the merged UI. The merge id can be used
1575 * to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1576 * the return value is 0.
1581 gtk_ui_manager_add_ui_from_file (GtkUIManager *self,
1582 const gchar *filename,
1589 g_return_val_if_fail (GTK_IS_UI_MANAGER (self), 0);
1591 if (!g_file_get_contents (filename, &buffer, &length, error))
1594 res = add_ui_from_string (self, buffer, length, FALSE, error);
1601 * gtk_ui_manager_add_ui:
1602 * @self: a #GtkUIManager
1603 * @merge_id: the merge id for the merged UI, see gtk_ui_manager_new_merge_id()
1605 * @name: the name for the added UI element
1606 * @action: the name of the action to be proxied, or %NULL to add a separator
1607 * @type: the type of UI element to add.
1608 * @top: if %TRUE, the UI element is added before its siblings, otherwise it
1609 * is added after its siblings.
1611 * Adds a UI element to the current contents of @self.
1613 * If @type is %GTK_UI_MANAGER_AUTO, GTK+ inserts a menuitem, toolitem or
1614 * separator if such an element can be inserted at the place determined by
1615 * @path. Otherwise @type must indicate an element that can be inserted at
1616 * the place determined by @path.
1618 * If @path points to a menuitem or toolitem, the new element will be inserted
1619 * before or after this item, depending on @top.
1624 gtk_ui_manager_add_ui (GtkUIManager *self,
1628 const gchar *action,
1629 GtkUIManagerItemType type,
1636 GQuark action_quark = 0;
1638 g_return_if_fail (GTK_IS_UI_MANAGER (self));
1639 g_return_if_fail (merge_id > 0);
1640 g_return_if_fail (name != NULL);
1642 node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
1648 node_type = NODE_TYPE_UNDECIDED;
1651 switch (NODE_INFO (node)->type)
1653 case NODE_TYPE_SEPARATOR:
1654 case NODE_TYPE_MENUITEM:
1655 case NODE_TYPE_TOOLITEM:
1657 node = node->parent;
1659 case NODE_TYPE_MENUBAR:
1660 case NODE_TYPE_MENU:
1661 case NODE_TYPE_POPUP:
1662 case NODE_TYPE_MENU_PLACEHOLDER:
1665 case GTK_UI_MANAGER_AUTO:
1667 node_type = NODE_TYPE_MENUITEM;
1669 node_type = NODE_TYPE_SEPARATOR;
1671 case GTK_UI_MANAGER_MENU:
1672 node_type = NODE_TYPE_MENU;
1674 case GTK_UI_MANAGER_MENUITEM:
1675 node_type = NODE_TYPE_MENUITEM;
1677 case GTK_UI_MANAGER_SEPARATOR:
1678 node_type = NODE_TYPE_SEPARATOR;
1680 case GTK_UI_MANAGER_PLACEHOLDER:
1681 node_type = NODE_TYPE_MENU_PLACEHOLDER;
1687 case NODE_TYPE_TOOLBAR:
1688 case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1691 case GTK_UI_MANAGER_AUTO:
1693 node_type = NODE_TYPE_TOOLITEM;
1695 node_type = NODE_TYPE_SEPARATOR;
1697 case GTK_UI_MANAGER_TOOLITEM:
1698 node_type = NODE_TYPE_TOOLITEM;
1700 case GTK_UI_MANAGER_SEPARATOR:
1701 node_type = NODE_TYPE_SEPARATOR;
1703 case GTK_UI_MANAGER_PLACEHOLDER:
1704 node_type = NODE_TYPE_TOOLBAR_PLACEHOLDER;
1710 case NODE_TYPE_ROOT:
1713 case GTK_UI_MANAGER_MENUBAR:
1714 node_type = NODE_TYPE_MENUBAR;
1716 case GTK_UI_MANAGER_TOOLBAR:
1717 node_type = NODE_TYPE_TOOLBAR;
1719 case GTK_UI_MANAGER_POPUP:
1720 node_type = NODE_TYPE_POPUP;
1722 case GTK_UI_MANAGER_ACCELERATOR:
1723 node_type = NODE_TYPE_ACCELERATOR;
1733 if (node_type == NODE_TYPE_UNDECIDED)
1735 g_warning ("item type %d not suitable for adding at '%s'",
1740 child = get_child_node (self, node, sibling,
1741 name, strlen (name),
1742 node_type, TRUE, top);
1745 action_quark = g_quark_from_string (action);
1747 node_prepend_ui_reference (child, merge_id, action_quark);
1749 if (NODE_INFO (child)->action_name == 0)
1750 NODE_INFO (child)->action_name = action_quark;
1752 queue_update (self);
1754 g_object_notify (G_OBJECT (self), "ui");
1758 remove_ui (GNode *node,
1761 guint merge_id = GPOINTER_TO_UINT (user_data);
1763 node_remove_ui_reference (node, merge_id);
1765 return FALSE; /* continue */
1769 * gtk_ui_manager_remove_ui:
1770 * @self: a #GtkUIManager object
1771 * @merge_id: a merge id as returned by gtk_ui_manager_add_ui_from_string()
1773 * Unmerges the part of @self<!-- -->s content identified by @merge_id.
1778 gtk_ui_manager_remove_ui (GtkUIManager *self,
1781 g_node_traverse (self->private_data->root_node,
1782 G_POST_ORDER, G_TRAVERSE_ALL, -1,
1783 remove_ui, GUINT_TO_POINTER (merge_id));
1785 queue_update (self);
1787 g_object_notify (G_OBJECT (self), "ui");
1790 /* -------------------- Updates -------------------- */
1794 get_action_by_name (GtkUIManager *merge,
1795 const gchar *action_name)
1803 for (tmp = merge->private_data->action_groups; tmp != NULL; tmp = tmp->next)
1805 GtkActionGroup *action_group = tmp->data;
1808 action = gtk_action_group_get_action (action_group, action_name);
1818 find_menu_position (GNode *node,
1819 GtkWidget **menushell_p,
1822 GtkWidget *menushell;
1825 g_return_val_if_fail (node != NULL, FALSE);
1826 g_return_val_if_fail (NODE_INFO (node)->type == NODE_TYPE_MENU ||
1827 NODE_INFO (node)->type == NODE_TYPE_POPUP ||
1828 NODE_INFO (node)->type == NODE_TYPE_MENU_PLACEHOLDER ||
1829 NODE_INFO (node)->type == NODE_TYPE_MENUITEM ||
1830 NODE_INFO (node)->type == NODE_TYPE_SEPARATOR,
1833 /* first sibling -- look at parent */
1834 if (node->prev == NULL)
1839 parent = node->parent;
1840 switch (NODE_INFO (parent)->type)
1842 case NODE_TYPE_MENUBAR:
1843 case NODE_TYPE_POPUP:
1844 menushell = NODE_INFO (parent)->proxy;
1847 case NODE_TYPE_MENU:
1848 menushell = NODE_INFO (parent)->proxy;
1849 if (GTK_IS_MENU_ITEM (menushell))
1850 menushell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menushell));
1851 siblings = gtk_container_get_children (GTK_CONTAINER (menushell));
1852 if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1856 g_list_free (siblings);
1858 case NODE_TYPE_MENU_PLACEHOLDER:
1859 menushell = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1860 g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1861 pos = g_list_index (GTK_MENU_SHELL (menushell)->children,
1862 NODE_INFO (parent)->proxy) + 1;
1865 g_warning("%s: bad parent node type %d", G_STRLOC,
1866 NODE_INFO (parent)->type);
1872 GtkWidget *prev_child;
1875 sibling = node->prev;
1876 if (NODE_INFO (sibling)->type == NODE_TYPE_MENU_PLACEHOLDER)
1877 prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1879 prev_child = NODE_INFO (sibling)->proxy;
1881 g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
1882 menushell = gtk_widget_get_parent (prev_child);
1883 g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1885 pos = g_list_index (GTK_MENU_SHELL (menushell)->children, prev_child) + 1;
1889 *menushell_p = menushell;
1897 find_toolbar_position (GNode *node,
1898 GtkWidget **toolbar_p,
1904 g_return_val_if_fail (node != NULL, FALSE);
1905 g_return_val_if_fail (NODE_INFO (node)->type == NODE_TYPE_TOOLBAR ||
1906 NODE_INFO (node)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER ||
1907 NODE_INFO (node)->type == NODE_TYPE_TOOLITEM ||
1908 NODE_INFO (node)->type == NODE_TYPE_SEPARATOR,
1911 /* first sibling -- look at parent */
1912 if (node->prev == NULL)
1916 parent = node->parent;
1917 switch (NODE_INFO (parent)->type)
1919 case NODE_TYPE_TOOLBAR:
1920 toolbar = NODE_INFO (parent)->proxy;
1923 case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1924 toolbar = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1925 g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
1926 pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
1927 GTK_TOOL_ITEM (NODE_INFO (parent)->proxy)) + 1;
1930 g_warning ("%s: bad parent node type %d", G_STRLOC,
1931 NODE_INFO (parent)->type);
1937 GtkWidget *prev_child;
1940 sibling = node->prev;
1941 if (NODE_INFO (sibling)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
1942 prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1944 prev_child = NODE_INFO (sibling)->proxy;
1946 g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
1947 toolbar = gtk_widget_get_parent (prev_child);
1948 g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
1950 pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
1951 GTK_TOOL_ITEM (prev_child)) + 1;
1955 *toolbar_p = toolbar;
1963 * _gtk_menu_is_empty:
1964 * @menu: a #GtkMenu or %NULL
1966 * Determines whether @menu is empty. A menu is considered empty if it
1967 * the only visible children are tearoff menu items or "filler" menu
1968 * items which were inserted to mark the menu as empty.
1970 * This function is used by #GtkAction.
1972 * Return value: whether @menu is empty.
1975 _gtk_menu_is_empty (GtkWidget *menu)
1977 GList *children, *cur;
1979 g_return_val_if_fail (menu == NULL || GTK_IS_MENU (menu), TRUE);
1984 children = gtk_container_get_children (GTK_CONTAINER (menu));
1989 if (GTK_WIDGET_VISIBLE (cur->data))
1991 if (!GTK_IS_TEAROFF_MENU_ITEM (cur->data) &&
1992 !g_object_get_data (cur->data, "gtk-empty-menu-item"))
1997 g_list_free (children);
2003 SEPARATOR_MODE_SMART,
2004 SEPARATOR_MODE_VISIBLE,
2005 SEPARATOR_MODE_HIDDEN
2008 void _gtk_action_sync_menu_visible (GtkAction *action,
2013 update_smart_separators (GtkWidget *proxy)
2015 GtkWidget *parent = NULL;
2017 if (GTK_IS_MENU (proxy) || GTK_IS_TOOLBAR (proxy))
2019 else if (GTK_IS_MENU_ITEM (proxy) || GTK_IS_TOOL_ITEM (proxy))
2020 parent = gtk_widget_get_parent (proxy);
2026 GList *children, *cur, *last;
2030 children = gtk_container_get_children (GTK_CONTAINER (parent));
2041 if (g_object_get_data (cur->data, "gtk-empty-menu-item"))
2045 else if (GTK_IS_SEPARATOR_MENU_ITEM (cur->data) ||
2046 GTK_IS_SEPARATOR_TOOL_ITEM (cur->data))
2049 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cur->data),
2050 "gtk-separator-mode"));
2053 case SEPARATOR_MODE_VISIBLE:
2054 gtk_widget_show (GTK_WIDGET (cur->data));
2058 case SEPARATOR_MODE_HIDDEN:
2059 gtk_widget_hide (GTK_WIDGET (cur->data));
2061 case SEPARATOR_MODE_SMART:
2064 gtk_widget_show (GTK_WIDGET (cur->data));
2069 gtk_widget_hide (GTK_WIDGET (cur->data));
2073 else if (GTK_WIDGET_VISIBLE (cur->data))
2076 if (GTK_IS_TEAROFF_MENU_ITEM (cur->data) || cur->data == filler)
2089 gtk_widget_hide (GTK_WIDGET (last->data));
2091 if (GTK_IS_MENU (parent))
2095 item = gtk_menu_get_attach_widget (GTK_MENU (parent));
2096 if (GTK_IS_MENU_ITEM (item))
2097 _gtk_action_sync_menu_visible (NULL, item, empty);
2098 if (GTK_IS_WIDGET (filler))
2101 gtk_widget_show (filler);
2103 gtk_widget_hide (filler);
2107 g_list_free (children);
2112 update_node (GtkUIManager *self,
2119 const gchar *action_name;
2120 NodeUIReference *ref;
2122 #ifdef DEBUG_UI_MANAGER
2126 g_return_if_fail (node != NULL);
2127 g_return_if_fail (NODE_INFO (node) != NULL);
2129 info = NODE_INFO (node);
2134 in_popup = in_popup || (info->type == NODE_TYPE_POPUP);
2136 #ifdef DEBUG_UI_MANAGER
2137 g_print ("update_node name=%s dirty=%d popup %d (",
2138 info->name, info->dirty, in_popup);
2139 for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next)
2141 NodeUIReference *ref = tmp->data;
2142 g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id);
2149 if (info->uifiles == NULL) {
2150 /* We may need to remove this node.
2151 * This must be done in post order
2153 goto recurse_children;
2156 ref = info->uifiles->data;
2157 action_name = g_quark_to_string (ref->action_quark);
2158 action = get_action_by_name (self, action_name);
2160 info->dirty = FALSE;
2162 /* Check if the node doesn't have an action and must have an action */
2163 if (action == NULL &&
2164 info->type != NODE_TYPE_ROOT &&
2165 info->type != NODE_TYPE_MENUBAR &&
2166 info->type != NODE_TYPE_TOOLBAR &&
2167 info->type != NODE_TYPE_POPUP &&
2168 info->type != NODE_TYPE_SEPARATOR &&
2169 info->type != NODE_TYPE_MENU_PLACEHOLDER &&
2170 info->type != NODE_TYPE_TOOLBAR_PLACEHOLDER)
2172 g_warning ("%s: missing action", info->name);
2174 goto recurse_children;
2178 gtk_action_set_accel_group (action, self->private_data->accel_group);
2180 /* If the widget already has a proxy and the action hasn't changed, then
2181 * we only have to update the tearoff menu items.
2183 if (info->proxy != NULL && action == info->action)
2185 if (info->type == NODE_TYPE_MENU)
2190 if (GTK_IS_MENU (info->proxy))
2193 menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2194 siblings = gtk_container_get_children (GTK_CONTAINER (menu));
2195 if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
2197 if (self->private_data->add_tearoffs && !in_popup)
2198 gtk_widget_show (GTK_WIDGET (siblings->data));
2200 gtk_widget_hide (GTK_WIDGET (siblings->data));
2202 g_list_free (siblings);
2205 goto recurse_children;
2210 case NODE_TYPE_MENUBAR:
2211 if (info->proxy == NULL)
2213 info->proxy = gtk_menu_bar_new ();
2214 g_object_ref (info->proxy);
2215 gtk_object_sink (GTK_OBJECT (info->proxy));
2216 gtk_widget_set_name (info->proxy, info->name);
2217 gtk_widget_show (info->proxy);
2218 g_signal_emit (self, ui_manager_signals[ADD_WIDGET], 0, info->proxy);
2221 case NODE_TYPE_POPUP:
2222 if (info->proxy == NULL)
2224 info->proxy = gtk_menu_new ();
2225 g_object_ref (info->proxy);
2226 gtk_object_sink (GTK_OBJECT (info->proxy));
2228 gtk_widget_set_name (info->proxy, info->name);
2230 case NODE_TYPE_MENU:
2232 GtkWidget *prev_submenu = NULL;
2235 /* remove the proxy if it is of the wrong type ... */
2237 G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->menu_item_type)
2239 if (GTK_IS_MENU_ITEM (info->proxy))
2241 prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2244 g_object_ref (prev_submenu);
2245 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
2249 gtk_action_disconnect_proxy (info->action, info->proxy);
2250 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2252 g_object_unref (info->proxy);
2255 /* create proxy if needed ... */
2256 if (info->proxy == NULL)
2258 GtkWidget *menushell;
2261 if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLITEM ||
2262 find_menu_position (node, &menushell, &pos))
2267 menu = gtk_menu_new ();
2268 gtk_widget_set_name (menu, info->name);
2269 tearoff = gtk_tearoff_menu_item_new ();
2270 gtk_widget_set_no_show_all (tearoff, TRUE);
2271 gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff);
2272 filler = gtk_menu_item_new_with_label (_("Empty"));
2273 g_object_set_data (G_OBJECT (filler),
2274 "gtk-empty-menu-item",
2275 GINT_TO_POINTER (TRUE));
2276 gtk_widget_set_sensitive (filler, FALSE);
2277 gtk_widget_set_no_show_all (filler, TRUE);
2278 gtk_menu_shell_append (GTK_MENU_SHELL (menu), filler);
2280 if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLITEM)
2283 g_object_ref (info->proxy);
2284 gtk_object_sink (GTK_OBJECT (info->proxy));
2285 gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (NODE_INFO (node->parent)->proxy),
2290 info->proxy = gtk_action_create_menu_item (action);
2291 g_object_ref (info->proxy);
2292 gtk_object_sink (GTK_OBJECT (info->proxy));
2293 g_signal_connect (info->proxy, "notify::visible",
2294 G_CALLBACK (update_smart_separators), NULL);
2295 gtk_widget_set_name (info->proxy, info->name);
2297 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu);
2298 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos);
2303 gtk_action_connect_proxy (action, info->proxy);
2307 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),
2309 g_object_unref (prev_submenu);
2312 if (GTK_IS_MENU (info->proxy))
2315 menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2316 siblings = gtk_container_get_children (GTK_CONTAINER (menu));
2317 if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
2319 if (self->private_data->add_tearoffs && !in_popup)
2320 gtk_widget_show (GTK_WIDGET (siblings->data));
2322 gtk_widget_hide (GTK_WIDGET (siblings->data));
2324 g_list_free (siblings);
2327 case NODE_TYPE_UNDECIDED:
2328 g_warning ("found undecided node!");
2330 case NODE_TYPE_ROOT:
2332 case NODE_TYPE_TOOLBAR:
2333 if (info->proxy == NULL)
2335 info->proxy = gtk_toolbar_new ();
2336 g_object_ref (info->proxy);
2337 gtk_object_sink (GTK_OBJECT (info->proxy));
2338 gtk_widget_set_name (info->proxy, info->name);
2339 gtk_widget_show (info->proxy);
2340 g_signal_emit (self, ui_manager_signals[ADD_WIDGET], 0, info->proxy);
2343 case NODE_TYPE_MENU_PLACEHOLDER:
2344 /* create menu items for placeholders if necessary ... */
2345 if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) ||
2346 !GTK_IS_SEPARATOR_MENU_ITEM (info->extra))
2350 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2352 g_object_unref (info->proxy);
2357 gtk_container_remove (GTK_CONTAINER (info->extra->parent),
2359 g_object_unref (info->extra);
2363 if (info->proxy == NULL)
2365 GtkWidget *menushell;
2368 if (find_menu_position (node, &menushell, &pos))
2370 info->proxy = gtk_separator_menu_item_new ();
2371 g_object_ref (info->proxy);
2372 gtk_object_sink (GTK_OBJECT (info->proxy));
2373 g_object_set_data (G_OBJECT (info->proxy),
2374 "gtk-separator-mode",
2375 GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2376 gtk_widget_set_no_show_all (info->proxy, TRUE);
2377 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2378 NODE_INFO (node)->proxy, pos);
2380 info->extra = gtk_separator_menu_item_new ();
2381 g_object_ref (info->extra);
2382 gtk_object_sink (GTK_OBJECT (info->extra));
2383 g_object_set_data (G_OBJECT (info->extra),
2384 "gtk-separator-mode",
2385 GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2386 gtk_widget_set_no_show_all (info->extra, TRUE);
2387 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2388 NODE_INFO (node)->extra, pos+1);
2392 case NODE_TYPE_TOOLBAR_PLACEHOLDER:
2393 /* create toolbar items for placeholders if necessary ... */
2394 if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) ||
2395 !GTK_IS_SEPARATOR_TOOL_ITEM (info->extra))
2399 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2401 g_object_unref (info->proxy);
2406 gtk_container_remove (GTK_CONTAINER (info->extra->parent),
2408 g_object_unref (info->extra);
2412 if (info->proxy == NULL)
2417 if (find_toolbar_position (node, &toolbar, &pos))
2421 item = gtk_separator_tool_item_new ();
2422 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2423 info->proxy = GTK_WIDGET (item);
2424 g_object_ref (info->proxy);
2425 gtk_object_sink (GTK_OBJECT (info->proxy));
2426 g_object_set_data (G_OBJECT (info->proxy),
2427 "gtk-separator-mode",
2428 GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2429 gtk_widget_set_no_show_all (info->proxy, TRUE);
2431 item = gtk_separator_tool_item_new ();
2432 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1);
2433 info->extra = GTK_WIDGET (item);
2434 g_object_ref (info->extra);
2435 gtk_object_sink (GTK_OBJECT (info->extra));
2436 g_object_set_data (G_OBJECT (info->extra),
2437 "gtk-separator-mode",
2438 GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2439 gtk_widget_set_no_show_all (info->extra, TRUE);
2443 case NODE_TYPE_MENUITEM:
2444 /* remove the proxy if it is of the wrong type ... */
2446 G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->menu_item_type)
2448 g_signal_handlers_disconnect_by_func (info->proxy,
2449 G_CALLBACK (update_smart_separators),
2451 gtk_action_disconnect_proxy (info->action, info->proxy);
2452 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2454 g_object_unref (info->proxy);
2457 /* create proxy if needed ... */
2458 if (info->proxy == NULL)
2460 GtkWidget *menushell;
2463 if (find_menu_position (node, &menushell, &pos))
2465 info->proxy = gtk_action_create_menu_item (action);
2466 g_object_ref (info->proxy);
2467 gtk_object_sink (GTK_OBJECT (info->proxy));
2468 gtk_widget_set_name (info->proxy, info->name);
2470 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2476 g_signal_handlers_disconnect_by_func (info->proxy,
2477 G_CALLBACK (update_smart_separators),
2479 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
2480 gtk_action_connect_proxy (action, info->proxy);
2482 g_signal_connect (info->proxy, "notify::visible",
2483 G_CALLBACK (update_smart_separators), NULL);
2486 /* don't show accels in popups */
2487 GtkWidget *label = GTK_BIN (info->proxy)->child;
2488 g_object_set (label,
2489 "accel_closure", NULL,
2494 case NODE_TYPE_TOOLITEM:
2495 /* remove the proxy if it is of the wrong type ... */
2497 G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->toolbar_item_type)
2499 g_signal_handlers_disconnect_by_func (info->proxy,
2500 G_CALLBACK (update_smart_separators),
2502 gtk_action_disconnect_proxy (info->action, info->proxy);
2503 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2505 g_object_unref (info->proxy);
2508 /* create proxy if needed ... */
2509 if (info->proxy == NULL)
2514 if (find_toolbar_position (node, &toolbar, &pos))
2516 info->proxy = gtk_action_create_tool_item (action);
2517 g_object_ref (info->proxy);
2518 gtk_object_sink (GTK_OBJECT (info->proxy));
2519 gtk_widget_set_name (info->proxy, info->name);
2521 gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
2522 GTK_TOOL_ITEM (info->proxy), pos);
2527 g_signal_handlers_disconnect_by_func (info->proxy,
2528 G_CALLBACK (update_smart_separators),
2530 gtk_action_connect_proxy (action, info->proxy);
2533 /* FIXME: we must trigger the notify::tooltip handler, since
2534 * tooltips on toolitems can't be set before the toolitem
2535 * is added to the toolbar.
2537 g_object_notify (G_OBJECT (action), "tooltip");
2539 g_signal_connect (info->proxy, "notify::visible",
2540 G_CALLBACK (update_smart_separators), NULL);
2542 case NODE_TYPE_SEPARATOR:
2543 if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR ||
2544 NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
2548 gint separator_mode;
2550 if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy))
2552 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2554 g_object_unref (info->proxy);
2558 if (find_toolbar_position (node, &toolbar, &pos))
2560 GtkToolItem *item = gtk_separator_tool_item_new ();
2561 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2562 info->proxy = GTK_WIDGET (item);
2563 g_object_ref (info->proxy);
2564 gtk_object_sink (GTK_OBJECT (info->proxy));
2565 gtk_widget_set_no_show_all (info->proxy, TRUE);
2568 gtk_tool_item_set_expand (GTK_TOOL_ITEM (item), TRUE);
2569 gtk_separator_tool_item_set_draw
2570 (GTK_SEPARATOR_TOOL_ITEM (item), FALSE);
2571 separator_mode = SEPARATOR_MODE_VISIBLE;
2574 separator_mode = SEPARATOR_MODE_SMART;
2576 g_object_set_data (G_OBJECT (info->proxy),
2577 "gtk-separator-mode",
2578 GINT_TO_POINTER (separator_mode));
2579 gtk_widget_show (info->proxy);
2584 GtkWidget *menushell;
2587 if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy))
2589 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2591 g_object_unref (info->proxy);
2595 if (find_menu_position (node, &menushell, &pos))
2597 info->proxy = gtk_separator_menu_item_new ();
2598 g_object_ref (info->proxy);
2599 gtk_object_sink (GTK_OBJECT (info->proxy));
2600 gtk_widget_set_no_show_all (info->proxy, TRUE);
2601 g_object_set_data (G_OBJECT (info->proxy),
2602 "gtk-separator-mode",
2603 GINT_TO_POINTER (SEPARATOR_MODE_SMART));
2604 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2606 gtk_widget_show (info->proxy);
2610 case NODE_TYPE_ACCELERATOR:
2611 gtk_action_connect_accelerator (action);
2616 g_object_ref (action);
2618 g_object_unref (info->action);
2619 info->action = action;
2622 /* process children */
2623 child = node->children;
2629 child = current->next;
2630 update_node (self, current, in_popup);
2635 if (info->type == NODE_TYPE_MENU && GTK_IS_MENU_ITEM (info->proxy))
2636 update_smart_separators (gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy)));
2637 else if (info->type == NODE_TYPE_MENU ||
2638 info->type == NODE_TYPE_TOOLBAR ||
2639 info->type == NODE_TYPE_POPUP)
2640 update_smart_separators (info->proxy);
2643 /* handle cleanup of dead nodes */
2644 if (node->children == NULL && info->uifiles == NULL)
2647 gtk_widget_destroy (info->proxy);
2649 gtk_widget_destroy (info->extra);
2650 if (info->type == NODE_TYPE_ACCELERATOR)
2651 gtk_action_disconnect_accelerator (info->action);
2653 g_node_destroy (node);
2658 do_updates (GtkUIManager *self)
2660 /* this function needs to check through the tree for dirty nodes.
2661 * For such nodes, it needs to do the following:
2663 * 1) check if they are referenced by any loaded UI files anymore.
2664 * In which case, the proxy widget should be destroyed, unless
2665 * there are any subnodes.
2667 * 2) lookup the action for this node again. If it is different to
2668 * the current one (or if no previous action has been looked up),
2669 * the proxy is reconnected to the new action (or a new proxy widget
2670 * is created and added to the parent container).
2672 update_node (self, self->private_data->root_node, FALSE);
2674 self->private_data->update_tag = 0;
2680 do_updates_idle (GtkUIManager *self)
2682 GDK_THREADS_ENTER ();
2684 GDK_THREADS_LEAVE ();
2690 queue_update (GtkUIManager *self)
2692 if (self->private_data->update_tag != 0)
2695 self->private_data->update_tag = g_idle_add ((GSourceFunc)do_updates_idle,
2701 * gtk_ui_manager_ensure_update:
2702 * @self: a #GtkUIManager
2704 * Makes sure that all pending updates to the UI have been completed.
2706 * This may occasionally be necessary, since #GtkUIManager updates the
2707 * UI in an idle function. A typical example where this function is
2708 * useful is to enforce that the menubar and toolbar have been added to
2709 * the main window before showing it:
2712 * gtk_container_add (GTK_CONTAINER (window), vbox);
2713 * g_signal_connect (merge, "add_widget",
2714 * G_CALLBACK (add_widget), vbox);
2715 * gtk_ui_manager_add_ui_from_file (merge, "my-menus");
2716 * gtk_ui_manager_add_ui_from_file (merge, "my-toolbars");
2717 * gtk_ui_manager_ensure_update (merge);
2718 * gtk_widget_show (window);
2720 * </informalexample>
2725 gtk_ui_manager_ensure_update (GtkUIManager *self)
2727 if (self->private_data->update_tag != 0)
2729 g_source_remove (self->private_data->update_tag);
2735 dirty_traverse_func (GNode *node,
2738 NODE_INFO (node)->dirty = TRUE;
2743 dirty_all_nodes (GtkUIManager *self)
2745 g_node_traverse (self->private_data->root_node,
2746 G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2747 dirty_traverse_func, NULL);
2748 queue_update (self);
2752 mark_node_dirty (GNode *node)
2756 /* FIXME could optimize this */
2757 for (p = node; p; p = p->parent)
2758 NODE_INFO (p)->dirty = TRUE;
2761 static const gchar *const open_tag_format[] = {
2776 static const gchar *const close_tag_format[] = {
2777 "%*s</UNDECIDED>\n",
2782 "%*s</placeholder>\n",
2783 "%*s</placeholder>\n",
2792 print_node (GtkUIManager *self,
2802 g_string_append_printf (buffer, open_tag_format[mnode->type],
2805 if (mnode->type != NODE_TYPE_ROOT)
2808 g_string_append_printf (buffer, " name=\"%s\"", mnode->name);
2810 if (mnode->action_name)
2811 g_string_append_printf (buffer, " action=\"%s\"",
2812 g_quark_to_string (mnode->action_name));
2815 g_string_append (buffer,
2816 close_tag_format[mnode->type] ? ">\n" : "/>\n");
2818 for (child = node->children; child != NULL; child = child->next)
2819 print_node (self, child, indent_level + 2, buffer);
2821 if (close_tag_format[mnode->type])
2822 g_string_append_printf (buffer, close_tag_format[mnode->type],
2827 * gtk_ui_manager_get_ui:
2828 * @self: a #GtkUIManager
2830 * Creates a <link linkend="XML-UI">UI definition</link> of the merged UI.
2832 * Return value: A newly allocated string containing an XML representation of
2838 gtk_ui_manager_get_ui (GtkUIManager *self)
2842 buffer = g_string_new (NULL);
2844 gtk_ui_manager_ensure_update (self);
2846 print_node (self, self->private_data->root_node, 0, buffer);
2848 return g_string_free (buffer, FALSE);
2853 #undef gtk_ui_manager_add_ui_from_file
2856 gtk_ui_manager_add_ui_from_file (GtkUIManager *self,
2857 const gchar *filename,
2860 gchar *utf8_filename = g_locale_to_utf8 (filename, -1, NULL, NULL, error);
2863 if (utf8_filename == NULL)
2866 retval = gtk_ui_manager_add_ui_from_file_utf8 (self, utf8_filename, error);
2868 g_free (utf8_filename);
2875 #define __GTK_UI_MANAGER_C__
2876 #include "gtkaliasdef.c"