]> Pileus Git - ~andy/gtk/blob - gtk/gtkuimanager.c
Fixes for (#124212, Marco Pesenti Gritti):
[~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 FALSE;
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           if (GTK_IS_MENU_ITEM (item))
1776             _gtk_action_sync_menu_visible (NULL, item, empty);
1777           if (GTK_IS_WIDGET (filler))
1778             g_object_set (G_OBJECT (filler), "visible", empty, NULL);
1779         }
1780     }
1781 }
1782
1783 static void
1784 update_node (GtkUIManager *self, 
1785              GNode        *node,
1786              gboolean      add_tearoffs)
1787 {
1788   Node *info;
1789   GNode *child;
1790   GtkAction *action;
1791   gchar *tooltip;
1792 #ifdef DEBUG_UI_MANAGER
1793   GList *tmp;
1794 #endif
1795   
1796   g_return_if_fail (node != NULL);
1797   g_return_if_fail (NODE_INFO (node) != NULL);
1798
1799   info = NODE_INFO (node);
1800
1801 #ifdef DEBUG_UI_MANAGER
1802   g_print ("update_node name=%s dirty=%d (", info->name, info->dirty);
1803   for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next)
1804     {
1805       NodeUIReference *ref = tmp->data;
1806       g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id);
1807       if (tmp->next)
1808         g_print (", ");
1809     }
1810   g_print (")\n");
1811 #endif
1812
1813   if (info->dirty)
1814     {
1815       const gchar *action_name;
1816       NodeUIReference *ref;
1817
1818       if (info->uifiles == NULL) {
1819         /* We may need to remove this node.
1820          * This must be done in post order
1821          */
1822         goto recurse_children;
1823       }
1824
1825       ref = info->uifiles->data;
1826       action_name = g_quark_to_string (ref->action_quark);
1827       action = get_action_by_name (self, action_name);
1828
1829       info->dirty = FALSE;
1830
1831       /* Check if the node doesn't have an action and must have an action */
1832       if (action == NULL &&
1833           info->type != NODE_TYPE_ROOT &&
1834           info->type != NODE_TYPE_MENUBAR &&
1835           info->type != NODE_TYPE_TOOLBAR &&
1836           info->type != NODE_TYPE_POPUP &&
1837           info->type != NODE_TYPE_SEPARATOR &&
1838           info->type != NODE_TYPE_MENU_PLACEHOLDER &&
1839           info->type != NODE_TYPE_TOOLBAR_PLACEHOLDER)
1840         {
1841           g_warning ("%s: missing action", info->name);
1842
1843           goto recurse_children;
1844         }
1845
1846       if (action)
1847         gtk_action_set_accel_group (action, self->private_data->accel_group);
1848
1849       /* If the widget already has a proxy and the action hasn't changed, then
1850        * we only have to update the tearoff menu items.
1851        */
1852       if (info->proxy != NULL && action == info->action)
1853         {
1854           if (info->type == NODE_TYPE_MENU) 
1855             {
1856               GtkWidget *menu;
1857               GList *siblings;
1858
1859               menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
1860               siblings = gtk_container_get_children (GTK_CONTAINER (menu));
1861               if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1862                 g_object_set (G_OBJECT (siblings->data), "visible", add_tearoffs, 0);
1863             }
1864
1865           goto recurse_children;
1866         }
1867       
1868       switch (info->type)
1869         {
1870         case NODE_TYPE_MENUBAR:
1871           if (info->proxy == NULL)
1872             {
1873               info->proxy = gtk_menu_bar_new ();
1874               gtk_widget_show (info->proxy);
1875               g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
1876             }
1877           break;
1878         case NODE_TYPE_POPUP:
1879           if (info->proxy == NULL) 
1880             info->proxy = gtk_menu_new ();
1881           break;
1882         case NODE_TYPE_MENU:
1883           {
1884             GtkWidget *prev_submenu = NULL;
1885             GtkWidget *menu;
1886             GList *siblings;
1887             /* remove the proxy if it is of the wrong type ... */
1888             if (info->proxy &&  G_OBJECT_TYPE (info->proxy) !=
1889                 GTK_ACTION_GET_CLASS (action)->menu_item_type)
1890               {
1891                 prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
1892                 if (prev_submenu)
1893                   {
1894                     g_object_ref (prev_submenu);
1895                     gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
1896                   }
1897                 gtk_action_disconnect_proxy (info->action, info->proxy);
1898                 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1899                                       info->proxy);
1900                 info->proxy = NULL;
1901               }
1902             /* create proxy if needed ... */
1903             if (info->proxy == NULL)
1904               {
1905                 GtkWidget *menushell;
1906                 gint pos;
1907                 
1908                 if (find_menu_position (node, &menushell, &pos))
1909                   {
1910                     GtkWidget *tearoff;
1911                     GtkWidget *filler;
1912
1913                     info->proxy = gtk_action_create_menu_item (action);
1914                     menu = gtk_menu_new ();
1915                     tearoff = gtk_tearoff_menu_item_new ();
1916                     gtk_widget_set_no_show_all (tearoff, TRUE);
1917                     gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff);
1918                     filler = gtk_menu_item_new_with_label (_("Empty"));
1919                     g_object_set_data (G_OBJECT (filler),
1920                                        "gtk-empty-menu-item",
1921                                        GINT_TO_POINTER (TRUE));
1922                     gtk_widget_set_sensitive (filler, FALSE);
1923                     gtk_widget_set_no_show_all (filler, TRUE);
1924                     gtk_menu_shell_append (GTK_MENU_SHELL (menu), filler);
1925                     gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu);
1926                     gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos);
1927                   }
1928               }
1929             else
1930               gtk_action_connect_proxy (action, info->proxy);
1931
1932             if (prev_submenu)
1933               {
1934                 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),
1935                                            prev_submenu);
1936                 g_object_unref (prev_submenu);
1937               }
1938             menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
1939             siblings = gtk_container_get_children (GTK_CONTAINER (menu));
1940             if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1941               g_object_set (G_OBJECT (siblings->data), "visible", add_tearoffs, 0);
1942           }
1943           break;
1944         case NODE_TYPE_UNDECIDED:
1945           g_warning ("found 'undecided node!");
1946           break;
1947         case NODE_TYPE_ROOT:
1948           break;
1949         case NODE_TYPE_TOOLBAR:
1950           if (info->proxy == NULL)
1951             {
1952               info->proxy = gtk_toolbar_new ();
1953               gtk_widget_show (info->proxy);
1954               g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
1955             }
1956           break;
1957         case NODE_TYPE_MENU_PLACEHOLDER:
1958           /* create menu items for placeholders if necessary ... */
1959           if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) ||
1960               !GTK_IS_SEPARATOR_MENU_ITEM (info->extra))
1961             {
1962               if (info->proxy)
1963                 {
1964                   gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
1965                                         info->proxy);
1966                   info->proxy = NULL;
1967                 }
1968               if (info->extra)
1969                 {
1970                   gtk_container_remove (GTK_CONTAINER (info->extra->parent),
1971                                         info->extra);
1972                   info->extra = NULL;
1973                 }
1974             }
1975           if (info->proxy == NULL)
1976             {
1977               GtkWidget *menushell;
1978               gint pos;
1979
1980               if (find_menu_position (node, &menushell, &pos))
1981                 {
1982                   info->proxy = gtk_separator_menu_item_new ();
1983                   g_object_set_data (G_OBJECT (info->proxy),
1984                                      "gtk-separator-mode",
1985                                      GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
1986                   gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
1987                                         NODE_INFO (node)->proxy, pos);
1988
1989                   info->extra = gtk_separator_menu_item_new ();
1990                   g_object_set_data (G_OBJECT (info->extra),
1991                                      "gtk-separator-mode",
1992                                      GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
1993                   gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
1994                                          NODE_INFO (node)->extra, pos+1);
1995                 }
1996             }
1997           break;
1998         case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1999           /* create toolbar items for placeholders if necessary ... */
2000           if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) ||
2001               !GTK_IS_SEPARATOR_TOOL_ITEM (info->extra))
2002             {
2003               if (info->proxy)
2004                 {
2005                   gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2006                                         info->proxy);
2007                   info->proxy = NULL;
2008                 }
2009               if (info->extra)
2010                 {
2011                   gtk_container_remove (GTK_CONTAINER (info->extra->parent),
2012                                         info->extra);
2013                   info->extra = NULL;
2014                 }
2015             }
2016           if (info->proxy == NULL)
2017             {
2018               GtkWidget *toolbar;
2019               gint pos;
2020
2021               if (find_toolbar_position (node, &toolbar, &pos))
2022                 {
2023                   GtkToolItem *item;
2024
2025                   item = gtk_separator_tool_item_new ();
2026                   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2027                   info->proxy = GTK_WIDGET (item);
2028                   g_object_set_data (G_OBJECT (info->proxy),
2029                                      "gtk-separator-mode",
2030                                      GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2031
2032                   item = gtk_separator_tool_item_new ();
2033                   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1);
2034                   info->extra = GTK_WIDGET (item);
2035                   g_object_set_data (G_OBJECT (info->extra),
2036                                      "gtk-separator-mode",
2037                                      GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2038                 }
2039             }
2040           break;
2041         case NODE_TYPE_MENUITEM:
2042           /* remove the proxy if it is of the wrong type ... */
2043           if (info->proxy &&  G_OBJECT_TYPE (info->proxy) !=
2044               GTK_ACTION_GET_CLASS (action)->menu_item_type)
2045             {
2046               g_signal_handlers_disconnect_by_func (info->proxy,
2047                                                     G_CALLBACK (update_smart_separators),
2048                                                     0);  
2049               gtk_action_disconnect_proxy (info->action, info->proxy);
2050               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2051                                     info->proxy);
2052               info->proxy = NULL;
2053             }
2054           /* create proxy if needed ... */
2055           if (info->proxy == NULL)
2056             {
2057               GtkWidget *menushell;
2058               gint pos;
2059
2060               if (find_menu_position (node, &menushell, &pos))
2061                 {
2062                   info->proxy = gtk_action_create_menu_item (action);
2063                   
2064                   gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2065                                          info->proxy, pos);
2066                 }
2067             }
2068           else
2069             {
2070               g_signal_handlers_disconnect_by_func (info->proxy,
2071                                                     G_CALLBACK (update_smart_separators),
2072                                                     0);
2073               gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
2074               gtk_action_connect_proxy (action, info->proxy);
2075             }
2076           g_signal_connect (info->proxy, "notify::visible",
2077                             G_CALLBACK (update_smart_separators), 0);
2078           break;
2079         case NODE_TYPE_TOOLITEM:
2080           /* remove the proxy if it is of the wrong type ... */
2081           if (info->proxy &&  G_OBJECT_TYPE (info->proxy) !=
2082               GTK_ACTION_GET_CLASS (action)->toolbar_item_type)
2083             {
2084               g_signal_handlers_disconnect_by_func (info->proxy,
2085                                                     G_CALLBACK (update_smart_separators),
2086                                                     0);
2087               gtk_action_disconnect_proxy (info->action, info->proxy);
2088               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2089                                     info->proxy);
2090               info->proxy = NULL;
2091             }
2092           /* create proxy if needed ... */
2093           if (info->proxy == NULL)
2094             {
2095               GtkWidget *toolbar;
2096               gint pos;
2097
2098               if (find_toolbar_position (node, &toolbar, &pos))
2099                 {
2100                   info->proxy = gtk_action_create_tool_item (action);
2101
2102                   gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
2103                                       GTK_TOOL_ITEM (info->proxy), pos);
2104
2105                  /* FIXME: we must trigger the notify::tooltip handler, since 
2106                   * tooltips on toolitems can't be set before the toolitem 
2107                   * is added to the toolbar.
2108                   */
2109                   g_object_get (G_OBJECT (action), "tooltip", &tooltip, NULL);
2110                   g_object_set (G_OBJECT (action), "tooltip", tooltip, NULL);
2111                   g_free (tooltip);
2112                 }
2113             }
2114           else
2115             {
2116               g_signal_handlers_disconnect_by_func (info->proxy,
2117                                                     G_CALLBACK (update_smart_separators),
2118                                                     0);
2119               gtk_action_connect_proxy (action, info->proxy);
2120             }
2121           g_signal_connect (info->proxy, "notify::visible",
2122                             G_CALLBACK (update_smart_separators), 0);
2123           break;
2124         case NODE_TYPE_SEPARATOR:
2125           if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR ||
2126               NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
2127             {
2128               GtkWidget *toolbar;
2129               gint pos;
2130
2131               if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy))
2132                 {
2133                   gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2134                                         info->proxy);
2135                   info->proxy = NULL;
2136                 }
2137
2138               if (find_toolbar_position (node, &toolbar, &pos))
2139                 {
2140                   GtkToolItem *item = gtk_separator_tool_item_new ();
2141                   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2142                   info->proxy = GTK_WIDGET (item);
2143                   gtk_widget_set_no_show_all (info->proxy, TRUE);
2144                   g_object_set_data (G_OBJECT (info->proxy),
2145                                      "gtk-separator-mode",
2146                                      GINT_TO_POINTER (SEPARATOR_MODE_SMART));
2147                   gtk_widget_show (info->proxy);
2148                 }
2149             }
2150           else
2151             {
2152               GtkWidget *menushell;
2153               gint pos;
2154
2155               if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy))
2156                 {
2157                   gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2158                                         info->proxy);
2159                   info->proxy = NULL;
2160                 }
2161
2162               if (find_menu_position (node, &menushell, &pos))
2163                 {
2164                   info->proxy = gtk_separator_menu_item_new ();
2165                   gtk_widget_set_no_show_all (info->proxy, TRUE);
2166                   g_object_set_data (G_OBJECT (info->proxy),
2167                                      "gtk-separator-mode",
2168                                      GINT_TO_POINTER (SEPARATOR_MODE_SMART));
2169                   gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2170                                          info->proxy, pos);
2171                   gtk_widget_show (info->proxy);
2172                 }
2173             }
2174           break;
2175         case NODE_TYPE_ACCELERATOR:
2176           gtk_action_connect_accelerator (action);
2177           break;
2178         }
2179
2180       if (action)
2181         g_object_ref (action);
2182       if (info->action)
2183         g_object_unref (info->action);
2184       info->action = action;
2185     }
2186
2187  recurse_children:
2188   /* process children */
2189   child = node->children;
2190   while (child)
2191     {
2192       GNode *current;
2193
2194       current = child;
2195       child = current->next;
2196       update_node (self, current, add_tearoffs && (info->type != NODE_TYPE_POPUP));
2197     }
2198
2199   if (info->proxy) 
2200     {
2201       if (info->type == NODE_TYPE_MENU) 
2202         update_smart_separators (gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy)));
2203       else if (info->type == NODE_TYPE_TOOLBAR)
2204         update_smart_separators (info->proxy);
2205     }
2206
2207   /* handle cleanup of dead nodes */
2208   if (node->children == NULL && info->uifiles == NULL)
2209     {
2210       if (info->proxy)
2211         gtk_widget_destroy (info->proxy);
2212       if (info->extra)
2213         gtk_widget_destroy (info->extra);
2214       if (info->type == NODE_TYPE_ACCELERATOR)
2215         gtk_action_disconnect_accelerator (info->action);
2216       free_node (node);
2217       g_node_destroy (node);
2218     }
2219 }
2220
2221 static gboolean
2222 do_updates (GtkUIManager *self)
2223 {
2224   /* this function needs to check through the tree for dirty nodes.
2225    * For such nodes, it needs to do the following:
2226    *
2227    * 1) check if they are referenced by any loaded UI files anymore.
2228    *    In which case, the proxy widget should be destroyed, unless
2229    *    there are any subnodes.
2230    *
2231    * 2) lookup the action for this node again.  If it is different to
2232    *    the current one (or if no previous action has been looked up),
2233    *    the proxy is reconnected to the new action (or a new proxy widget
2234    *    is created and added to the parent container).
2235    */
2236   update_node (self, self->private_data->root_node, 
2237                self->private_data->add_tearoffs);
2238
2239   self->private_data->update_tag = 0;
2240
2241   return FALSE;
2242 }
2243
2244 static void
2245 queue_update (GtkUIManager *self)
2246 {
2247   if (self->private_data->update_tag != 0)
2248     return;
2249
2250   self->private_data->update_tag = g_idle_add ((GSourceFunc)do_updates, self);
2251 }
2252
2253
2254 /**
2255  * gtk_ui_manager_ensure_update:
2256  * @self: a #GtkUIManager
2257  * 
2258  * Makes sure that all pending updates to the UI have been completed.
2259  *
2260  * This may occasionally be necessary, since #GtkUIManager updates the 
2261  * UI in an idle function. A typical example where this function is
2262  * useful is to enforce that the menubar and toolbar have been added to 
2263  * the main window before showing it:
2264  * <informalexample>
2265  * <programlisting>
2266  * gtk_container_add (GTK_CONTAINER (window), vbox); 
2267  * g_signal_connect (merge, "add_widget", 
2268  *                   G_CALLBACK (add_widget), vbox);
2269  * gtk_ui_manager_add_ui_from_file (merge, "my-menus");
2270  * gtk_ui_manager_add_ui_from_file (merge, "my-toolbars");
2271  * gtk_ui_manager_ensure_update (merge);  
2272  * gtk_widget_show (window);
2273  * </programlisting>
2274  * </informalexample>
2275  *
2276  * Since: 2.4
2277  **/
2278 void
2279 gtk_ui_manager_ensure_update (GtkUIManager *self)
2280 {
2281   if (self->private_data->update_tag != 0)
2282     {
2283       g_source_remove (self->private_data->update_tag);
2284       do_updates (self);
2285     }
2286 }
2287
2288 static gboolean
2289 dirty_traverse_func (GNode   *node, 
2290                      gpointer data)
2291 {
2292   NODE_INFO (node)->dirty = TRUE;
2293   return FALSE;
2294 }
2295
2296 static void
2297 dirty_all_nodes (GtkUIManager *self)
2298 {
2299   g_node_traverse (self->private_data->root_node, 
2300                    G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2301                    dirty_traverse_func, NULL);
2302   queue_update (self);
2303 }
2304
2305 static const gchar *open_tag_format[] = {
2306   "%*s<UNDECIDED>\n",
2307   "%*s<ui>\n",
2308   "%*s<menubar name=\"%s\">\n",  
2309   "%*s<menu name=\"%s\" action=\"%s\">\n",
2310   "%*s<toolbar name=\"%s\">\n",
2311   "%*s<placeholder name=\"%s\">\n",
2312   "%*s<placeholder name=\"%s\">\n",
2313   "%*s<popup name=\"%s\">\n",
2314   "%*s<menuitem name=\"%s\" action=\"%s\"/>\n", 
2315   "%*s<toolitem name=\"%s\" action=\"%s\"/>\n", 
2316   "%*s<separator name=\"%s\"/>\n",
2317   "%*s<accelerator name=\"%s\" action=\"%s\"/>\n",
2318 };
2319
2320 static const gchar *close_tag_format[] = {
2321   "%*s</UNDECIDED>\n",
2322   "%*s</ui>\n",
2323   "%*s</menubar>\n",
2324   "%*s</menu>\n",
2325   "%*s</toolbar>\n",
2326   "%*s</placeholder>\n",
2327   "%*s</placeholder>\n",
2328   "%*s</popup>\n",
2329   "",
2330   "",
2331   "",
2332   "",
2333 };
2334
2335 static void
2336 print_node (GtkUIManager *self, 
2337             GNode        *node, 
2338             gint          indent_level,
2339             GString      *buffer)
2340 {
2341   Node *mnode;
2342   GNode *child;
2343
2344   mnode = node->data;
2345
2346   g_string_append_printf (buffer, open_tag_format[mnode->type],
2347                           indent_level, "",
2348                           mnode->name, 
2349                           g_quark_to_string (mnode->action_name));
2350
2351   for (child = node->children; child != NULL; child = child->next) 
2352     print_node (self, child, indent_level + 2, buffer);
2353
2354   g_string_append_printf (buffer, close_tag_format[mnode->type],
2355                           indent_level, "");
2356
2357 }
2358
2359 /**
2360  * gtk_ui_manager_get_ui:
2361  * @self: a #GtkUIManager
2362  * 
2363  * Creates a <link linkend="XML-UI">UI definition</link> of the merged UI.
2364  * 
2365  * Return value: A newly allocated string containing an XML representation of 
2366  * the merged UI.
2367  *
2368  * Since: 2.4
2369  **/
2370 gchar *
2371 gtk_ui_manager_get_ui (GtkUIManager *self)
2372 {
2373   GString *buffer;
2374
2375   buffer = g_string_new (NULL);
2376
2377   gtk_ui_manager_ensure_update (self); 
2378  
2379   print_node (self, self->private_data->root_node, 0, buffer);  
2380
2381   return g_string_free (buffer, FALSE);
2382 }
2383