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