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