]> Pileus Git - ~andy/gtk/blob - gtk/gtkuimanager.c
a3d78ca20c6ede6960c378cfdd5496e97507f033
[~andy/gtk] / gtk / gtkuimanager.c
1 /*
2  * GTK - The GIMP Toolkit
3  * Copyright (C) 1998, 1999 Red Hat, Inc.
4  * All rights reserved.
5  *
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.
10  *
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.
15  *
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.
20  */
21
22 /*
23  * Author: James Henstridge <james@daa.com.au>
24  *
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/. 
29  */
30
31 #include <config.h>
32
33 #include <string.h>
34 #include "gtkintl.h"
35 #include "gtkmarshalers.h"
36 #include "gtkmenu.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"
44
45 #undef DEBUG_UI_MANAGER
46
47 typedef enum 
48 {
49   NODE_TYPE_UNDECIDED,
50   NODE_TYPE_ROOT,
51   NODE_TYPE_MENUBAR,
52   NODE_TYPE_MENU,
53   NODE_TYPE_TOOLBAR,
54   NODE_TYPE_MENU_PLACEHOLDER,
55   NODE_TYPE_TOOLBAR_PLACEHOLDER,
56   NODE_TYPE_POPUP,
57   NODE_TYPE_MENUITEM,
58   NODE_TYPE_TOOLITEM,
59   NODE_TYPE_SEPARATOR,
60   NODE_TYPE_ACCELERATOR
61 } NodeType;
62
63 typedef struct _Node Node;
64
65 struct _Node {
66   NodeType type;
67
68   gchar *name;
69
70   GQuark action_name;
71   GtkAction *action;
72   GtkWidget *proxy;
73   GtkWidget *extra; /* second separator for placeholders */
74
75   GList *uifiles;
76
77   guint dirty : 1;
78 };
79
80 #define GTK_UI_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_UI_MANAGER, GtkUIManagerPrivate))
81
82 struct _GtkUIManagerPrivate 
83 {
84   GtkAccelGroup *accel_group;
85
86   GNode *root_node;
87   GList *action_groups;
88
89   guint last_merge_id;
90
91   guint update_tag;  
92
93   gboolean add_tearoffs;
94 };
95
96 #define NODE_INFO(node) ((Node *)node->data)
97
98 typedef struct _NodeUIReference NodeUIReference;
99
100 struct _NodeUIReference 
101 {
102   guint merge_id;
103   GQuark action_quark;
104 };
105
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,
110                                              guint              prop_id,
111                                              const GValue      *value,
112                                              GParamSpec        *pspec);
113 static void     gtk_ui_manager_get_property (GObject           *object,
114                                              guint              prop_id,
115                                              GValue            *value,
116                                              GParamSpec        *pspec);
117 static void     queue_update                (GtkUIManager      *self);
118 static void     dirty_all_nodes             (GtkUIManager      *self);
119 static GNode *  get_child_node              (GtkUIManager      *self,
120                                              GNode             *parent,
121                                              const gchar       *childname,
122                                              gint               childname_length,
123                                              NodeType           node_type,
124                                              gboolean           create,
125                                              gboolean           top);
126 static GNode *  get_node                    (GtkUIManager      *self,
127                                              const gchar       *path,
128                                              NodeType           node_type,
129                                              gboolean           create);
130 static gboolean free_node                   (GNode             *node);
131 static void     node_prepend_ui_reference   (Node              *node,
132                                              guint              merge_id,
133                                              GQuark             action_quark);
134 static void     node_remove_ui_reference    (Node              *node,
135                                              guint              merge_id);
136
137
138 enum 
139 {
140   ADD_WIDGET,
141   ACTIONS_CHANGED,
142   CONNECT_PROXY,
143   DISCONNECT_PROXY,
144   PRE_ACTIVATE,
145   POST_ACTIVATE,
146   LAST_SIGNAL
147 };
148
149 enum
150 {
151   PROP_0,
152   PROP_ADD_TEAROFFS,
153   PROP_UI
154 };
155
156 static guint ui_manager_signals[LAST_SIGNAL] = { 0 };
157
158 static GMemChunk *merge_node_chunk = NULL;
159
160 GType
161 gtk_ui_manager_get_type (void)
162 {
163   static GtkType type = 0;
164
165   if (!type)
166     {
167       static const GTypeInfo type_info =
168       {
169         sizeof (GtkUIManagerClass),
170         (GBaseInitFunc) NULL,
171         (GBaseFinalizeFunc) NULL,
172         (GClassInitFunc) gtk_ui_manager_class_init,
173         (GClassFinalizeFunc) NULL,
174         NULL,
175         
176         sizeof (GtkUIManager),
177         0, /* n_preallocs */
178         (GInstanceInitFunc) gtk_ui_manager_init,
179       };
180
181       type = g_type_register_static (G_TYPE_OBJECT,
182                                      "GtkUIManager",
183                                      &type_info, 0);
184     }
185   return type;
186 }
187
188 static void
189 gtk_ui_manager_class_init (GtkUIManagerClass *klass)
190 {
191   GObjectClass *gobject_class;
192
193   gobject_class = G_OBJECT_CLASS (klass);
194
195   if (!merge_node_chunk)
196     merge_node_chunk = g_mem_chunk_create (Node, 64,
197                                            G_ALLOC_AND_FREE);
198
199   gobject_class->finalize = gtk_ui_manager_finalize;
200   gobject_class->set_property = gtk_ui_manager_set_property;
201   gobject_class->get_property = gtk_ui_manager_get_property;
202   
203   /**
204    * GtkUIManager:add-tearoffs:
205    *
206    * The "add-tearoffs" property controls whether generated menus 
207    * have tearoff menu items. 
208    *
209    * Note that this only affects regular menus. Generated popup 
210    * menus never have tearoff menu items.   
211    *
212    * Since: 2.4
213    */
214   g_object_class_install_property (gobject_class,
215                                    PROP_ADD_TEAROFFS,
216                                    g_param_spec_boolean ("add_tearoffs",
217                                                          P_("Add tearoffs to menus"),
218                                                          P_("Whether tearoff menu items should be added to menus"),
219                                                          FALSE,
220                                                          G_PARAM_READWRITE));
221
222   g_object_class_install_property (gobject_class,
223                                    PROP_UI,
224                                    g_param_spec_string ("ui",
225                                                         P_("Merged UI definition"),
226                                                         P_("An XML string describing the merged UI"),
227                                                         NULL,
228                                                         G_PARAM_READABLE));
229
230
231   /**
232    * GtkUIManager::add-widget:
233    * @merge: a #GtkUIManager
234    * @widget: the added widget
235    *
236    * The add_widget signal is emitted for each generated menubar and toolbar.
237    * It is not emitted for generated popup menus, which can be obtained by 
238    * gtk_ui_manager_get_widget().
239    *
240    * Since: 2.4
241    */
242   ui_manager_signals[ADD_WIDGET] =
243     g_signal_new ("add_widget",
244                   G_OBJECT_CLASS_TYPE (klass),
245                   G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
246                   G_STRUCT_OFFSET (GtkUIManagerClass, add_widget), NULL, NULL,
247                   g_cclosure_marshal_VOID__OBJECT,
248                   G_TYPE_NONE, 1, 
249                   GTK_TYPE_WIDGET);
250
251   /**
252    * GtkUIManager::actions-changed:
253    * @merge: a #GtkUIManager
254    *
255    * The "actions-changed" signal is emitted whenever the set of actions
256    * changes.
257    *
258    * Since: 2.4
259    */
260   ui_manager_signals[ACTIONS_CHANGED] =
261     g_signal_new ("actions_changed",
262                   G_OBJECT_CLASS_TYPE (klass),
263                   G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
264                   G_STRUCT_OFFSET (GtkUIManagerClass, actions_changed),  
265                   NULL, NULL,
266                   g_cclosure_marshal_VOID__VOID,
267                   G_TYPE_NONE, 0);
268   
269   /**
270    * GtkUIManager::connect_proxy:
271    * @uimanager: the ui manager
272    * @action: the action
273    * @proxy: the proxy
274    *
275    * The connect_proxy signal is emitted after connecting a proxy to 
276    * an action in the group. 
277    *
278    * This is intended for simple customizations for which a custom action
279    * class would be too clumsy, e.g. showing tooltips for menuitems in the
280    * statusbar.
281    *
282    * Since: 2.4
283    */
284   ui_manager_signals[CONNECT_PROXY] =
285     g_signal_new ("connect_proxy",
286                   G_OBJECT_CLASS_TYPE (klass),
287                   0, 0, NULL, NULL,
288                   _gtk_marshal_VOID__OBJECT_OBJECT,
289                   G_TYPE_NONE, 2, 
290                   GTK_TYPE_ACTION, GTK_TYPE_WIDGET);
291
292   /**
293    * GtkUIManager::disconnect_proxy:
294    * @uimanager: the ui manager
295    * @action: the action
296    * @proxy: the proxy
297    *
298    * The disconnect_proxy signal is emitted after disconnecting a proxy 
299    * from an action in the group. 
300    *
301    * Since: 2.4
302    */
303   ui_manager_signals[DISCONNECT_PROXY] =
304     g_signal_new ("disconnect_proxy",
305                   G_OBJECT_CLASS_TYPE (klass),
306                   0, 0, NULL, NULL,
307                   _gtk_marshal_VOID__OBJECT_OBJECT,
308                   G_TYPE_NONE, 2, 
309                   GTK_TYPE_ACTION, GTK_TYPE_WIDGET);
310
311   /**
312    * GtkUIManager::pre_activate:
313    * @uimanager: the ui manager
314    * @action: the action
315    *
316    * The pre_activate signal is emitted just before the @action
317    * is activated.
318    *
319    * This is intended for applications to get notification
320    * just before any action is activated.
321    *
322    * Since: 2.4
323    */
324   ui_manager_signals[PRE_ACTIVATE] =
325     g_signal_new ("pre_activate",
326                   G_OBJECT_CLASS_TYPE (klass),
327                   0, 0, NULL, NULL,
328                   _gtk_marshal_VOID__OBJECT,
329                   G_TYPE_NONE, 1, 
330                   GTK_TYPE_ACTION);
331
332   /**
333    * GtkUIManager::post_activate:
334    * @uimanager: the ui manager
335    * @action: the action
336    *
337    * The post_activate signal is emitted just after the @action
338    * is activated.
339    *
340    * This is intended for applications to get notification
341    * just after any action is activated.
342    *
343    * Since: 2.4
344    */
345   ui_manager_signals[POST_ACTIVATE] =
346     g_signal_new ("post_activate",
347                   G_OBJECT_CLASS_TYPE (klass),
348                   0, 0, NULL, NULL,
349                   _gtk_marshal_VOID__OBJECT,
350                   G_TYPE_NONE, 1, 
351                   GTK_TYPE_ACTION);
352
353   g_type_class_add_private (gobject_class, sizeof (GtkUIManagerPrivate));
354 }
355
356
357 static void
358 gtk_ui_manager_init (GtkUIManager *self)
359 {
360   guint merge_id;
361   GNode *node;
362
363   self->private_data = GTK_UI_MANAGER_GET_PRIVATE (self);
364
365   self->private_data->accel_group = gtk_accel_group_new ();
366
367   self->private_data->root_node = NULL;
368   self->private_data->action_groups = NULL;
369
370   self->private_data->last_merge_id = 0;
371   self->private_data->add_tearoffs = FALSE;
372
373   merge_id = gtk_ui_manager_new_merge_id (self);
374   node = get_child_node (self, NULL, "ui", 2,
375                          NODE_TYPE_ROOT, TRUE, FALSE);
376   node_prepend_ui_reference (NODE_INFO (node), merge_id, 0);
377 }
378
379 static void
380 gtk_ui_manager_finalize (GObject *object)
381 {
382   GtkUIManager *self = GTK_UI_MANAGER (object);
383   
384   if (self->private_data->update_tag != 0)
385     {
386       g_source_remove (self->private_data->update_tag);
387       self->private_data->update_tag = 0;
388     }
389   
390   g_node_traverse (self->private_data->root_node, 
391                    G_POST_ORDER, G_TRAVERSE_ALL, -1,
392                    (GNodeTraverseFunc)free_node, 0);
393   g_node_destroy (self->private_data->root_node);
394   self->private_data->root_node = NULL;
395   
396   g_list_foreach (self->private_data->action_groups,
397                   (GFunc) g_object_unref, NULL);
398   g_list_free (self->private_data->action_groups);
399   self->private_data->action_groups = NULL;
400
401   g_object_unref (self->private_data->accel_group);
402   self->private_data->accel_group = NULL;
403 }
404
405 static void
406 gtk_ui_manager_set_property (GObject         *object,
407                              guint            prop_id,
408                              const GValue    *value,
409                              GParamSpec      *pspec)
410 {
411   GtkUIManager *self = GTK_UI_MANAGER (object);
412  
413   switch (prop_id)
414     {
415     case PROP_ADD_TEAROFFS:
416       gtk_ui_manager_set_add_tearoffs (self, g_value_get_boolean (value));
417       break;
418     default:
419       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
420       break;
421     }
422 }
423
424 static void
425 gtk_ui_manager_get_property (GObject         *object,
426                              guint            prop_id,
427                              GValue          *value,
428                              GParamSpec      *pspec)
429 {
430   GtkUIManager *self = GTK_UI_MANAGER (object);
431
432   switch (prop_id)
433     {
434     case PROP_ADD_TEAROFFS:
435       g_value_set_boolean (value, self->private_data->add_tearoffs);
436       break;
437     case PROP_UI:
438       g_value_set_string (value, gtk_ui_manager_get_ui (self));
439       break;
440     default:
441       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
442       break;
443     }
444 }
445
446
447 /**
448  * gtk_ui_manager_new:
449  * 
450  * Creates a new ui manager object.
451  * 
452  * Return value: a new ui manager object.
453  *
454  * Since: 2.4
455  **/
456 GtkUIManager*
457 gtk_ui_manager_new (void)
458 {
459   return g_object_new (GTK_TYPE_UI_MANAGER, NULL);
460 }
461
462
463 /**
464  * gtk_ui_manager_get_add_tearoffs:
465  * @self: a #GtkUIManager
466  * 
467  * Returns whether menus generated by this #GtkUIManager
468  * will have tearoff menu items. 
469  * 
470  * Return value: whether tearoff menu items are added
471  *
472  * Since: 2.4
473  **/
474 gboolean 
475 gtk_ui_manager_get_add_tearoffs (GtkUIManager *self)
476 {
477   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), FALSE);
478   
479   return self->private_data->add_tearoffs;
480 }
481
482
483 /**
484  * gtk_ui_manager_set_add_tearoffs:
485  * @self: a #GtkUIManager
486  * @add_tearoffs: whether tearoff menu items are added
487  * 
488  * Sets the "add_tearoffs" property, which controls whether menus 
489  * generated by this #GtkUIManager will have tearoff menu items. 
490  *
491  * Note that this only affects regular menus. Generated popup 
492  * menus never have tearoff menu items.
493  *
494  * Since: 2.4
495  **/
496 void 
497 gtk_ui_manager_set_add_tearoffs (GtkUIManager *self,
498                                  gboolean      add_tearoffs)
499 {
500   g_return_if_fail (GTK_IS_UI_MANAGER (self));
501
502   add_tearoffs = add_tearoffs != FALSE;
503
504   if (add_tearoffs != self->private_data->add_tearoffs)
505     {
506       self->private_data->add_tearoffs = add_tearoffs;
507       
508       dirty_all_nodes (self);
509
510       g_object_notify (G_OBJECT (self), "add_tearoffs");
511     }
512 }
513
514 static void
515 cb_proxy_connect_proxy (GtkActionGroup *group, 
516                         GtkAction      *action,
517                         GtkWidget      *proxy, 
518                         GtkUIManager *self)
519 {
520   g_signal_emit (self, ui_manager_signals[CONNECT_PROXY], 0, action, proxy);
521 }
522
523 static void
524 cb_proxy_disconnect_proxy (GtkActionGroup *group, 
525                            GtkAction      *action,
526                            GtkWidget      *proxy, 
527                            GtkUIManager *self)
528 {
529   g_signal_emit (self, ui_manager_signals[DISCONNECT_PROXY], 0, action, proxy);
530 }
531
532 static void
533 cb_proxy_pre_activate (GtkActionGroup *group, 
534                        GtkAction      *action,
535                        GtkUIManager   *self)
536 {
537   g_signal_emit (self, ui_manager_signals[PRE_ACTIVATE], 0, action);
538 }
539
540 static void
541 cb_proxy_post_activate (GtkActionGroup *group, 
542                         GtkAction      *action,
543                         GtkUIManager   *self)
544 {
545   g_signal_emit (self, ui_manager_signals[POST_ACTIVATE], 0, action);
546 }
547
548 /**
549  * gtk_ui_manager_insert_action_group:
550  * @self: a #GtkUIManager object
551  * @action_group: the action group to be inserted
552  * @pos: the position at which the group will be inserted.
553  * 
554  * Inserts an action group into the list of action groups associated 
555  * with @self. Actions in earlier groups hide actions with the same 
556  * name in later groups. 
557  *
558  * Since: 2.4
559  **/
560 void
561 gtk_ui_manager_insert_action_group (GtkUIManager   *self,
562                                     GtkActionGroup *action_group, 
563                                     gint            pos)
564 {
565   g_return_if_fail (GTK_IS_UI_MANAGER (self));
566   g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
567   g_return_if_fail (g_list_find (self->private_data->action_groups, 
568                                  action_group) == NULL);
569
570   g_object_ref (action_group);
571   self->private_data->action_groups = 
572     g_list_insert (self->private_data->action_groups, action_group, pos);
573   g_object_connect (action_group,
574                     "object_signal::connect_proxy", G_CALLBACK (cb_proxy_connect_proxy), self,
575                     "object_signal::disconnect_proxy", G_CALLBACK (cb_proxy_disconnect_proxy), self,
576                     "object_signal::pre_activate", G_CALLBACK (cb_proxy_pre_activate), self,
577                     "object_signal::post_activate", G_CALLBACK (cb_proxy_post_activate), self, 0);
578
579   /* dirty all nodes, as action bindings may change */
580   dirty_all_nodes (self);
581
582   g_signal_emit (self, ui_manager_signals[ACTIONS_CHANGED], 0);
583 }
584
585 /**
586  * gtk_ui_manager_remove_action_group:
587  * @self: a #GtkUIManager object
588  * @action_group: the action group to be removed
589  * 
590  * Removes an action group from the list of action groups associated 
591  * with @self.
592  *
593  * Since: 2.4
594  **/
595 void
596 gtk_ui_manager_remove_action_group (GtkUIManager   *self,
597                                     GtkActionGroup *action_group)
598 {
599   g_return_if_fail (GTK_IS_UI_MANAGER (self));
600   g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
601   g_return_if_fail (g_list_find (self->private_data->action_groups, 
602                                  action_group) != NULL);
603
604   self->private_data->action_groups =
605     g_list_remove (self->private_data->action_groups, action_group);
606
607   g_object_disconnect (action_group,
608                        "any_signal::connect_proxy", G_CALLBACK (cb_proxy_connect_proxy), self,
609                        "any_signal::disconnect_proxy", G_CALLBACK (cb_proxy_disconnect_proxy), self,
610                        "any_signal::pre_activate", G_CALLBACK (cb_proxy_pre_activate), self,
611                        "any_signal::post_activate", G_CALLBACK (cb_proxy_post_activate), self, 
612                        0);
613   g_object_unref (action_group);
614
615   /* dirty all nodes, as action bindings may change */
616   dirty_all_nodes (self);
617
618   g_signal_emit (self, ui_manager_signals[ACTIONS_CHANGED], 0);
619 }
620
621 /**
622  * gtk_ui_manager_get_action_groups:
623  * @self: a #GtkUIManager object
624  * 
625  * Returns the list of action groups associated with @self.
626  *
627  * Return value: a #GList of action groups. The list is owned by GTK+ 
628  *   and should not be modified.
629  *
630  * Since: 2.4
631  **/
632 GList *
633 gtk_ui_manager_get_action_groups (GtkUIManager *self)
634 {
635   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
636
637   return self->private_data->action_groups;
638 }
639
640 /**
641  * gtk_ui_manager_get_accel_group:
642  * @self: a #GtkUIManager object
643  * 
644  * Returns the #GtkAccelGroup associated with @self.
645  *
646  * Return value: the #GtkAccelGroup.
647  *
648  * Since: 2.4
649  **/
650 GtkAccelGroup *
651 gtk_ui_manager_get_accel_group (GtkUIManager *self)
652 {
653   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
654
655   return self->private_data->accel_group;
656 }
657
658 /**
659  * gtk_ui_manager_get_widget:
660  * @self: a #GtkUIManager
661  * @path: a path
662  * 
663  * Looks up a widget by following a path. 
664  * The path consists of the names specified in the XML description of the UI. 
665  * separated by '/'. Elements which don't have a name or action attribute in 
666  * the XML (e.g. &lt;popup&gt;) can be addressed by their XML element name 
667  * (e.g. "popup"). The root element ("/ui") can be omitted in the path.
668  * 
669  * Return value: the widget found by following the path, or %NULL if no widget
670  *   was found.
671  *
672  * Since: 2.4
673  **/
674 GtkWidget *
675 gtk_ui_manager_get_widget (GtkUIManager *self, 
676                            const gchar  *path)
677 {
678   GNode *node;
679
680   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
681   g_return_val_if_fail (path != NULL, NULL);
682
683   /* ensure that there are no pending updates before we get the
684    * widget */
685   gtk_ui_manager_ensure_update (self);
686
687   node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
688
689   if (node == NULL)
690     return NULL;
691
692   return NODE_INFO (node)->proxy;
693 }
694
695 static void
696 collect_toplevels (GNode   *node, 
697                    gpointer user_data)
698 {
699   struct {
700     GtkUIManagerItemType types;
701     GSList *list;
702   } *data = user_data;
703
704   switch (NODE_INFO (node)->type) {
705   case NODE_TYPE_MENUBAR:
706     if (data->types & GTK_UI_MANAGER_MENUBAR)
707       data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
708     break;
709   case NODE_TYPE_TOOLBAR:
710     if (data->types & GTK_UI_MANAGER_TOOLBAR)
711       data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
712     break;
713   case NODE_TYPE_POPUP:
714     if (data->types & GTK_UI_MANAGER_POPUP)
715       data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
716     break;
717   default: ;
718   }
719 }
720
721 /**
722  * gtk_ui_manager_get_toplevels:
723  * @self: a #GtkUIManager
724  * @types: specifies the types of toplevel widgets to include. Allowed
725  *   types are #GTK_UI_MANAGER_MENUBAR, #GTK_UI_MANAGER_TOOLBAR and
726  *   #GTK_UI_MANAGER_POPUP.
727  * 
728  * Obtains a list of all toplevel widgets of the requested types.
729  * 
730  * Return value: a newly-allocated of all toplevel widgets of the requested 
731  * types. 
732  *
733  * Since: 2.4
734  **/
735 GSList *
736 gtk_ui_manager_get_toplevels (GtkUIManager         *self,
737                               GtkUIManagerItemType  types)
738 {
739   struct {
740     GtkUIManagerItemType types;
741     GSList *list;
742   } data;
743
744   g_return_val_if_fail ((~(GTK_UI_MANAGER_MENUBAR | 
745                            GTK_UI_MANAGER_TOOLBAR |
746                            GTK_UI_MANAGER_POPUP) & types) == 0, NULL);
747   
748       
749   data.types = types;
750   data.list = NULL;
751
752   g_node_children_foreach (self->private_data->root_node, 
753                            G_TRAVERSE_ALL, 
754                            collect_toplevels, &data);
755
756   return data.list;
757 }
758
759
760 /**
761  * gtk_ui_manager_get_action:
762  * @self: a #GtkUIManager
763  * @path: a path
764  * 
765  * Looks up an action by following a path. See gtk_ui_manager_get_widget()
766  * for more information about paths.
767  * 
768  * Return value: the action whose proxy widget is found by following the path, 
769  *     or %NULL if no widget was found.
770  *
771  * Since: 2.4
772  **/
773 GtkAction *           
774 gtk_ui_manager_get_action (GtkUIManager *self,
775                            const gchar  *path)
776 {
777   GNode *node;
778
779   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
780   g_return_val_if_fail (path != NULL, NULL);
781   
782   /* ensure that there are no pending updates before we get
783    * the action */
784   gtk_ui_manager_ensure_update (self);
785   
786   node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
787
788   if (node == NULL)
789     return NULL;
790
791   return NODE_INFO (node)->action;
792 }
793
794 static GNode *
795 get_child_node (GtkUIManager *self, 
796                 GNode        *parent,
797                 const gchar  *childname, 
798                 gint          childname_length,
799                 NodeType      node_type,
800                 gboolean      create, 
801                 gboolean      top)
802 {
803   GNode *child = NULL;
804
805   g_return_val_if_fail (parent == NULL ||
806                         (NODE_INFO (parent)->type != NODE_TYPE_MENUITEM &&
807                          NODE_INFO (parent)->type != NODE_TYPE_TOOLITEM), 
808                         NULL);
809
810   if (parent)
811     {
812       if (childname)
813         {
814           for (child = parent->children; child != NULL; child = child->next)
815             {
816               if (strlen (NODE_INFO (child)->name) == childname_length &&
817                   !strncmp (NODE_INFO (child)->name, childname, childname_length))
818                 {
819                   /* if undecided about node type, set it */
820                   if (NODE_INFO (child)->type == NODE_TYPE_UNDECIDED)
821                     NODE_INFO (child)->type = node_type;
822                   
823                   /* warn about type mismatch */
824                   if (NODE_INFO (child)->type != NODE_TYPE_UNDECIDED &&
825                       node_type != NODE_TYPE_UNDECIDED &&
826                       NODE_INFO (child)->type != node_type)
827                     g_warning ("node type doesn't match %d (%s is type %d)",
828                                node_type, 
829                                NODE_INFO (child)->name,
830                                NODE_INFO (child)->type);
831                   
832                   return child;
833                 }
834             }
835         }
836       if (!child && create)
837         {
838           Node *mnode;
839           
840           mnode = g_chunk_new0 (Node, merge_node_chunk);
841           mnode->type = node_type;
842           mnode->name = g_strndup (childname, childname_length);
843           mnode->dirty = TRUE;
844
845           if (top)
846             child = g_node_prepend_data (parent, mnode);
847           else
848             child = g_node_append_data (parent, mnode);
849         }
850     }
851   else
852     {
853       /* handle root node */
854       if (self->private_data->root_node)
855         {
856           child = self->private_data->root_node;
857           if (strncmp (NODE_INFO (child)->name, childname, childname_length) != 0)
858             g_warning ("root node name '%s' doesn't match '%s'",
859                        childname, NODE_INFO (child)->name);
860           if (NODE_INFO (child)->type != NODE_TYPE_ROOT)
861             g_warning ("base element must be of type ROOT");
862         }
863       else if (create)
864         {
865           Node *mnode;
866
867           mnode = g_chunk_new0 (Node, merge_node_chunk);
868           mnode->type = node_type;
869           mnode->name = g_strndup (childname, childname_length);
870           mnode->dirty = TRUE;
871           
872           child = self->private_data->root_node = g_node_new (mnode);
873         }
874     }
875
876   return child;
877 }
878
879 static GNode *
880 get_node (GtkUIManager *self, 
881           const gchar  *path,
882           NodeType      node_type, 
883           gboolean      create)
884 {
885   const gchar *pos, *end;
886   GNode *parent, *node;
887   
888   end = path + strlen (path);
889   pos = path;
890   parent = node = NULL;
891   while (pos < end)
892     {
893       const gchar *slash;
894       gsize length;
895
896       slash = strchr (pos, '/');
897       if (slash)
898         length = slash - pos;
899       else
900         length = strlen (pos);
901
902       node = get_child_node (self, parent, pos, length, NODE_TYPE_UNDECIDED,
903                              create, FALSE);
904       if (!node)
905         return NULL;
906       
907       pos += length + 1; /* move past the node name and the slash too */
908       parent = node;
909     }
910
911   if (node != NULL && NODE_INFO (node)->type == NODE_TYPE_UNDECIDED)
912     NODE_INFO (node)->type = node_type;
913
914   return node;
915 }
916
917 static gboolean
918 free_node (GNode *node)
919 {
920   Node *info = NODE_INFO (node);
921   
922   g_list_foreach (info->uifiles, (GFunc) g_free, NULL);
923   g_list_free (info->uifiles);
924
925   if (info->action)
926     g_object_unref (info->action);
927   g_free (info->name);
928   g_chunk_free (info, merge_node_chunk);
929
930   return FALSE;
931 }
932
933 /**
934  * gtk_ui_manager_new_merge_id:
935  * @self: a #GtkUIManager
936  * 
937  * Returns an unused merge id, suitable for use with 
938  * gtk_ui_manager_add_ui().
939  * 
940  * Return value: an unused merge id.
941  *
942  * Since: 2.4
943  **/
944 guint
945 gtk_ui_manager_new_merge_id (GtkUIManager *self)
946 {
947   self->private_data->last_merge_id++;
948
949   return self->private_data->last_merge_id;
950 }
951
952 static void
953 node_prepend_ui_reference (Node   *node,
954                            guint   merge_id, 
955                            GQuark  action_quark)
956 {
957   NodeUIReference *reference;
958
959   reference = g_new (NodeUIReference, 1);
960   reference->action_quark = action_quark;
961   reference->merge_id = merge_id;
962
963   /* Prepend the reference */
964   node->uifiles = g_list_prepend (node->uifiles, reference);
965
966   node->dirty = TRUE;
967 }
968
969 static void
970 node_remove_ui_reference (Node  *node,
971                           guint  merge_id)
972 {
973   GList *p;
974   
975   for (p = node->uifiles; p != NULL; p = p->next)
976     {
977       NodeUIReference *reference = p->data;
978       
979       if (reference->merge_id == merge_id)
980         {
981           node->uifiles = g_list_remove_link (node->uifiles, p);
982           node->dirty = TRUE;
983           g_free (reference);
984
985           break;
986         }
987     }
988 }
989
990 /* -------------------- The UI file parser -------------------- */
991
992 typedef enum
993 {
994   STATE_START,
995   STATE_ROOT,
996   STATE_MENU,
997   STATE_TOOLBAR,
998   STATE_MENUITEM,
999   STATE_TOOLITEM,
1000   STATE_ACCELERATOR,
1001   STATE_END
1002 } ParseState;
1003
1004 typedef struct _ParseContext ParseContext;
1005 struct _ParseContext
1006 {
1007   ParseState state;
1008   ParseState prev_state;
1009
1010   GtkUIManager *self;
1011
1012   GNode *current;
1013
1014   guint merge_id;
1015 };
1016
1017 static void
1018 start_element_handler (GMarkupParseContext *context,
1019                        const gchar         *element_name,
1020                        const gchar        **attribute_names,
1021                        const gchar        **attribute_values,
1022                        gpointer             user_data,
1023                        GError             **error)
1024 {
1025   ParseContext *ctx = user_data;
1026   GtkUIManager *self = ctx->self;
1027
1028   gint i;
1029   const gchar *node_name;
1030   const gchar *action;
1031   GQuark action_quark;
1032   gboolean top;
1033   
1034   gboolean raise_error = TRUE;
1035
1036   node_name = NULL;
1037   action = NULL;
1038   action_quark = 0;
1039   top = FALSE;
1040
1041   for (i = 0; attribute_names[i] != NULL; i++)
1042     {
1043       if (!strcmp (attribute_names[i], "name"))
1044         {
1045           node_name = attribute_values[i];
1046         }
1047       else if (!strcmp (attribute_names[i], "action"))
1048         {
1049           action = attribute_values[i];
1050           action_quark = g_quark_from_string (attribute_values[i]);
1051         }
1052       else if (!strcmp (attribute_names[i], "position"))
1053         {
1054           top = !strcmp (attribute_values[i], "top");
1055         }
1056       else
1057         {
1058           gint line_number, char_number;
1059           
1060           g_markup_parse_context_get_position (context,
1061                                                &line_number, &char_number);
1062           g_set_error (error,
1063                        G_MARKUP_ERROR,
1064                        G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1065                        _("Unknown attribute '%s' on line %d char %d"),
1066                        attribute_names[i],
1067                        line_number, char_number);
1068           return;
1069         }
1070     }
1071
1072   /* Work out a name for this node.  Either the name attribute, or
1073    * the action, or the element name */
1074   if (node_name == NULL) 
1075     {
1076       if (action != NULL)
1077         node_name = action;
1078       else 
1079         node_name = element_name;
1080     }
1081
1082   switch (element_name[0])
1083     {
1084     case 'a':
1085       if (ctx->state == STATE_ROOT && !strcmp (element_name, "accelerator"))
1086         {
1087           ctx->state = STATE_ACCELERATOR;
1088           ctx->current = get_child_node (self, ctx->current,
1089                                          node_name, strlen (node_name),
1090                                          NODE_TYPE_ACCELERATOR,
1091                                          TRUE, FALSE);
1092           if (NODE_INFO (ctx->current)->action_name == 0)
1093             NODE_INFO (ctx->current)->action_name = action_quark;
1094
1095           node_prepend_ui_reference (NODE_INFO (ctx->current),
1096                                      ctx->merge_id, action_quark);
1097           NODE_INFO (ctx->current)->dirty = TRUE;
1098
1099           raise_error = FALSE;
1100         }
1101       break;
1102     case 'u':
1103       if (ctx->state == STATE_START && !strcmp (element_name, "ui"))
1104         {
1105           ctx->state = STATE_ROOT;
1106           ctx->current = self->private_data->root_node;
1107           raise_error = FALSE;
1108
1109           node_prepend_ui_reference (NODE_INFO (ctx->current),
1110                                      ctx->merge_id, action_quark);
1111         }
1112       break;
1113     case 'm':
1114       if (ctx->state == STATE_ROOT && !strcmp (element_name, "menubar"))
1115         {
1116           ctx->state = STATE_MENU;
1117           ctx->current = get_child_node (self, ctx->current,
1118                                          node_name, strlen (node_name),
1119                                          NODE_TYPE_MENUBAR,
1120                                          TRUE, FALSE);
1121           if (NODE_INFO (ctx->current)->action_name == 0)
1122             NODE_INFO (ctx->current)->action_name = action_quark;
1123
1124           node_prepend_ui_reference (NODE_INFO (ctx->current),
1125                                      ctx->merge_id, action_quark);
1126           NODE_INFO (ctx->current)->dirty = TRUE;
1127
1128           raise_error = FALSE;
1129         }
1130       else if (ctx->state == STATE_MENU && !strcmp (element_name, "menu"))
1131         {
1132           ctx->current = get_child_node (self, ctx->current,
1133                                          node_name, strlen (node_name),
1134                                          NODE_TYPE_MENU,
1135                                          TRUE, top);
1136           if (NODE_INFO (ctx->current)->action_name == 0)
1137             NODE_INFO (ctx->current)->action_name = action_quark;
1138
1139           node_prepend_ui_reference (NODE_INFO (ctx->current),
1140                                      ctx->merge_id, action_quark);
1141           NODE_INFO (ctx->current)->dirty = TRUE;
1142           
1143           raise_error = FALSE;
1144         }
1145       else if (ctx->state == STATE_MENU && !strcmp (element_name, "menuitem"))
1146         {
1147           GNode *node;
1148
1149           ctx->state = STATE_MENUITEM;
1150           node = get_child_node (self, ctx->current,
1151                                  node_name, strlen (node_name),
1152                                  NODE_TYPE_MENUITEM,
1153                                  TRUE, top);
1154           if (NODE_INFO (node)->action_name == 0)
1155             NODE_INFO (node)->action_name = action_quark;
1156           
1157           node_prepend_ui_reference (NODE_INFO (node),
1158                                      ctx->merge_id, action_quark);
1159           NODE_INFO (node)->dirty = TRUE;
1160           
1161           raise_error = FALSE;
1162         }
1163       break;
1164     case 'p':
1165       if (ctx->state == STATE_ROOT && !strcmp (element_name, "popup"))
1166         {
1167           ctx->state = STATE_MENU;
1168           ctx->current = get_child_node (self, ctx->current,
1169                                          node_name, strlen (node_name),
1170                                          NODE_TYPE_POPUP,
1171                                          TRUE, FALSE);
1172           if (NODE_INFO (ctx->current)->action_name == 0)
1173             NODE_INFO (ctx->current)->action_name = action_quark;
1174           
1175           node_prepend_ui_reference (NODE_INFO (ctx->current),
1176                                      ctx->merge_id, action_quark);
1177           NODE_INFO (ctx->current)->dirty = TRUE;
1178           
1179           raise_error = FALSE;
1180         }
1181       else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
1182                !strcmp (element_name, "placeholder"))
1183         {
1184           if (ctx->state == STATE_TOOLBAR)
1185             ctx->current = get_child_node (self, ctx->current,
1186                                            node_name, strlen (node_name),
1187                                            NODE_TYPE_TOOLBAR_PLACEHOLDER,
1188                                            TRUE, top);
1189           else
1190             ctx->current = get_child_node (self, ctx->current,
1191                                            node_name, strlen (node_name),
1192                                            NODE_TYPE_MENU_PLACEHOLDER,
1193                                            TRUE, top);
1194           
1195           node_prepend_ui_reference (NODE_INFO (ctx->current),
1196                                      ctx->merge_id, action_quark);
1197           NODE_INFO (ctx->current)->dirty = TRUE;
1198           
1199           raise_error = FALSE;
1200         }
1201       break;
1202     case 's':
1203       if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
1204           !strcmp (element_name, "separator"))
1205         {
1206           GNode *node;
1207
1208           if (ctx->state == STATE_TOOLBAR)
1209             ctx->state = STATE_TOOLITEM;
1210           else
1211             ctx->state = STATE_MENUITEM;
1212           node = get_child_node (self, ctx->current,
1213                                  node_name, strlen (node_name),
1214                                  NODE_TYPE_SEPARATOR,
1215                                  TRUE, top);
1216           if (NODE_INFO (node)->action_name == 0)
1217             NODE_INFO (node)->action_name = action_quark;
1218
1219           node_prepend_ui_reference (NODE_INFO (node),
1220                                      ctx->merge_id, action_quark);
1221           NODE_INFO (node)->dirty = TRUE;
1222           
1223           raise_error = FALSE;
1224         }
1225       break;
1226     case 't':
1227       if (ctx->state == STATE_ROOT && !strcmp (element_name, "toolbar"))
1228         {
1229           ctx->state = STATE_TOOLBAR;
1230           ctx->current = get_child_node (self, ctx->current,
1231                                          node_name, strlen (node_name),
1232                                          NODE_TYPE_TOOLBAR,
1233                                          TRUE, FALSE);
1234           if (NODE_INFO (ctx->current)->action_name == 0)
1235             NODE_INFO (ctx->current)->action_name = action_quark;
1236           
1237           node_prepend_ui_reference (NODE_INFO (ctx->current),
1238                                      ctx->merge_id, action_quark);
1239           NODE_INFO (ctx->current)->dirty = TRUE;
1240           
1241           raise_error = FALSE;
1242         }
1243       else if (ctx->state == STATE_TOOLBAR && !strcmp (element_name, "toolitem"))
1244         {
1245           GNode *node;
1246
1247           ctx->state = STATE_TOOLITEM;
1248           node = get_child_node (self, ctx->current,
1249                                 node_name, strlen (node_name),
1250                                  NODE_TYPE_TOOLITEM,
1251                                  TRUE, top);
1252           if (NODE_INFO (node)->action_name == 0)
1253             NODE_INFO (node)->action_name = action_quark;
1254           
1255           node_prepend_ui_reference (NODE_INFO (node),
1256                                      ctx->merge_id, action_quark);
1257           NODE_INFO (node)->dirty = TRUE;
1258
1259           raise_error = FALSE;
1260         }
1261       break;
1262     default:
1263       break;
1264     }
1265   if (raise_error)
1266     {
1267       gint line_number, char_number;
1268  
1269       g_markup_parse_context_get_position (context,
1270                                            &line_number, &char_number);
1271       g_set_error (error,
1272                    G_MARKUP_ERROR,
1273                    G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1274                    _("Unexpected start tag '%s' on line %d char %d"),
1275                    element_name,
1276                    line_number, char_number);
1277     }
1278 }
1279
1280 static void
1281 end_element_handler (GMarkupParseContext *context,
1282                      const gchar         *element_name,
1283                      gpointer             user_data,
1284                      GError             **error)
1285 {
1286   ParseContext *ctx = user_data;
1287
1288   switch (ctx->state)
1289     {
1290     case STATE_START:
1291     case STATE_END:
1292       /* no need to GError here, GMarkup already catches this */
1293       break;
1294     case STATE_ROOT:
1295       ctx->current = NULL;
1296       ctx->state = STATE_END;
1297       break;
1298     case STATE_MENU:
1299     case STATE_TOOLBAR:
1300     case STATE_ACCELERATOR:
1301       ctx->current = ctx->current->parent;
1302       if (NODE_INFO (ctx->current)->type == NODE_TYPE_ROOT) 
1303         ctx->state = STATE_ROOT;
1304       /* else, stay in same state */
1305       break;
1306     case STATE_MENUITEM:
1307       ctx->state = STATE_MENU;
1308       break;
1309     case STATE_TOOLITEM:
1310       ctx->state = STATE_TOOLBAR;
1311       break;
1312     }
1313 }
1314
1315 static void
1316 cleanup (GMarkupParseContext *context,
1317          GError              *error,
1318          gpointer             user_data)
1319 {
1320   ParseContext *ctx = user_data;
1321
1322   ctx->current = NULL;
1323   /* should also walk through the tree and get rid of nodes related to
1324    * this UI file's tag */
1325
1326   gtk_ui_manager_remove_ui (ctx->self, ctx->merge_id);
1327 }
1328
1329 static gboolean
1330 xml_isspace (char c)
1331 {
1332   return c == ' ' || c == '\t' || c == '\n' || c == '\r';
1333 }
1334
1335 static void 
1336 text_handler (GMarkupParseContext *context,
1337               const gchar         *text,
1338               gsize                text_len,  
1339               gpointer             user_data,
1340               GError             **error)
1341 {
1342   const gchar *p;
1343   const gchar *end;
1344
1345   p = text;
1346   end = text + text_len;
1347   while (p != end && xml_isspace (*p))
1348     ++p;
1349   
1350   if (p != end)
1351     {
1352       gint line_number, char_number;
1353       
1354       g_markup_parse_context_get_position (context,
1355                                            &line_number, &char_number);
1356       g_set_error (error,
1357                    G_MARKUP_ERROR,
1358                    G_MARKUP_ERROR_INVALID_CONTENT,
1359                    _("Unexpected character data on line %d char %d"),
1360                    line_number, char_number);
1361     }
1362 }
1363
1364
1365 static GMarkupParser ui_parser = {
1366   start_element_handler,
1367   end_element_handler,
1368   text_handler,
1369   NULL,
1370   cleanup
1371 };
1372
1373 static guint
1374 add_ui_from_string (GtkUIManager *self,
1375                     const gchar  *buffer, 
1376                     gssize        length,
1377                     gboolean      needs_root,
1378                     GError      **error)
1379 {
1380   ParseContext ctx = { 0 };
1381   GMarkupParseContext *context;
1382
1383   ctx.state = STATE_START;
1384   ctx.self = self;
1385   ctx.current = NULL;
1386   ctx.merge_id = gtk_ui_manager_new_merge_id (self);
1387
1388   context = g_markup_parse_context_new (&ui_parser, 0, &ctx, NULL);
1389
1390   if (needs_root)
1391     if (!g_markup_parse_context_parse (context, "<ui>", -1, error))
1392       goto error;
1393
1394   if (!g_markup_parse_context_parse (context, buffer, length, error))
1395     goto error;
1396
1397   if (needs_root)
1398     if (!g_markup_parse_context_parse (context, "</ui>", -1, error))
1399       goto error;
1400
1401   if (!g_markup_parse_context_end_parse (context, error))
1402     goto error;
1403
1404   g_markup_parse_context_free (context);
1405
1406   queue_update (self);
1407
1408   g_object_notify (G_OBJECT (self), "ui");      
1409
1410   return ctx.merge_id;
1411
1412  error:
1413
1414   g_markup_parse_context_free (context);
1415
1416   return 0;
1417 }
1418
1419 /**
1420  * gtk_ui_manager_add_ui_from_string:
1421  * @self: a #GtkUIManager object
1422  * @buffer: the string to parse
1423  * @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
1424  * @error: return location for an error
1425  * 
1426  * Parses a string containing a <link linkend="XML-UI">UI definition</link> and 
1427  * merges it with the current contents of @self. An enclosing &lt;ui&gt; 
1428  * element is added if it is missing.
1429  * 
1430  * Return value: The merge id for the merged UI. The merge id can be used
1431  *   to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1432  *   the return value is 0.
1433  *
1434  * Since: 2.4
1435  **/
1436 guint
1437 gtk_ui_manager_add_ui_from_string (GtkUIManager *self,
1438                                    const gchar  *buffer, 
1439                                    gssize        length,
1440                                    GError      **error)
1441 {
1442   gboolean needs_root = TRUE;
1443   const gchar *p;
1444   const gchar *end;
1445
1446   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), 0);
1447   g_return_val_if_fail (buffer != NULL, 0);
1448
1449   if (length < 0)
1450     length = strlen (buffer);
1451
1452   p = buffer;
1453   end = buffer + length;
1454   while (p != end && xml_isspace (*p))
1455     ++p;
1456
1457   if (end - p >= 4 && strncmp (p, "<ui>", 4) == 0)
1458     needs_root = FALSE;
1459   
1460   return add_ui_from_string (self, buffer, length, needs_root, error);
1461 }
1462
1463 /**
1464  * gtk_ui_manager_add_ui_from_file:
1465  * @self: a #GtkUIManager object
1466  * @filename: the name of the file to parse 
1467  * @error: return location for an error
1468  * 
1469  * Parses a file containing a <link linkend="XML-UI">UI definition</link> and 
1470  * merges it with the current contents of @self. 
1471  * 
1472  * Return value: The merge id for the merged UI. The merge id can be used
1473  *   to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1474  *   the return value is 0.
1475  *
1476  * Since: 2.4
1477  **/
1478 guint
1479 gtk_ui_manager_add_ui_from_file (GtkUIManager *self,
1480                                  const gchar  *filename,
1481                                  GError      **error)
1482 {
1483   gchar *buffer;
1484   gsize length;
1485   guint res;
1486
1487   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), 0);
1488
1489   if (!g_file_get_contents (filename, &buffer, &length, error))
1490     return 0;
1491
1492   res = add_ui_from_string (self, buffer, length, FALSE, error);
1493   g_free (buffer);
1494
1495   return res;
1496 }
1497
1498 /**
1499  * gtk_ui_manager_add_ui:
1500  * @self: a #GtkUIManager
1501  * @merge_id: the merge id for the merged UI, see gtk_ui_manager_new_merge_id()
1502  * @path: a path
1503  * @name: the name for the added UI element
1504  * @action: the name of the action to be proxied, or %NULL to add a separator
1505  * @type: the type of UI element to add.
1506  * @top: if %TRUE, the UI element is added before its siblings, otherwise it 
1507  *   is added after its siblings.
1508  * 
1509  * Adds a UI element to the current contents of @self. 
1510  *
1511  * If @type is %GTK_UI_MANAGER_AUTO, GTK+ inserts a menuitem, toolitem or 
1512  * separator if such an element can be inserted at the place determined by 
1513  * @path. Otherwise @type must indicate an element that can be inserted at 
1514  * the place determined by @path.
1515  * 
1516  * Since: 2.4
1517  **/
1518 void
1519 gtk_ui_manager_add_ui (GtkUIManager        *self,
1520                        guint                merge_id,
1521                        const gchar         *path,
1522                        const gchar         *name,
1523                        const gchar         *action,
1524                        GtkUIManagerItemType type,
1525                        gboolean             top)
1526 {
1527   GNode *node;
1528   GNode *child;
1529   NodeType node_type;
1530   GQuark action_quark = 0;
1531
1532   g_return_if_fail (GTK_IS_UI_MANAGER (self));  
1533   g_return_if_fail (merge_id > 0);
1534   g_return_if_fail (name != NULL);
1535
1536   node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
1537   
1538   if (node == NULL)
1539     return;
1540
1541   node_type = NODE_TYPE_UNDECIDED;
1542
1543   switch (NODE_INFO (node)->type) 
1544     {
1545     case NODE_TYPE_MENUBAR:
1546     case NODE_TYPE_MENU:
1547     case NODE_TYPE_POPUP:
1548     case NODE_TYPE_MENU_PLACEHOLDER:
1549       switch (type) 
1550         {
1551         case GTK_UI_MANAGER_AUTO:
1552           if (action != NULL)
1553               node_type = NODE_TYPE_MENUITEM;
1554           else
1555               node_type = NODE_TYPE_SEPARATOR;
1556           break;
1557         case GTK_UI_MANAGER_MENU:
1558           node_type = NODE_TYPE_MENU;
1559           break;
1560         case GTK_UI_MANAGER_MENUITEM:
1561           node_type = NODE_TYPE_MENUITEM;
1562           break;
1563         case GTK_UI_MANAGER_SEPARATOR:
1564           node_type = NODE_TYPE_SEPARATOR;
1565           break;
1566         case GTK_UI_MANAGER_PLACEHOLDER:
1567           node_type = NODE_TYPE_MENU_PLACEHOLDER;
1568           break;
1569         default: ;
1570           /* do nothing */
1571         }
1572       break;
1573     case NODE_TYPE_TOOLBAR:
1574     case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1575       switch (type) 
1576         {
1577         case GTK_UI_MANAGER_AUTO:
1578           if (action != NULL)
1579               node_type = NODE_TYPE_TOOLITEM;
1580           else
1581               node_type = NODE_TYPE_SEPARATOR;
1582           break;
1583         case GTK_UI_MANAGER_TOOLITEM:
1584           node_type = NODE_TYPE_TOOLITEM;
1585           break;
1586         case GTK_UI_MANAGER_SEPARATOR:
1587           node_type = NODE_TYPE_SEPARATOR;
1588           break;
1589         case GTK_UI_MANAGER_PLACEHOLDER:
1590           node_type = NODE_TYPE_MENU_PLACEHOLDER;
1591           break;
1592         default: ;
1593           /* do nothing */
1594         }
1595       break;
1596     case NODE_TYPE_ROOT:
1597       switch (type) 
1598         {
1599         case GTK_UI_MANAGER_MENUBAR:
1600           node_type = NODE_TYPE_MENUBAR;
1601           break;
1602         case GTK_UI_MANAGER_TOOLBAR:
1603           node_type = NODE_TYPE_TOOLBAR;
1604           break;
1605         case GTK_UI_MANAGER_POPUP:
1606           node_type = NODE_TYPE_POPUP;
1607           break;
1608         case GTK_UI_MANAGER_ACCELERATOR:
1609           node_type = NODE_TYPE_ACCELERATOR;
1610           break;
1611         default: ;
1612           /* do nothing */
1613         }
1614       break;
1615     default: ;
1616       /* do nothing */
1617     }
1618
1619   if (node_type == NODE_TYPE_UNDECIDED)
1620     return;
1621    
1622   child = get_child_node (self, node,
1623                           name, strlen (name),
1624                           node_type, TRUE, top);
1625
1626   if (action != NULL)
1627     action_quark = g_quark_from_string (action);
1628
1629   node_prepend_ui_reference (NODE_INFO (child), 
1630                              merge_id, action_quark);
1631
1632   if (NODE_INFO (node)->action_name == 0)
1633     NODE_INFO (child)->action_name = action_quark;
1634
1635   NODE_INFO (child)->dirty = TRUE;
1636
1637   queue_update (self);
1638
1639   g_object_notify (G_OBJECT (self), "ui");      
1640 }
1641
1642 static gboolean
1643 remove_ui (GNode   *node, 
1644            gpointer user_data)
1645 {
1646   guint merge_id = GPOINTER_TO_UINT (user_data);
1647
1648   node_remove_ui_reference (NODE_INFO (node), merge_id);
1649
1650   return FALSE; /* continue */
1651 }
1652
1653 /**
1654  * gtk_ui_manager_remove_ui:
1655  * @self: a #GtkUIManager object
1656  * @merge_id: a merge id as returned by gtk_ui_manager_add_ui_from_string()
1657  * 
1658  * Unmerges the part of @self<!-- -->s content identified by @merge_id.
1659  *
1660  * Since: 2.4
1661  **/
1662 void
1663 gtk_ui_manager_remove_ui (GtkUIManager *self, 
1664                           guint         merge_id)
1665 {
1666   g_node_traverse (self->private_data->root_node, 
1667                    G_POST_ORDER, G_TRAVERSE_ALL, -1,
1668                    remove_ui, GUINT_TO_POINTER (merge_id));
1669
1670   queue_update (self);
1671
1672   g_object_notify (G_OBJECT (self), "ui");      
1673 }
1674
1675 /* -------------------- Updates -------------------- */
1676
1677
1678 static GtkAction *
1679 get_action_by_name (GtkUIManager *merge, 
1680                     const gchar  *action_name)
1681 {
1682   GList *tmp;
1683
1684   if (!action_name)
1685     return NULL;
1686   
1687   /* lookup name */
1688   for (tmp = merge->private_data->action_groups; tmp != NULL; tmp = tmp->next)
1689     {
1690       GtkActionGroup *action_group = tmp->data;
1691       GtkAction *action;
1692       
1693       action = gtk_action_group_get_action (action_group, action_name);
1694
1695       if (action)
1696         return action;
1697     }
1698
1699   return NULL;
1700 }
1701
1702 static gboolean
1703 find_menu_position (GNode      *node, 
1704                     GtkWidget **menushell_p, 
1705                     gint       *pos_p)
1706 {
1707   GtkWidget *menushell;
1708   gint pos;
1709
1710   g_return_val_if_fail (node != NULL, FALSE);
1711   g_return_val_if_fail (NODE_INFO (node)->type == NODE_TYPE_MENU ||
1712                         NODE_INFO (node)->type == NODE_TYPE_POPUP ||
1713                         NODE_INFO (node)->type == NODE_TYPE_MENU_PLACEHOLDER ||
1714                         NODE_INFO (node)->type == NODE_TYPE_MENUITEM ||
1715                         NODE_INFO (node)->type == NODE_TYPE_SEPARATOR,
1716                         FALSE);
1717
1718   /* first sibling -- look at parent */
1719   if (node->prev == NULL)
1720     {
1721       GNode *parent;
1722       GList *siblings;
1723
1724       parent = node->parent;
1725       switch (NODE_INFO (parent)->type)
1726         {
1727         case NODE_TYPE_MENUBAR:
1728         case NODE_TYPE_POPUP:
1729           menushell = NODE_INFO (parent)->proxy;
1730           pos = 0;
1731           break;
1732         case NODE_TYPE_MENU:
1733           menushell = NODE_INFO (parent)->proxy;
1734           if (GTK_IS_MENU_ITEM (menushell))
1735             menushell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menushell));
1736           siblings = gtk_container_get_children (GTK_CONTAINER (menushell));
1737           if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1738             pos = 1;
1739           else
1740             pos = 0;
1741           g_list_free (siblings);
1742           break;
1743         case NODE_TYPE_MENU_PLACEHOLDER:
1744           menushell = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1745           g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1746           pos = g_list_index (GTK_MENU_SHELL (menushell)->children,
1747                               NODE_INFO (parent)->proxy) + 1;
1748           break;
1749         default:
1750           g_warning("%s: bad parent node type %d", G_STRLOC,
1751                     NODE_INFO (parent)->type);
1752           return FALSE;
1753         }
1754     }
1755   else
1756     {
1757       GtkWidget *prev_child;
1758       GNode *sibling;
1759
1760       sibling = node->prev;
1761       if (NODE_INFO (sibling)->type == NODE_TYPE_MENU_PLACEHOLDER)
1762         prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1763       else
1764         prev_child = NODE_INFO (sibling)->proxy;
1765
1766       g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
1767       menushell = gtk_widget_get_parent (prev_child);
1768       g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1769
1770       pos = g_list_index (GTK_MENU_SHELL (menushell)->children, prev_child) + 1;
1771     }
1772
1773   if (menushell_p)
1774     *menushell_p = menushell;
1775   if (pos_p)
1776     *pos_p = pos;
1777
1778   return TRUE;
1779 }
1780
1781 static gboolean
1782 find_toolbar_position (GNode      *node, 
1783                        GtkWidget **toolbar_p, 
1784                        gint       *pos_p)
1785 {
1786   GtkWidget *toolbar;
1787   gint pos;
1788
1789   g_return_val_if_fail (node != NULL, FALSE);
1790   g_return_val_if_fail (NODE_INFO (node)->type == NODE_TYPE_TOOLBAR ||
1791                         NODE_INFO (node)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER ||
1792                         NODE_INFO (node)->type == NODE_TYPE_TOOLITEM ||
1793                         NODE_INFO (node)->type == NODE_TYPE_SEPARATOR,
1794                         FALSE);
1795
1796   /* first sibling -- look at parent */
1797   if (node->prev == NULL)
1798     {
1799       GNode *parent;
1800
1801       parent = node->parent;
1802       switch (NODE_INFO (parent)->type)
1803         {
1804         case NODE_TYPE_TOOLBAR:
1805           toolbar = NODE_INFO (parent)->proxy;
1806           pos = 0;
1807           break;
1808         case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1809           toolbar = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1810           g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
1811           pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
1812                                             GTK_TOOL_ITEM (NODE_INFO (parent)->proxy)) + 1;
1813           break;
1814         default:
1815           g_warning ("%s: bad parent node type %d", G_STRLOC,
1816                      NODE_INFO (parent)->type);
1817           return FALSE;
1818         }
1819     }
1820   else
1821     {
1822       GtkWidget *prev_child;
1823       GNode *sibling;
1824
1825       sibling = node->prev;
1826       if (NODE_INFO (sibling)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
1827         prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1828       else
1829         prev_child = NODE_INFO (sibling)->proxy;
1830
1831       g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
1832       toolbar = gtk_widget_get_parent (prev_child);
1833       g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
1834
1835       pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
1836                                         GTK_TOOL_ITEM (prev_child)) + 1;
1837     }
1838
1839   if (toolbar_p)
1840     *toolbar_p = toolbar;
1841   if (pos_p)
1842     *pos_p = pos;
1843
1844   return TRUE;
1845 }
1846
1847 /**
1848  * _gtk_menu_is_empty:
1849  * @menu: a #GtkMenu or %NULL
1850  * 
1851  * Determines whether @menu is empty. A menu is considered empty if it
1852  * the only visible children are tearoff menu items or "filler" menu 
1853  * items which were inserted to mark the menu as empty.
1854  * 
1855  * This function is used by #GtkAction.
1856  *
1857  * Return value: whether @menu is empty.
1858  **/
1859 gboolean
1860 _gtk_menu_is_empty (GtkWidget *menu)
1861 {
1862   GList *children, *cur;
1863
1864   g_return_val_if_fail (menu == NULL || GTK_IS_MENU (menu), TRUE);
1865
1866   if (!menu)
1867     return FALSE;
1868
1869   children = gtk_container_get_children (GTK_CONTAINER (menu));
1870
1871   cur = children;
1872   while (cur) 
1873     {
1874       if (GTK_WIDGET_VISIBLE (cur->data))
1875         {
1876           if (!GTK_IS_TEAROFF_MENU_ITEM (cur->data) &&
1877               !g_object_get_data (cur->data, "gtk-empty-menu-item"))
1878             return FALSE;
1879         }
1880       cur = cur->next;
1881     }
1882   g_list_free (children);
1883
1884   return TRUE;
1885 }
1886
1887 enum {
1888   SEPARATOR_MODE_SMART,
1889   SEPARATOR_MODE_VISIBLE,
1890   SEPARATOR_MODE_HIDDEN
1891 };
1892
1893 void _gtk_action_sync_menu_visible (GtkAction *action,
1894                                     GtkWidget *proxy,
1895                                     gboolean   empty);
1896
1897 static void
1898 update_smart_separators (GtkWidget *proxy)
1899 {
1900   GtkWidget *parent = NULL;
1901   
1902   if (GTK_IS_MENU (proxy) || GTK_IS_TOOLBAR (proxy))
1903     parent = proxy;
1904   else if (GTK_IS_MENU_ITEM (proxy) || GTK_IS_TOOL_ITEM (proxy))
1905     parent = gtk_widget_get_parent (proxy);
1906
1907   
1908   if (parent) 
1909     {
1910       gboolean visible;
1911       gboolean empty;
1912       GList *children, *cur, *last;
1913       GtkWidget *filler;
1914       gint i;
1915
1916       children = gtk_container_get_children (GTK_CONTAINER (parent));
1917       
1918       visible = FALSE;
1919       last = NULL;
1920       empty = TRUE;
1921       filler = NULL;
1922
1923       i = 0;
1924       cur = children;
1925       while (cur) 
1926         {
1927           if (g_object_get_data (cur->data, "gtk-empty-menu-item"))
1928             filler = cur->data;
1929
1930           if (GTK_IS_SEPARATOR_MENU_ITEM (cur->data) ||
1931               GTK_IS_SEPARATOR_TOOL_ITEM (cur->data))
1932             {
1933               gint mode = 
1934                 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cur->data), 
1935                                                     "gtk-separator-mode"));
1936               switch (mode) 
1937                 {
1938                 case SEPARATOR_MODE_VISIBLE:
1939                   gtk_widget_show (GTK_WIDGET (cur->data));
1940                   last = NULL;
1941                   visible = FALSE;
1942                   break;
1943                 case SEPARATOR_MODE_HIDDEN:
1944                   gtk_widget_hide (GTK_WIDGET (cur->data));
1945                   break;
1946                 case SEPARATOR_MODE_SMART:
1947                   if (visible)
1948                     {
1949                       gtk_widget_show (GTK_WIDGET (cur->data));
1950                       last = cur;
1951                       visible = FALSE;
1952                     }
1953                   else 
1954                     gtk_widget_hide (GTK_WIDGET (cur->data));
1955                   break;
1956                 }
1957             }
1958           else if (GTK_WIDGET_VISIBLE (cur->data))
1959             {
1960               last = NULL;
1961               if (GTK_IS_TEAROFF_MENU_ITEM (cur->data) || cur->data == filler)
1962                 visible = FALSE;
1963               else 
1964                 {
1965                   visible = TRUE;
1966                   empty = FALSE;
1967                 }
1968             }
1969           
1970           cur = cur->next;
1971         }
1972
1973       if (last)
1974         gtk_widget_hide (GTK_WIDGET (last->data));
1975
1976       if (GTK_IS_MENU (parent)) 
1977         {
1978           GtkWidget *item;
1979
1980           item = gtk_menu_get_attach_widget (GTK_MENU (parent));
1981           if (GTK_IS_MENU_ITEM (item))
1982             _gtk_action_sync_menu_visible (NULL, item, empty);
1983           if (GTK_IS_WIDGET (filler))
1984             g_object_set (G_OBJECT (filler), "visible", empty, NULL);
1985         }
1986
1987       g_list_free (children);
1988     }
1989 }
1990
1991 static void
1992 update_node (GtkUIManager *self, 
1993              GNode        *node,
1994              gboolean      in_popup)
1995 {
1996   Node *info;
1997   GNode *child;
1998   GtkAction *action;
1999   gchar *tooltip;
2000 #ifdef DEBUG_UI_MANAGER
2001   GList *tmp;
2002 #endif
2003   
2004   g_return_if_fail (node != NULL);
2005   g_return_if_fail (NODE_INFO (node) != NULL);
2006
2007   info = NODE_INFO (node);
2008
2009   in_popup = in_popup || (info->type == NODE_TYPE_POPUP);
2010
2011 #ifdef DEBUG_UI_MANAGER
2012   g_print ("update_node name=%s dirty=%d popup %d (", 
2013            info->name, info->dirty, in_popup);
2014   for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next)
2015     {
2016       NodeUIReference *ref = tmp->data;
2017       g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id);
2018       if (tmp->next)
2019         g_print (", ");
2020     }
2021   g_print (")\n");
2022 #endif
2023
2024   if (info->dirty)
2025     {
2026       const gchar *action_name;
2027       NodeUIReference *ref;
2028
2029       if (info->uifiles == NULL) {
2030         /* We may need to remove this node.
2031          * This must be done in post order
2032          */
2033         goto recurse_children;
2034       }
2035
2036       ref = info->uifiles->data;
2037       action_name = g_quark_to_string (ref->action_quark);
2038       action = get_action_by_name (self, action_name);
2039
2040       info->dirty = FALSE;
2041
2042       /* Check if the node doesn't have an action and must have an action */
2043       if (action == NULL &&
2044           info->type != NODE_TYPE_ROOT &&
2045           info->type != NODE_TYPE_MENUBAR &&
2046           info->type != NODE_TYPE_TOOLBAR &&
2047           info->type != NODE_TYPE_POPUP &&
2048           info->type != NODE_TYPE_SEPARATOR &&
2049           info->type != NODE_TYPE_MENU_PLACEHOLDER &&
2050           info->type != NODE_TYPE_TOOLBAR_PLACEHOLDER)
2051         {
2052           g_warning ("%s: missing action", info->name);
2053
2054           goto recurse_children;
2055         }
2056
2057       if (action)
2058         gtk_action_set_accel_group (action, self->private_data->accel_group);
2059
2060       /* If the widget already has a proxy and the action hasn't changed, then
2061        * we only have to update the tearoff menu items.
2062        */
2063       if (info->proxy != NULL && action == info->action)
2064         {
2065           if (info->type == NODE_TYPE_MENU) 
2066             {
2067               GtkWidget *menu;
2068               GList *siblings;
2069
2070               menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2071               siblings = gtk_container_get_children (GTK_CONTAINER (menu));
2072               if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
2073                 g_object_set (G_OBJECT (siblings->data), 
2074                               "visible", self->private_data->add_tearoffs && !in_popup, 
2075                               NULL);
2076               g_list_free (siblings);
2077             }
2078
2079           goto recurse_children;
2080         }
2081       
2082       switch (info->type)
2083         {
2084         case NODE_TYPE_MENUBAR:
2085           if (info->proxy == NULL)
2086             {
2087               info->proxy = gtk_menu_bar_new ();
2088               gtk_widget_set_name (info->proxy, info->name);
2089               gtk_widget_show (info->proxy);
2090               g_signal_emit (self, ui_manager_signals[ADD_WIDGET], 0, info->proxy);
2091             }
2092           break;
2093         case NODE_TYPE_POPUP:
2094           if (info->proxy == NULL) 
2095             info->proxy = gtk_menu_new ();
2096           gtk_widget_set_name (info->proxy, info->name);
2097           break;
2098         case NODE_TYPE_MENU:
2099           {
2100             GtkWidget *prev_submenu = NULL;
2101             GtkWidget *menu;
2102             GList *siblings;
2103             /* remove the proxy if it is of the wrong type ... */
2104             if (info->proxy &&  
2105                 G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->menu_item_type)
2106               {
2107                 prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2108                 if (prev_submenu)
2109                   {
2110                     g_object_ref (prev_submenu);
2111                     gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
2112                   }
2113                 gtk_action_disconnect_proxy (info->action, info->proxy);
2114                 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2115                                       info->proxy);
2116                 info->proxy = NULL;
2117               }
2118             /* create proxy if needed ... */
2119             if (info->proxy == NULL)
2120               {
2121                 GtkWidget *menushell;
2122                 gint pos;
2123                 
2124                 if (find_menu_position (node, &menushell, &pos))
2125                   {
2126                     GtkWidget *tearoff;
2127                     GtkWidget *filler;
2128
2129                     info->proxy = gtk_action_create_menu_item (action);
2130                     menu = gtk_menu_new ();
2131                     gtk_widget_set_name (info->proxy, info->name);
2132                     gtk_widget_set_name (menu, info->name);
2133                     tearoff = gtk_tearoff_menu_item_new ();
2134                     gtk_widget_set_no_show_all (tearoff, TRUE);
2135                     gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff);
2136                     filler = gtk_menu_item_new_with_label (_("Empty"));
2137                     g_object_set_data (G_OBJECT (filler),
2138                                        "gtk-empty-menu-item",
2139                                        GINT_TO_POINTER (TRUE));
2140                     gtk_widget_set_sensitive (filler, FALSE);
2141                     gtk_widget_set_no_show_all (filler, TRUE);
2142                     gtk_menu_shell_append (GTK_MENU_SHELL (menu), filler);
2143                     gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu);
2144                     gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos);
2145                   }
2146               }
2147             else
2148               gtk_action_connect_proxy (action, info->proxy);
2149
2150             if (prev_submenu)
2151               {
2152                 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),
2153                                            prev_submenu);
2154                 g_object_unref (prev_submenu);
2155               }
2156             menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2157             siblings = gtk_container_get_children (GTK_CONTAINER (menu));
2158             if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
2159               g_object_set (G_OBJECT (siblings->data), 
2160                             "visible", self->private_data->add_tearoffs && !in_popup, 
2161                             NULL);
2162             g_list_free (siblings);
2163           }
2164           break;
2165         case NODE_TYPE_UNDECIDED:
2166           g_warning ("found undecided node!");
2167           break;
2168         case NODE_TYPE_ROOT:
2169           break;
2170         case NODE_TYPE_TOOLBAR:
2171           if (info->proxy == NULL)
2172             {
2173               info->proxy = gtk_toolbar_new ();
2174               gtk_widget_set_name (info->proxy, info->name);
2175               gtk_widget_show (info->proxy);
2176               g_signal_emit (self, ui_manager_signals[ADD_WIDGET], 0, info->proxy);
2177             }
2178           break;
2179         case NODE_TYPE_MENU_PLACEHOLDER:
2180           /* create menu items for placeholders if necessary ... */
2181           if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) ||
2182               !GTK_IS_SEPARATOR_MENU_ITEM (info->extra))
2183             {
2184               if (info->proxy)
2185                 {
2186                   gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2187                                         info->proxy);
2188                   info->proxy = NULL;
2189                 }
2190               if (info->extra)
2191                 {
2192                   gtk_container_remove (GTK_CONTAINER (info->extra->parent),
2193                                         info->extra);
2194                   info->extra = NULL;
2195                 }
2196             }
2197           if (info->proxy == NULL)
2198             {
2199               GtkWidget *menushell;
2200               gint pos;
2201
2202               if (find_menu_position (node, &menushell, &pos))
2203                 {
2204                   info->proxy = gtk_separator_menu_item_new ();
2205                   g_object_set_data (G_OBJECT (info->proxy),
2206                                      "gtk-separator-mode",
2207                                      GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2208                   gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2209                                         NODE_INFO (node)->proxy, pos);
2210
2211                   info->extra = gtk_separator_menu_item_new ();
2212                   g_object_set_data (G_OBJECT (info->extra),
2213                                      "gtk-separator-mode",
2214                                      GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2215                   gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2216                                          NODE_INFO (node)->extra, pos+1);
2217                 }
2218             }
2219           break;
2220         case NODE_TYPE_TOOLBAR_PLACEHOLDER:
2221           /* create toolbar items for placeholders if necessary ... */
2222           if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) ||
2223               !GTK_IS_SEPARATOR_TOOL_ITEM (info->extra))
2224             {
2225               if (info->proxy)
2226                 {
2227                   gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2228                                         info->proxy);
2229                   info->proxy = NULL;
2230                 }
2231               if (info->extra)
2232                 {
2233                   gtk_container_remove (GTK_CONTAINER (info->extra->parent),
2234                                         info->extra);
2235                   info->extra = NULL;
2236                 }
2237             }
2238           if (info->proxy == NULL)
2239             {
2240               GtkWidget *toolbar;
2241               gint pos;
2242
2243               if (find_toolbar_position (node, &toolbar, &pos))
2244                 {
2245                   GtkToolItem *item;
2246
2247                   item = gtk_separator_tool_item_new ();
2248                   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2249                   info->proxy = GTK_WIDGET (item);
2250                   g_object_set_data (G_OBJECT (info->proxy),
2251                                      "gtk-separator-mode",
2252                                      GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2253
2254                   item = gtk_separator_tool_item_new ();
2255                   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1);
2256                   info->extra = GTK_WIDGET (item);
2257                   g_object_set_data (G_OBJECT (info->extra),
2258                                      "gtk-separator-mode",
2259                                      GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2260                 }
2261             }
2262           break;
2263         case NODE_TYPE_MENUITEM:
2264           /* remove the proxy if it is of the wrong type ... */
2265           if (info->proxy &&  
2266               G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->menu_item_type)
2267             {
2268               g_signal_handlers_disconnect_by_func (info->proxy,
2269                                                     G_CALLBACK (update_smart_separators),
2270                                                     0);  
2271               gtk_action_disconnect_proxy (info->action, info->proxy);
2272               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2273                                     info->proxy);
2274               info->proxy = NULL;
2275             }
2276           /* create proxy if needed ... */
2277           if (info->proxy == NULL)
2278             {
2279               GtkWidget *menushell;
2280               gint pos;
2281
2282               if (find_menu_position (node, &menushell, &pos))
2283                 {
2284                   info->proxy = gtk_action_create_menu_item (action);
2285                   gtk_widget_set_name (info->proxy, info->name);
2286                   
2287                   gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2288                                          info->proxy, pos);
2289                 }
2290             }
2291           else
2292             {
2293               g_signal_handlers_disconnect_by_func (info->proxy,
2294                                                     G_CALLBACK (update_smart_separators),
2295                                                     0);
2296               gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
2297               gtk_action_connect_proxy (action, info->proxy);
2298             }
2299           g_signal_connect (info->proxy, "notify::visible",
2300                             G_CALLBACK (update_smart_separators), 0);
2301           if (in_popup) 
2302             {
2303               /* don't show accels in popups */
2304               GtkWidget *label = GTK_BIN (info->proxy)->child;
2305               g_object_set (G_OBJECT (label),
2306                             "accel_closure", NULL,
2307                             NULL);
2308             }
2309
2310           break;
2311         case NODE_TYPE_TOOLITEM:
2312           /* remove the proxy if it is of the wrong type ... */
2313           if (info->proxy &&  G_OBJECT_TYPE (info->proxy) !=
2314               GTK_ACTION_GET_CLASS (action)->toolbar_item_type)
2315             {
2316               g_signal_handlers_disconnect_by_func (info->proxy,
2317                                                     G_CALLBACK (update_smart_separators),
2318                                                     0);
2319               gtk_action_disconnect_proxy (info->action, info->proxy);
2320               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2321                                     info->proxy);
2322               info->proxy = NULL;
2323             }
2324           /* create proxy if needed ... */
2325           if (info->proxy == NULL)
2326             {
2327               GtkWidget *toolbar;
2328               gint pos;
2329
2330               if (find_toolbar_position (node, &toolbar, &pos))
2331                 {
2332                   info->proxy = gtk_action_create_tool_item (action);
2333                   gtk_widget_set_name (info->proxy, info->name);
2334
2335                   gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
2336                                       GTK_TOOL_ITEM (info->proxy), pos);
2337
2338                  /* FIXME: we must trigger the notify::tooltip handler, since 
2339                   * tooltips on toolitems can't be set before the toolitem 
2340                   * is added to the toolbar.
2341                   */
2342                   g_object_get (G_OBJECT (action), "tooltip", &tooltip, NULL);
2343                   g_object_set (G_OBJECT (action), "tooltip", tooltip, NULL);
2344                   g_free (tooltip);
2345                 }
2346             }
2347           else
2348             {
2349               g_signal_handlers_disconnect_by_func (info->proxy,
2350                                                     G_CALLBACK (update_smart_separators),
2351                                                     0);
2352               gtk_action_connect_proxy (action, info->proxy);
2353             }
2354           g_signal_connect (info->proxy, "notify::visible",
2355                             G_CALLBACK (update_smart_separators), 0);
2356           break;
2357         case NODE_TYPE_SEPARATOR:
2358           if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR ||
2359               NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
2360             {
2361               GtkWidget *toolbar;
2362               gint pos;
2363
2364               if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy))
2365                 {
2366                   gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2367                                         info->proxy);
2368                   info->proxy = NULL;
2369                 }
2370
2371               if (find_toolbar_position (node, &toolbar, &pos))
2372                 {
2373                   GtkToolItem *item = gtk_separator_tool_item_new ();
2374                   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2375                   info->proxy = GTK_WIDGET (item);
2376                   gtk_widget_set_no_show_all (info->proxy, TRUE);
2377                   g_object_set_data (G_OBJECT (info->proxy),
2378                                      "gtk-separator-mode",
2379                                      GINT_TO_POINTER (SEPARATOR_MODE_SMART));
2380                   gtk_widget_show (info->proxy);
2381                 }
2382             }
2383           else
2384             {
2385               GtkWidget *menushell;
2386               gint pos;
2387
2388               if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy))
2389                 {
2390                   gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2391                                         info->proxy);
2392                   info->proxy = NULL;
2393                 }
2394
2395               if (find_menu_position (node, &menushell, &pos))
2396                 {
2397                   info->proxy = gtk_separator_menu_item_new ();
2398                   gtk_widget_set_no_show_all (info->proxy, TRUE);
2399                   g_object_set_data (G_OBJECT (info->proxy),
2400                                      "gtk-separator-mode",
2401                                      GINT_TO_POINTER (SEPARATOR_MODE_SMART));
2402                   gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2403                                          info->proxy, pos);
2404                   gtk_widget_show (info->proxy);
2405                 }
2406             }
2407           break;
2408         case NODE_TYPE_ACCELERATOR:
2409           gtk_action_connect_accelerator (action);
2410           break;
2411         }
2412
2413       if (action)
2414         g_object_ref (action);
2415       if (info->action)
2416         g_object_unref (info->action);
2417       info->action = action;
2418     }
2419
2420  recurse_children:
2421   /* process children */
2422   child = node->children;
2423   while (child)
2424     {
2425       GNode *current;
2426
2427       current = child;
2428       child = current->next;
2429       update_node (self, current, in_popup);
2430     }
2431
2432   if (info->proxy) 
2433     {
2434       if (info->type == NODE_TYPE_MENU) 
2435         update_smart_separators (gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy)));
2436       else if (info->type == NODE_TYPE_TOOLBAR)
2437         update_smart_separators (info->proxy);
2438     }
2439
2440   /* handle cleanup of dead nodes */
2441   if (node->children == NULL && info->uifiles == NULL)
2442     {
2443       if (info->proxy)
2444         gtk_widget_destroy (info->proxy);
2445       if (info->extra)
2446         gtk_widget_destroy (info->extra);
2447       if (info->type == NODE_TYPE_ACCELERATOR)
2448         gtk_action_disconnect_accelerator (info->action);
2449       free_node (node);
2450       g_node_destroy (node);
2451     }
2452 }
2453
2454 static gboolean
2455 do_updates (GtkUIManager *self)
2456 {
2457   GDK_THREADS_ENTER ();
2458
2459   /* this function needs to check through the tree for dirty nodes.
2460    * For such nodes, it needs to do the following:
2461    *
2462    * 1) check if they are referenced by any loaded UI files anymore.
2463    *    In which case, the proxy widget should be destroyed, unless
2464    *    there are any subnodes.
2465    *
2466    * 2) lookup the action for this node again.  If it is different to
2467    *    the current one (or if no previous action has been looked up),
2468    *    the proxy is reconnected to the new action (or a new proxy widget
2469    *    is created and added to the parent container).
2470    */
2471   update_node (self, self->private_data->root_node, FALSE);
2472
2473   self->private_data->update_tag = 0;
2474
2475   GDK_THREADS_LEAVE ();
2476
2477   return FALSE;
2478 }
2479
2480 static void
2481 queue_update (GtkUIManager *self)
2482 {
2483   if (self->private_data->update_tag != 0)
2484     return;
2485
2486   self->private_data->update_tag = g_idle_add ((GSourceFunc)do_updates, self);
2487 }
2488
2489
2490 /**
2491  * gtk_ui_manager_ensure_update:
2492  * @self: a #GtkUIManager
2493  * 
2494  * Makes sure that all pending updates to the UI have been completed.
2495  *
2496  * This may occasionally be necessary, since #GtkUIManager updates the 
2497  * UI in an idle function. A typical example where this function is
2498  * useful is to enforce that the menubar and toolbar have been added to 
2499  * the main window before showing it:
2500  * <informalexample>
2501  * <programlisting>
2502  * gtk_container_add (GTK_CONTAINER (window), vbox); 
2503  * g_signal_connect (merge, "add_widget", 
2504  *                   G_CALLBACK (add_widget), vbox);
2505  * gtk_ui_manager_add_ui_from_file (merge, "my-menus");
2506  * gtk_ui_manager_add_ui_from_file (merge, "my-toolbars");
2507  * gtk_ui_manager_ensure_update (merge);  
2508  * gtk_widget_show (window);
2509  * </programlisting>
2510  * </informalexample>
2511  *
2512  * Since: 2.4
2513  **/
2514 void
2515 gtk_ui_manager_ensure_update (GtkUIManager *self)
2516 {
2517   if (self->private_data->update_tag != 0)
2518     {
2519       g_source_remove (self->private_data->update_tag);
2520       do_updates (self);
2521     }
2522 }
2523
2524 static gboolean
2525 dirty_traverse_func (GNode   *node, 
2526                      gpointer data)
2527 {
2528   NODE_INFO (node)->dirty = TRUE;
2529   return FALSE;
2530 }
2531
2532 static void
2533 dirty_all_nodes (GtkUIManager *self)
2534 {
2535   g_node_traverse (self->private_data->root_node, 
2536                    G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2537                    dirty_traverse_func, NULL);
2538   queue_update (self);
2539 }
2540
2541 static const gchar *open_tag_format[] = {
2542   "%*s<UNDECIDED>\n",
2543   "%*s<ui>\n",
2544   "%*s<menubar name=\"%s\">\n",  
2545   "%*s<menu name=\"%s\" action=\"%s\">\n",
2546   "%*s<toolbar name=\"%s\">\n",
2547   "%*s<placeholder name=\"%s\">\n",
2548   "%*s<placeholder name=\"%s\">\n",
2549   "%*s<popup name=\"%s\">\n",
2550   "%*s<menuitem name=\"%s\" action=\"%s\"/>\n", 
2551   "%*s<toolitem name=\"%s\" action=\"%s\"/>\n", 
2552   "%*s<separator name=\"%s\"/>\n",
2553   "%*s<accelerator name=\"%s\" action=\"%s\"/>\n",
2554 };
2555
2556 static const gchar *close_tag_format[] = {
2557   "%*s</UNDECIDED>\n",
2558   "%*s</ui>\n",
2559   "%*s</menubar>\n",
2560   "%*s</menu>\n",
2561   "%*s</toolbar>\n",
2562   "%*s</placeholder>\n",
2563   "%*s</placeholder>\n",
2564   "%*s</popup>\n",
2565   "",
2566   "",
2567   "",
2568   "",
2569 };
2570
2571 static void
2572 print_node (GtkUIManager *self, 
2573             GNode        *node, 
2574             gint          indent_level,
2575             GString      *buffer)
2576 {
2577   Node *mnode;
2578   GNode *child;
2579
2580   mnode = node->data;
2581
2582   g_string_append_printf (buffer, open_tag_format[mnode->type],
2583                           indent_level, "",
2584                           mnode->name, 
2585                           g_quark_to_string (mnode->action_name));
2586
2587   for (child = node->children; child != NULL; child = child->next) 
2588     print_node (self, child, indent_level + 2, buffer);
2589
2590   g_string_append_printf (buffer, close_tag_format[mnode->type],
2591                           indent_level, "");
2592
2593 }
2594
2595 /**
2596  * gtk_ui_manager_get_ui:
2597  * @self: a #GtkUIManager
2598  * 
2599  * Creates a <link linkend="XML-UI">UI definition</link> of the merged UI.
2600  * 
2601  * Return value: A newly allocated string containing an XML representation of 
2602  * the merged UI.
2603  *
2604  * Since: 2.4
2605  **/
2606 gchar *
2607 gtk_ui_manager_get_ui (GtkUIManager *self)
2608 {
2609   GString *buffer;
2610
2611   buffer = g_string_new (NULL);
2612
2613   gtk_ui_manager_ensure_update (self); 
2614  
2615   print_node (self, self->private_data->root_node, 0, buffer);  
2616
2617   return g_string_free (buffer, FALSE);
2618 }
2619