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