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