]> Pileus Git - ~andy/gtk/blob - gtk/gtkuimanager.c
deprecated floating/sink API and implemented it in terms of the GObject
[~andy/gtk] / gtk / gtkuimanager.c
1 /*
2  * GTK - The GIMP Toolkit
3  * Copyright (C) 1998, 1999 Red Hat, Inc.
4  * All rights reserved.
5  *
6  * This Library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This Library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
18  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 /*
23  * Author: James Henstridge <james@daa.com.au>
24  *
25  * Modified by the GTK+ Team and others 2003.  See the AUTHORS
26  * file for a list of people on the GTK+ Team.  See the ChangeLog
27  * files for a list of changes.  These files are distributed with
28  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
29  */
30
31 #include <config.h>
32
33 #include <string.h>
34 #include "gtkintl.h"
35 #include "gtkmarshalers.h"
36 #include "gtkmenu.h"
37 #include "gtkmenubar.h"
38 #include "gtkmenushell.h"
39 #include "gtkmenutoolbutton.h"
40 #include "gtkseparatormenuitem.h"
41 #include "gtkseparatortoolitem.h"
42 #include "gtktearoffmenuitem.h"
43 #include "gtktoolbar.h"
44 #include "gtkuimanager.h"
45 #include "gtkprivate.h"
46 #include "gtkalias.h"
47
48 #undef DEBUG_UI_MANAGER
49
50 typedef enum 
51 {
52   NODE_TYPE_UNDECIDED,
53   NODE_TYPE_ROOT,
54   NODE_TYPE_MENUBAR,
55   NODE_TYPE_MENU,
56   NODE_TYPE_TOOLBAR,
57   NODE_TYPE_MENU_PLACEHOLDER,
58   NODE_TYPE_TOOLBAR_PLACEHOLDER,
59   NODE_TYPE_POPUP,
60   NODE_TYPE_MENUITEM,
61   NODE_TYPE_TOOLITEM,
62   NODE_TYPE_SEPARATOR,
63   NODE_TYPE_ACCELERATOR
64 } NodeType;
65
66 typedef struct _Node Node;
67
68 struct _Node {
69   NodeType type;
70
71   gchar *name;
72
73   GQuark action_name;
74   GtkAction *action;
75   GtkWidget *proxy;
76   GtkWidget *extra; /* second separator for placeholders */
77
78   GList *uifiles;
79
80   guint dirty : 1;
81   guint expand : 1;  /* used for separators */
82 };
83
84 #define GTK_UI_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_UI_MANAGER, GtkUIManagerPrivate))
85
86 struct _GtkUIManagerPrivate 
87 {
88   GtkAccelGroup *accel_group;
89
90   GNode *root_node;
91   GList *action_groups;
92
93   guint last_merge_id;
94
95   guint update_tag;  
96
97   gboolean add_tearoffs;
98 };
99
100 #define NODE_INFO(node) ((Node *)node->data)
101
102 typedef struct _NodeUIReference NodeUIReference;
103
104 struct _NodeUIReference 
105 {
106   guint merge_id;
107   GQuark action_quark;
108 };
109
110 static void        gtk_ui_manager_class_init      (GtkUIManagerClass *class);
111 static void        gtk_ui_manager_init            (GtkUIManager      *self);
112 static void        gtk_ui_manager_finalize        (GObject           *object);
113 static void        gtk_ui_manager_set_property    (GObject           *object,
114                                                    guint              prop_id,
115                                                    const GValue      *value,
116                                                    GParamSpec        *pspec);
117 static void        gtk_ui_manager_get_property    (GObject           *object,
118                                                    guint              prop_id,
119                                                    GValue            *value,
120                                                    GParamSpec        *pspec);
121 static GtkWidget * gtk_ui_manager_real_get_widget (GtkUIManager      *manager,
122                                                    const gchar       *path);
123 static GtkAction * gtk_ui_manager_real_get_action (GtkUIManager      *manager,
124                                                    const gchar       *path);
125 static void        queue_update                   (GtkUIManager      *self);
126 static void        dirty_all_nodes                (GtkUIManager      *self);
127 static void        mark_node_dirty                (GNode             *node);
128 static GNode     * get_child_node                 (GtkUIManager      *self,
129                                                    GNode             *parent,
130                                                    GNode             *sibling,
131                                                    const gchar       *childname,
132                                                    gint               childname_length,
133                                                    NodeType           node_type,
134                                                    gboolean           create,
135                                                    gboolean           top);
136 static GNode     * get_node                       (GtkUIManager      *self,
137                                                    const gchar       *path,
138                                                    NodeType           node_type,
139                                                    gboolean           create);
140 static gboolean    free_node                      (GNode             *node);
141 static void        node_prepend_ui_reference      (GNode             *node,
142                                                    guint              merge_id,
143                                                    GQuark             action_quark);
144 static void        node_remove_ui_reference       (GNode             *node,
145                                                    guint              merge_id);
146
147
148 enum 
149 {
150   ADD_WIDGET,
151   ACTIONS_CHANGED,
152   CONNECT_PROXY,
153   DISCONNECT_PROXY,
154   PRE_ACTIVATE,
155   POST_ACTIVATE,
156   LAST_SIGNAL
157 };
158
159 enum
160 {
161   PROP_0,
162   PROP_ADD_TEAROFFS,
163   PROP_UI
164 };
165
166 static GObjectClass *parent_class = NULL;
167 static guint ui_manager_signals[LAST_SIGNAL] = { 0 };
168
169 static GMemChunk *merge_node_chunk = NULL;
170
171 GType
172 gtk_ui_manager_get_type (void)
173 {
174   static GtkType type = 0;
175
176   if (!type)
177     {
178       static const GTypeInfo type_info =
179       {
180         sizeof (GtkUIManagerClass),
181         (GBaseInitFunc) NULL,
182         (GBaseFinalizeFunc) NULL,
183         (GClassInitFunc) gtk_ui_manager_class_init,
184         (GClassFinalizeFunc) NULL,
185         NULL,
186         
187         sizeof (GtkUIManager),
188         0, /* n_preallocs */
189         (GInstanceInitFunc) gtk_ui_manager_init,
190       };
191
192       type = g_type_register_static (G_TYPE_OBJECT,
193                                      I_("GtkUIManager"),
194                                      &type_info, 0);
195     }
196   return type;
197 }
198
199 static void
200 gtk_ui_manager_class_init (GtkUIManagerClass *klass)
201 {
202   GObjectClass *gobject_class;
203
204   parent_class = g_type_class_peek_parent (klass);
205
206   gobject_class = G_OBJECT_CLASS (klass);
207
208   if (!merge_node_chunk)
209     merge_node_chunk = g_mem_chunk_create (Node, 64,
210                                            G_ALLOC_AND_FREE);
211
212   gobject_class->finalize = gtk_ui_manager_finalize;
213   gobject_class->set_property = gtk_ui_manager_set_property;
214   gobject_class->get_property = gtk_ui_manager_get_property;
215   klass->get_widget = gtk_ui_manager_real_get_widget;
216   klass->get_action = gtk_ui_manager_real_get_action;
217
218   /**
219    * GtkUIManager:add-tearoffs:
220    *
221    * The "add-tearoffs" property controls whether generated menus 
222    * have tearoff menu items. 
223    *
224    * Note that this only affects regular menus. Generated popup 
225    * menus never have tearoff menu items.   
226    *
227    * Since: 2.4
228    */
229   g_object_class_install_property (gobject_class,
230                                    PROP_ADD_TEAROFFS,
231                                    g_param_spec_boolean ("add-tearoffs",
232                                                          P_("Add tearoffs to menus"),
233                                                          P_("Whether tearoff menu items should be added to menus"),
234                                                          FALSE,
235                                                          GTK_PARAM_READWRITE));
236
237   g_object_class_install_property (gobject_class,
238                                    PROP_UI,
239                                    g_param_spec_string ("ui",
240                                                         P_("Merged UI definition"),
241                                                         P_("An XML string describing the merged UI"),
242                                                         "<ui>\n</ui>\n",
243                                                         GTK_PARAM_READABLE));
244
245
246   /**
247    * GtkUIManager::add-widget:
248    * @merge: a #GtkUIManager
249    * @widget: the added widget
250    *
251    * The add_widget signal is emitted for each generated menubar and toolbar.
252    * It is not emitted for generated popup menus, which can be obtained by 
253    * gtk_ui_manager_get_widget().
254    *
255    * Since: 2.4
256    */
257   ui_manager_signals[ADD_WIDGET] =
258     g_signal_new (I_("add_widget"),
259                   G_OBJECT_CLASS_TYPE (klass),
260                   G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
261                   G_STRUCT_OFFSET (GtkUIManagerClass, add_widget),
262                   NULL, NULL,
263                   g_cclosure_marshal_VOID__OBJECT,
264                   G_TYPE_NONE, 1, 
265                   GTK_TYPE_WIDGET);
266
267   /**
268    * GtkUIManager::actions-changed:
269    * @merge: a #GtkUIManager
270    *
271    * The "actions-changed" signal is emitted whenever the set of actions
272    * changes.
273    *
274    * Since: 2.4
275    */
276   ui_manager_signals[ACTIONS_CHANGED] =
277     g_signal_new (I_("actions_changed"),
278                   G_OBJECT_CLASS_TYPE (klass),
279                   G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
280                   G_STRUCT_OFFSET (GtkUIManagerClass, actions_changed),  
281                   NULL, NULL,
282                   g_cclosure_marshal_VOID__VOID,
283                   G_TYPE_NONE, 0);
284   
285   /**
286    * GtkUIManager::connect-proxy:
287    * @uimanager: the ui manager
288    * @action: the action
289    * @proxy: the proxy
290    *
291    * The connect_proxy signal is emitted after connecting a proxy to 
292    * an action in the group. 
293    *
294    * This is intended for simple customizations for which a custom action
295    * class would be too clumsy, e.g. showing tooltips for menuitems in the
296    * statusbar.
297    *
298    * Since: 2.4
299    */
300   ui_manager_signals[CONNECT_PROXY] =
301     g_signal_new (I_("connect_proxy"),
302                   G_OBJECT_CLASS_TYPE (klass),
303                   G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
304                   G_STRUCT_OFFSET (GtkUIManagerClass, connect_proxy),
305                   NULL, NULL,
306                   _gtk_marshal_VOID__OBJECT_OBJECT,
307                   G_TYPE_NONE, 2, 
308                   GTK_TYPE_ACTION,
309                   GTK_TYPE_WIDGET);
310
311   /**
312    * GtkUIManager::disconnect-proxy:
313    * @uimanager: the ui manager
314    * @action: the action
315    * @proxy: the proxy
316    *
317    * The disconnect_proxy signal is emitted after disconnecting a proxy 
318    * from an action in the group. 
319    *
320    * Since: 2.4
321    */
322   ui_manager_signals[DISCONNECT_PROXY] =
323     g_signal_new (I_("disconnect_proxy"),
324                   G_OBJECT_CLASS_TYPE (klass),
325                   G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
326                   G_STRUCT_OFFSET (GtkUIManagerClass, disconnect_proxy),
327                   NULL, NULL,
328                   _gtk_marshal_VOID__OBJECT_OBJECT,
329                   G_TYPE_NONE, 2,
330                   GTK_TYPE_ACTION,
331                   GTK_TYPE_WIDGET);
332
333   /**
334    * GtkUIManager::pre-activate:
335    * @uimanager: the ui manager
336    * @action: the action
337    *
338    * The pre_activate signal is emitted just before the @action
339    * is activated.
340    *
341    * This is intended for applications to get notification
342    * just before any action is activated.
343    *
344    * Since: 2.4
345    */
346   ui_manager_signals[PRE_ACTIVATE] =
347     g_signal_new (I_("pre_activate"),
348                   G_OBJECT_CLASS_TYPE (klass),
349                   G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
350                   G_STRUCT_OFFSET (GtkUIManagerClass, pre_activate),
351                   NULL, NULL,
352                   _gtk_marshal_VOID__OBJECT,
353                   G_TYPE_NONE, 1,
354                   GTK_TYPE_ACTION);
355
356   /**
357    * GtkUIManager::post-activate:
358    * @uimanager: the ui manager
359    * @action: the action
360    *
361    * The post_activate signal is emitted just after the @action
362    * is activated.
363    *
364    * This is intended for applications to get notification
365    * just after any action is activated.
366    *
367    * Since: 2.4
368    */
369   ui_manager_signals[POST_ACTIVATE] =
370     g_signal_new (I_("post_activate"),
371                   G_OBJECT_CLASS_TYPE (klass),
372                   G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
373                   G_STRUCT_OFFSET (GtkUIManagerClass, post_activate),
374                   NULL, NULL,
375                   _gtk_marshal_VOID__OBJECT,
376                   G_TYPE_NONE, 1,
377                   GTK_TYPE_ACTION);
378
379   klass->add_widget = NULL;
380   klass->actions_changed = NULL;
381   klass->connect_proxy = NULL;
382   klass->disconnect_proxy = NULL;
383   klass->pre_activate = NULL;
384   klass->post_activate = NULL;
385
386   g_type_class_add_private (gobject_class, sizeof (GtkUIManagerPrivate));
387 }
388
389
390 static void
391 gtk_ui_manager_init (GtkUIManager *self)
392 {
393   guint merge_id;
394   GNode *node;
395
396   self->private_data = GTK_UI_MANAGER_GET_PRIVATE (self);
397
398   self->private_data->accel_group = gtk_accel_group_new ();
399
400   self->private_data->root_node = NULL;
401   self->private_data->action_groups = NULL;
402
403   self->private_data->last_merge_id = 0;
404   self->private_data->add_tearoffs = FALSE;
405
406   merge_id = gtk_ui_manager_new_merge_id (self);
407   node = get_child_node (self, NULL, NULL, "ui", 2,
408                          NODE_TYPE_ROOT, TRUE, FALSE);
409   node_prepend_ui_reference (node, merge_id, 0);
410 }
411
412 static void
413 gtk_ui_manager_finalize (GObject *object)
414 {
415   GtkUIManager *self = GTK_UI_MANAGER (object);
416   
417   if (self->private_data->update_tag != 0)
418     {
419       g_source_remove (self->private_data->update_tag);
420       self->private_data->update_tag = 0;
421     }
422   
423   g_node_traverse (self->private_data->root_node, 
424                    G_POST_ORDER, G_TRAVERSE_ALL, -1,
425                    (GNodeTraverseFunc)free_node, NULL);
426   g_node_destroy (self->private_data->root_node);
427   self->private_data->root_node = NULL;
428   
429   g_list_foreach (self->private_data->action_groups,
430                   (GFunc) g_object_unref, NULL);
431   g_list_free (self->private_data->action_groups);
432   self->private_data->action_groups = NULL;
433
434   g_object_unref (self->private_data->accel_group);
435   self->private_data->accel_group = NULL;
436
437   G_OBJECT_CLASS (parent_class)->finalize (object);
438 }
439
440 static void
441 gtk_ui_manager_set_property (GObject         *object,
442                              guint            prop_id,
443                              const GValue    *value,
444                              GParamSpec      *pspec)
445 {
446   GtkUIManager *self = GTK_UI_MANAGER (object);
447  
448   switch (prop_id)
449     {
450     case PROP_ADD_TEAROFFS:
451       gtk_ui_manager_set_add_tearoffs (self, g_value_get_boolean (value));
452       break;
453     default:
454       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
455       break;
456     }
457 }
458
459 static void
460 gtk_ui_manager_get_property (GObject         *object,
461                              guint            prop_id,
462                              GValue          *value,
463                              GParamSpec      *pspec)
464 {
465   GtkUIManager *self = GTK_UI_MANAGER (object);
466
467   switch (prop_id)
468     {
469     case PROP_ADD_TEAROFFS:
470       g_value_set_boolean (value, self->private_data->add_tearoffs);
471       break;
472     case PROP_UI:
473       g_value_set_string (value, gtk_ui_manager_get_ui (self));
474       break;
475     default:
476       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
477       break;
478     }
479 }
480
481 static GtkWidget *
482 gtk_ui_manager_real_get_widget (GtkUIManager *self,
483                                 const gchar  *path)
484 {
485   GNode *node;
486
487   /* ensure that there are no pending updates before we get the
488    * widget */
489   gtk_ui_manager_ensure_update (self);
490
491   node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
492
493   if (node == NULL)
494     return NULL;
495
496   return NODE_INFO (node)->proxy;
497 }
498
499 static GtkAction *
500 gtk_ui_manager_real_get_action (GtkUIManager *self,
501                                 const gchar  *path)
502 {
503   GNode *node;
504
505   /* ensure that there are no pending updates before we get
506    * the action */
507   gtk_ui_manager_ensure_update (self);
508
509   node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
510
511   if (node == NULL)
512     return NULL;
513
514   return NODE_INFO (node)->action;
515 }
516
517
518 /**
519  * gtk_ui_manager_new:
520  * 
521  * Creates a new ui manager object.
522  * 
523  * Return value: a new ui manager object.
524  *
525  * Since: 2.4
526  **/
527 GtkUIManager*
528 gtk_ui_manager_new (void)
529 {
530   return g_object_new (GTK_TYPE_UI_MANAGER, NULL);
531 }
532
533
534 /**
535  * gtk_ui_manager_get_add_tearoffs:
536  * @self: a #GtkUIManager
537  * 
538  * Returns whether menus generated by this #GtkUIManager
539  * will have tearoff menu items. 
540  * 
541  * Return value: whether tearoff menu items are added
542  *
543  * Since: 2.4
544  **/
545 gboolean 
546 gtk_ui_manager_get_add_tearoffs (GtkUIManager *self)
547 {
548   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), FALSE);
549   
550   return self->private_data->add_tearoffs;
551 }
552
553
554 /**
555  * gtk_ui_manager_set_add_tearoffs:
556  * @self: a #GtkUIManager
557  * @add_tearoffs: whether tearoff menu items are added
558  * 
559  * Sets the "add_tearoffs" property, which controls whether menus 
560  * generated by this #GtkUIManager will have tearoff menu items. 
561  *
562  * Note that this only affects regular menus. Generated popup 
563  * menus never have tearoff menu items.
564  *
565  * Since: 2.4
566  **/
567 void 
568 gtk_ui_manager_set_add_tearoffs (GtkUIManager *self,
569                                  gboolean      add_tearoffs)
570 {
571   g_return_if_fail (GTK_IS_UI_MANAGER (self));
572
573   add_tearoffs = add_tearoffs != FALSE;
574
575   if (add_tearoffs != self->private_data->add_tearoffs)
576     {
577       self->private_data->add_tearoffs = add_tearoffs;
578       
579       dirty_all_nodes (self);
580
581       g_object_notify (G_OBJECT (self), "add-tearoffs");
582     }
583 }
584
585 static void
586 cb_proxy_connect_proxy (GtkActionGroup *group, 
587                         GtkAction      *action,
588                         GtkWidget      *proxy, 
589                         GtkUIManager *self)
590 {
591   g_signal_emit (self, ui_manager_signals[CONNECT_PROXY], 0, action, proxy);
592 }
593
594 static void
595 cb_proxy_disconnect_proxy (GtkActionGroup *group, 
596                            GtkAction      *action,
597                            GtkWidget      *proxy, 
598                            GtkUIManager *self)
599 {
600   g_signal_emit (self, ui_manager_signals[DISCONNECT_PROXY], 0, action, proxy);
601 }
602
603 static void
604 cb_proxy_pre_activate (GtkActionGroup *group, 
605                        GtkAction      *action,
606                        GtkUIManager   *self)
607 {
608   g_signal_emit (self, ui_manager_signals[PRE_ACTIVATE], 0, action);
609 }
610
611 static void
612 cb_proxy_post_activate (GtkActionGroup *group, 
613                         GtkAction      *action,
614                         GtkUIManager   *self)
615 {
616   g_signal_emit (self, ui_manager_signals[POST_ACTIVATE], 0, action);
617 }
618
619 /**
620  * gtk_ui_manager_insert_action_group:
621  * @self: a #GtkUIManager object
622  * @action_group: the action group to be inserted
623  * @pos: the position at which the group will be inserted.
624  * 
625  * Inserts an action group into the list of action groups associated 
626  * with @self. Actions in earlier groups hide actions with the same 
627  * name in later groups. 
628  *
629  * Since: 2.4
630  **/
631 void
632 gtk_ui_manager_insert_action_group (GtkUIManager   *self,
633                                     GtkActionGroup *action_group, 
634                                     gint            pos)
635 {
636   g_return_if_fail (GTK_IS_UI_MANAGER (self));
637   g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
638   g_return_if_fail (g_list_find (self->private_data->action_groups, 
639                                  action_group) == NULL);
640
641   g_object_ref (action_group);
642   self->private_data->action_groups = 
643     g_list_insert (self->private_data->action_groups, action_group, pos);
644   g_object_connect (action_group,
645                     "object_signal::connect_proxy", G_CALLBACK (cb_proxy_connect_proxy), self,
646                     "object_signal::disconnect_proxy", G_CALLBACK (cb_proxy_disconnect_proxy), self,
647                     "object_signal::pre_activate", G_CALLBACK (cb_proxy_pre_activate), self,
648                     "object_signal::post_activate", G_CALLBACK (cb_proxy_post_activate), self,
649                     NULL);
650
651   /* dirty all nodes, as action bindings may change */
652   dirty_all_nodes (self);
653
654   g_signal_emit (self, ui_manager_signals[ACTIONS_CHANGED], 0);
655 }
656
657 /**
658  * gtk_ui_manager_remove_action_group:
659  * @self: a #GtkUIManager object
660  * @action_group: the action group to be removed
661  * 
662  * Removes an action group from the list of action groups associated 
663  * with @self.
664  *
665  * Since: 2.4
666  **/
667 void
668 gtk_ui_manager_remove_action_group (GtkUIManager   *self,
669                                     GtkActionGroup *action_group)
670 {
671   g_return_if_fail (GTK_IS_UI_MANAGER (self));
672   g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
673   g_return_if_fail (g_list_find (self->private_data->action_groups, 
674                                  action_group) != NULL);
675
676   self->private_data->action_groups =
677     g_list_remove (self->private_data->action_groups, action_group);
678
679   g_object_disconnect (action_group,
680                        "any_signal::connect_proxy", G_CALLBACK (cb_proxy_connect_proxy), self,
681                        "any_signal::disconnect_proxy", G_CALLBACK (cb_proxy_disconnect_proxy), self,
682                        "any_signal::pre_activate", G_CALLBACK (cb_proxy_pre_activate), self,
683                        "any_signal::post_activate", G_CALLBACK (cb_proxy_post_activate), self, 
684                        NULL);
685   g_object_unref (action_group);
686
687   /* dirty all nodes, as action bindings may change */
688   dirty_all_nodes (self);
689
690   g_signal_emit (self, ui_manager_signals[ACTIONS_CHANGED], 0);
691 }
692
693 /**
694  * gtk_ui_manager_get_action_groups:
695  * @self: a #GtkUIManager object
696  * 
697  * Returns the list of action groups associated with @self.
698  *
699  * Return value: a #GList of action groups. The list is owned by GTK+ 
700  *   and should not be modified.
701  *
702  * Since: 2.4
703  **/
704 GList *
705 gtk_ui_manager_get_action_groups (GtkUIManager *self)
706 {
707   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
708
709   return self->private_data->action_groups;
710 }
711
712 /**
713  * gtk_ui_manager_get_accel_group:
714  * @self: a #GtkUIManager object
715  * 
716  * Returns the #GtkAccelGroup associated with @self.
717  *
718  * Return value: the #GtkAccelGroup.
719  *
720  * Since: 2.4
721  **/
722 GtkAccelGroup *
723 gtk_ui_manager_get_accel_group (GtkUIManager *self)
724 {
725   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
726
727   return self->private_data->accel_group;
728 }
729
730 /**
731  * gtk_ui_manager_get_widget:
732  * @self: a #GtkUIManager
733  * @path: a path
734  * 
735  * Looks up a widget by following a path. 
736  * The path consists of the names specified in the XML description of the UI. 
737  * separated by '/'. Elements which don't have a name or action attribute in 
738  * the XML (e.g. &lt;popup&gt;) can be addressed by their XML element name 
739  * (e.g. "popup"). The root element ("/ui") can be omitted in the path.
740  *
741  * Note that the widget found by following a path that ends in a &lt;menu&gt;
742  * element is the menuitem to which the menu is attached, not the menu itself.
743  *
744  * Also note that the widgets constructed by a ui manager are not tied to 
745  * the lifecycle of the ui manager. If you add the widgets returned by this 
746  * function to some container or explicitly ref them, they will survive the
747  * destruction of the ui manager.
748  * 
749  * Return value: the widget found by following the path, or %NULL if no widget
750  *   was found.
751  *
752  * Since: 2.4
753  **/
754 GtkWidget *
755 gtk_ui_manager_get_widget (GtkUIManager *self,
756                            const gchar  *path)
757 {
758   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
759   g_return_val_if_fail (path != NULL, NULL);
760
761   return GTK_UI_MANAGER_GET_CLASS (self)->get_widget (self, path);
762 }
763
764 static void
765 collect_toplevels (GNode   *node, 
766                    gpointer user_data)
767 {
768   struct {
769     GtkUIManagerItemType types;
770     GSList *list;
771   } *data = user_data;
772
773   switch (NODE_INFO (node)->type) {
774   case NODE_TYPE_MENUBAR:
775     if (data->types & GTK_UI_MANAGER_MENUBAR)
776       data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
777     break;
778   case NODE_TYPE_TOOLBAR:
779     if (data->types & GTK_UI_MANAGER_TOOLBAR)
780       data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
781     break;
782   case NODE_TYPE_POPUP:
783     if (data->types & GTK_UI_MANAGER_POPUP)
784       data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
785     break;
786   default: ;
787   }
788 }
789
790 /**
791  * gtk_ui_manager_get_toplevels:
792  * @self: a #GtkUIManager
793  * @types: specifies the types of toplevel widgets to include. Allowed
794  *   types are #GTK_UI_MANAGER_MENUBAR, #GTK_UI_MANAGER_TOOLBAR and
795  *   #GTK_UI_MANAGER_POPUP.
796  * 
797  * Obtains a list of all toplevel widgets of the requested types.
798  * 
799  * Return value: a newly-allocated of all toplevel widgets of the requested 
800  * types. 
801  *
802  * Since: 2.4
803  **/
804 GSList *
805 gtk_ui_manager_get_toplevels (GtkUIManager         *self,
806                               GtkUIManagerItemType  types)
807 {
808   struct {
809     GtkUIManagerItemType types;
810     GSList *list;
811   } data;
812
813   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
814   g_return_val_if_fail ((~(GTK_UI_MANAGER_MENUBAR | 
815                            GTK_UI_MANAGER_TOOLBAR |
816                            GTK_UI_MANAGER_POPUP) & types) == 0, NULL);
817   
818       
819   data.types = types;
820   data.list = NULL;
821
822   g_node_children_foreach (self->private_data->root_node, 
823                            G_TRAVERSE_ALL, 
824                            collect_toplevels, &data);
825
826   return data.list;
827 }
828
829
830 /**
831  * gtk_ui_manager_get_action:
832  * @self: a #GtkUIManager
833  * @path: a path
834  * 
835  * Looks up an action by following a path. See gtk_ui_manager_get_widget()
836  * for more information about paths.
837  * 
838  * Return value: the action whose proxy widget is found by following the path, 
839  *     or %NULL if no widget was found.
840  *
841  * Since: 2.4
842  **/
843 GtkAction *
844 gtk_ui_manager_get_action (GtkUIManager *self,
845                            const gchar  *path)
846 {
847   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
848   g_return_val_if_fail (path != NULL, NULL);
849
850   return GTK_UI_MANAGER_GET_CLASS (self)->get_action (self, path);
851 }
852
853 static GNode *
854 get_child_node (GtkUIManager *self, 
855                 GNode        *parent,
856                 GNode        *sibling,
857                 const gchar  *childname, 
858                 gint          childname_length,
859                 NodeType      node_type,
860                 gboolean      create, 
861                 gboolean      top)
862 {
863   GNode *child = NULL;
864
865   if (parent)
866     {
867       if (childname)
868         {
869           for (child = parent->children; child != NULL; child = child->next)
870             {
871               if (NODE_INFO (child)->name &&
872                   strlen (NODE_INFO (child)->name) == childname_length &&
873                   !strncmp (NODE_INFO (child)->name, childname, childname_length))
874                 {
875                   /* if undecided about node type, set it */
876                   if (NODE_INFO (child)->type == NODE_TYPE_UNDECIDED)
877                     NODE_INFO (child)->type = node_type;
878                   
879                   /* warn about type mismatch */
880                   if (NODE_INFO (child)->type != NODE_TYPE_UNDECIDED &&
881                       node_type != NODE_TYPE_UNDECIDED &&
882                       NODE_INFO (child)->type != node_type)
883                     g_warning ("node type doesn't match %d (%s is type %d)",
884                                node_type, 
885                                NODE_INFO (child)->name,
886                                NODE_INFO (child)->type);
887                   
888                   return child;
889                 }
890             }
891         }
892       if (!child && create)
893         {
894           Node *mnode;
895           
896           mnode = g_chunk_new0 (Node, merge_node_chunk);
897           mnode->type = node_type;
898           mnode->name = g_strndup (childname, childname_length);
899
900           if (sibling)
901             {
902               if (top)
903                 child = g_node_insert_before (parent, sibling, 
904                                               g_node_new (mnode));
905               else
906                 child = g_node_insert_after (parent, sibling, 
907                                              g_node_new (mnode));
908             }
909           else
910             {
911               if (top)
912                 child = g_node_prepend_data (parent, mnode);
913               else
914                 child = g_node_append_data (parent, mnode);
915             }
916
917           mark_node_dirty (child);
918         }
919     }
920   else
921     {
922       /* handle root node */
923       if (self->private_data->root_node)
924         {
925           child = self->private_data->root_node;
926           if (strncmp (NODE_INFO (child)->name, childname, childname_length) != 0)
927             g_warning ("root node name '%s' doesn't match '%s'",
928                        childname, NODE_INFO (child)->name);
929           if (NODE_INFO (child)->type != NODE_TYPE_ROOT)
930             g_warning ("base element must be of type ROOT");
931         }
932       else if (create)
933         {
934           Node *mnode;
935
936           mnode = g_chunk_new0 (Node, merge_node_chunk);
937           mnode->type = node_type;
938           mnode->name = g_strndup (childname, childname_length);
939           mnode->dirty = TRUE;
940           
941           child = self->private_data->root_node = g_node_new (mnode);
942         }
943     }
944
945   return child;
946 }
947
948 static GNode *
949 get_node (GtkUIManager *self, 
950           const gchar  *path,
951           NodeType      node_type, 
952           gboolean      create)
953 {
954   const gchar *pos, *end;
955   GNode *parent, *node;
956   
957   if (strncmp ("/ui", path, 3) == 0)
958     path += 3;
959   
960   end = path + strlen (path);
961   pos = path;
962   parent = node = NULL;
963   while (pos < end)
964     {
965       const gchar *slash;
966       gsize length;
967
968       slash = strchr (pos, '/');
969       if (slash)
970         length = slash - pos;
971       else
972         length = strlen (pos);
973
974       node = get_child_node (self, parent, NULL, pos, length, NODE_TYPE_UNDECIDED,
975                              create, FALSE);
976       if (!node)
977         return NULL;
978
979       pos += length + 1; /* move past the node name and the slash too */
980       parent = node;
981     }
982
983   if (node != NULL && NODE_INFO (node)->type == NODE_TYPE_UNDECIDED)
984     NODE_INFO (node)->type = node_type;
985
986   return node;
987 }
988
989 static gboolean
990 free_node (GNode *node)
991 {
992   Node *info = NODE_INFO (node);
993   
994   g_list_foreach (info->uifiles, (GFunc) g_free, NULL);
995   g_list_free (info->uifiles);
996
997   if (info->action)
998     g_object_unref (info->action);
999   if (info->proxy)
1000     g_object_unref (info->proxy);
1001   if (info->extra)
1002     g_object_unref (info->extra);
1003   g_free (info->name);
1004   g_chunk_free (info, merge_node_chunk);
1005
1006   return FALSE;
1007 }
1008
1009 /**
1010  * gtk_ui_manager_new_merge_id:
1011  * @self: a #GtkUIManager
1012  * 
1013  * Returns an unused merge id, suitable for use with 
1014  * gtk_ui_manager_add_ui().
1015  * 
1016  * Return value: an unused merge id.
1017  *
1018  * Since: 2.4
1019  **/
1020 guint
1021 gtk_ui_manager_new_merge_id (GtkUIManager *self)
1022 {
1023   self->private_data->last_merge_id++;
1024
1025   return self->private_data->last_merge_id;
1026 }
1027
1028 static void
1029 node_prepend_ui_reference (GNode  *gnode,
1030                            guint   merge_id, 
1031                            GQuark  action_quark)
1032 {
1033   Node *node = NODE_INFO (gnode);
1034   NodeUIReference *reference = NULL;
1035
1036   if (node->uifiles && 
1037       ((NodeUIReference *)node->uifiles->data)->merge_id == merge_id)
1038     reference = node->uifiles->data;
1039   else
1040     {
1041       reference = g_new (NodeUIReference, 1);
1042       node->uifiles = g_list_prepend (node->uifiles, reference);
1043     }
1044
1045   reference->merge_id = merge_id;
1046   reference->action_quark = action_quark;
1047
1048   mark_node_dirty (gnode);
1049 }
1050
1051 static void
1052 node_remove_ui_reference (GNode  *gnode,
1053                           guint  merge_id)
1054 {
1055   Node *node = NODE_INFO (gnode);
1056   GList *p;
1057   
1058   for (p = node->uifiles; p != NULL; p = p->next)
1059     {
1060       NodeUIReference *reference = p->data;
1061       
1062       if (reference->merge_id == merge_id)
1063         {
1064           if (p == node->uifiles)
1065             mark_node_dirty (gnode);
1066           node->uifiles = g_list_delete_link (node->uifiles, p);
1067           g_free (reference);
1068
1069           break;
1070         }
1071     }
1072 }
1073
1074 /* -------------------- The UI file parser -------------------- */
1075
1076 typedef enum
1077 {
1078   STATE_START,
1079   STATE_ROOT,
1080   STATE_MENU,
1081   STATE_TOOLBAR,
1082   STATE_MENUITEM,
1083   STATE_TOOLITEM,
1084   STATE_ACCELERATOR,
1085   STATE_END
1086 } ParseState;
1087
1088 typedef struct _ParseContext ParseContext;
1089 struct _ParseContext
1090 {
1091   ParseState state;
1092   ParseState prev_state;
1093
1094   GtkUIManager *self;
1095
1096   GNode *current;
1097
1098   guint merge_id;
1099 };
1100
1101 static void
1102 start_element_handler (GMarkupParseContext *context,
1103                        const gchar         *element_name,
1104                        const gchar        **attribute_names,
1105                        const gchar        **attribute_values,
1106                        gpointer             user_data,
1107                        GError             **error)
1108 {
1109   ParseContext *ctx = user_data;
1110   GtkUIManager *self = ctx->self;
1111
1112   gint i;
1113   const gchar *node_name;
1114   const gchar *action;
1115   GQuark action_quark;
1116   gboolean top;
1117   gboolean expand = FALSE;
1118   
1119   gboolean raise_error = TRUE;
1120
1121   node_name = NULL;
1122   action = NULL;
1123   action_quark = 0;
1124   top = FALSE;
1125
1126   for (i = 0; attribute_names[i] != NULL; i++)
1127     {
1128       if (!strcmp (attribute_names[i], "name"))
1129         {
1130           node_name = attribute_values[i];
1131         }
1132       else if (!strcmp (attribute_names[i], "action"))
1133         {
1134           action = attribute_values[i];
1135           action_quark = g_quark_from_string (attribute_values[i]);
1136         }
1137       else if (!strcmp (attribute_names[i], "position"))
1138         {
1139           top = !strcmp (attribute_values[i], "top");
1140         }
1141       else if (!strcmp (attribute_names[i], "expand"))
1142         {
1143           expand = !strcmp (attribute_values[i], "true");
1144         }
1145       else
1146         {
1147           gint line_number, char_number;
1148           
1149           g_markup_parse_context_get_position (context,
1150                                                &line_number, &char_number);
1151           g_set_error (error,
1152                        G_MARKUP_ERROR,
1153                        G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1154                        _("Unknown attribute '%s' on line %d char %d"),
1155                        attribute_names[i],
1156                        line_number, char_number);
1157           return;
1158         }
1159     }
1160
1161   /* Work out a name for this node.  Either the name attribute, or
1162    * the action, or the element name */
1163   if (node_name == NULL) 
1164     {
1165       if (action != NULL)
1166         node_name = action;
1167       else 
1168         node_name = element_name;
1169     }
1170
1171   switch (element_name[0])
1172     {
1173     case 'a':
1174       if (ctx->state == STATE_ROOT && !strcmp (element_name, "accelerator"))
1175         {
1176           ctx->state = STATE_ACCELERATOR;
1177           ctx->current = get_child_node (self, ctx->current, NULL,
1178                                          node_name, strlen (node_name),
1179                                          NODE_TYPE_ACCELERATOR,
1180                                          TRUE, FALSE);
1181           if (NODE_INFO (ctx->current)->action_name == 0)
1182             NODE_INFO (ctx->current)->action_name = action_quark;
1183
1184           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1185
1186           raise_error = FALSE;
1187         }
1188       break;
1189     case 'u':
1190       if (ctx->state == STATE_START && !strcmp (element_name, "ui"))
1191         {
1192           ctx->state = STATE_ROOT;
1193           ctx->current = self->private_data->root_node;
1194           raise_error = FALSE;
1195
1196           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1197         }
1198       break;
1199     case 'm':
1200       if (ctx->state == STATE_ROOT && !strcmp (element_name, "menubar"))
1201         {
1202           ctx->state = STATE_MENU;
1203           ctx->current = get_child_node (self, ctx->current, NULL,
1204                                          node_name, strlen (node_name),
1205                                          NODE_TYPE_MENUBAR,
1206                                          TRUE, FALSE);
1207           if (NODE_INFO (ctx->current)->action_name == 0)
1208             NODE_INFO (ctx->current)->action_name = action_quark;
1209
1210           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1211           mark_node_dirty (ctx->current);
1212
1213           raise_error = FALSE;
1214         }
1215       else if (ctx->state == STATE_MENU && !strcmp (element_name, "menu"))
1216         {
1217           ctx->current = get_child_node (self, ctx->current, NULL,
1218                                          node_name, strlen (node_name),
1219                                          NODE_TYPE_MENU,
1220                                          TRUE, top);
1221           if (NODE_INFO (ctx->current)->action_name == 0)
1222             NODE_INFO (ctx->current)->action_name = action_quark;
1223
1224           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1225           
1226           raise_error = FALSE;
1227         }
1228       else if (ctx->state == STATE_TOOLITEM && !strcmp (element_name, "menu"))
1229         {
1230           ctx->state = STATE_MENU;
1231           
1232           ctx->current = get_child_node (self, g_node_last_child (ctx->current), NULL,
1233                                          node_name, strlen (node_name),
1234                                          NODE_TYPE_MENU,
1235                                          TRUE, top);
1236           if (NODE_INFO (ctx->current)->action_name == 0)
1237             NODE_INFO (ctx->current)->action_name = action_quark;
1238
1239           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1240           
1241           raise_error = FALSE;
1242         }
1243       else if (ctx->state == STATE_MENU && !strcmp (element_name, "menuitem"))
1244         {
1245           GNode *node;
1246
1247           ctx->state = STATE_MENUITEM;
1248           node = get_child_node (self, ctx->current, NULL,
1249                                  node_name, strlen (node_name),
1250                                  NODE_TYPE_MENUITEM,
1251                                  TRUE, top);
1252           if (NODE_INFO (node)->action_name == 0)
1253             NODE_INFO (node)->action_name = action_quark;
1254           
1255           node_prepend_ui_reference (node, ctx->merge_id, action_quark);
1256           
1257           raise_error = FALSE;
1258         }
1259       break;
1260     case 'p':
1261       if (ctx->state == STATE_ROOT && !strcmp (element_name, "popup"))
1262         {
1263           ctx->state = STATE_MENU;
1264           ctx->current = get_child_node (self, ctx->current, NULL,
1265                                          node_name, strlen (node_name),
1266                                          NODE_TYPE_POPUP,
1267                                          TRUE, FALSE);
1268           if (NODE_INFO (ctx->current)->action_name == 0)
1269             NODE_INFO (ctx->current)->action_name = action_quark;
1270           
1271           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1272           
1273           raise_error = FALSE;
1274         }
1275       else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
1276                !strcmp (element_name, "placeholder"))
1277         {
1278           if (ctx->state == STATE_TOOLBAR)
1279             ctx->current = get_child_node (self, ctx->current, NULL,
1280                                            node_name, strlen (node_name),
1281                                            NODE_TYPE_TOOLBAR_PLACEHOLDER,
1282                                            TRUE, top);
1283           else
1284             ctx->current = get_child_node (self, ctx->current, NULL,
1285                                            node_name, strlen (node_name),
1286                                            NODE_TYPE_MENU_PLACEHOLDER,
1287                                            TRUE, top);
1288           
1289           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1290           
1291           raise_error = FALSE;
1292         }
1293       break;
1294     case 's':
1295       if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
1296           !strcmp (element_name, "separator"))
1297         {
1298           GNode *node;
1299           gint length;
1300
1301           if (ctx->state == STATE_TOOLBAR)
1302             ctx->state = STATE_TOOLITEM;
1303           else
1304             ctx->state = STATE_MENUITEM;
1305           if (!strcmp (node_name, "separator"))
1306             {
1307               node_name = NULL;
1308               length = -1;
1309             }
1310           else
1311             length = strlen (node_name);
1312           node = get_child_node (self, ctx->current, NULL,
1313                                  node_name, length,
1314                                  NODE_TYPE_SEPARATOR,
1315                                  TRUE, top);
1316
1317           NODE_INFO (node)->expand = expand;
1318
1319           if (NODE_INFO (node)->action_name == 0)
1320             NODE_INFO (node)->action_name = action_quark;
1321
1322           node_prepend_ui_reference (node, ctx->merge_id, action_quark);
1323           
1324           raise_error = FALSE;
1325         }
1326       break;
1327     case 't':
1328       if (ctx->state == STATE_ROOT && !strcmp (element_name, "toolbar"))
1329         {
1330           ctx->state = STATE_TOOLBAR;
1331           ctx->current = get_child_node (self, ctx->current, NULL,
1332                                          node_name, strlen (node_name),
1333                                          NODE_TYPE_TOOLBAR,
1334                                          TRUE, FALSE);
1335           if (NODE_INFO (ctx->current)->action_name == 0)
1336             NODE_INFO (ctx->current)->action_name = action_quark;
1337           
1338           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1339           
1340           raise_error = FALSE;
1341         }
1342       else if (ctx->state == STATE_TOOLBAR && !strcmp (element_name, "toolitem"))
1343         {
1344           GNode *node;
1345
1346           ctx->state = STATE_TOOLITEM;
1347           node = get_child_node (self, ctx->current, NULL,
1348                                 node_name, strlen (node_name),
1349                                  NODE_TYPE_TOOLITEM,
1350                                  TRUE, top);
1351           if (NODE_INFO (node)->action_name == 0)
1352             NODE_INFO (node)->action_name = action_quark;
1353           
1354           node_prepend_ui_reference (node, ctx->merge_id, action_quark);
1355
1356           raise_error = FALSE;
1357         }
1358       break;
1359     default:
1360       break;
1361     }
1362   if (raise_error)
1363     {
1364       gint line_number, char_number;
1365  
1366       g_markup_parse_context_get_position (context,
1367                                            &line_number, &char_number);
1368       g_set_error (error,
1369                    G_MARKUP_ERROR,
1370                    G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1371                    _("Unexpected start tag '%s' on line %d char %d"),
1372                    element_name,
1373                    line_number, char_number);
1374     }
1375 }
1376
1377 static void
1378 end_element_handler (GMarkupParseContext *context,
1379                      const gchar         *element_name,
1380                      gpointer             user_data,
1381                      GError             **error)
1382 {
1383   ParseContext *ctx = user_data;
1384
1385   switch (ctx->state)
1386     {
1387     case STATE_START:
1388     case STATE_END:
1389       /* no need to GError here, GMarkup already catches this */
1390       break;
1391     case STATE_ROOT:
1392       ctx->current = NULL;
1393       ctx->state = STATE_END;
1394       break;
1395     case STATE_MENU:
1396     case STATE_TOOLBAR:
1397     case STATE_ACCELERATOR:
1398       ctx->current = ctx->current->parent;
1399       if (NODE_INFO (ctx->current)->type == NODE_TYPE_ROOT) 
1400         ctx->state = STATE_ROOT;
1401       else if (NODE_INFO (ctx->current)->type == NODE_TYPE_TOOLITEM)
1402         {
1403           ctx->current = ctx->current->parent;
1404           ctx->state = STATE_TOOLITEM;
1405         }
1406       /* else, stay in same state */
1407       break;
1408     case STATE_MENUITEM:
1409       ctx->state = STATE_MENU;
1410       break;
1411     case STATE_TOOLITEM:
1412       ctx->state = STATE_TOOLBAR;
1413       break;
1414     }
1415 }
1416
1417 static void
1418 cleanup (GMarkupParseContext *context,
1419          GError              *error,
1420          gpointer             user_data)
1421 {
1422   ParseContext *ctx = user_data;
1423
1424   ctx->current = NULL;
1425   /* should also walk through the tree and get rid of nodes related to
1426    * this UI file's tag */
1427
1428   gtk_ui_manager_remove_ui (ctx->self, ctx->merge_id);
1429 }
1430
1431 static gboolean
1432 xml_isspace (char c)
1433 {
1434   return c == ' ' || c == '\t' || c == '\n' || c == '\r';
1435 }
1436
1437 static void 
1438 text_handler (GMarkupParseContext *context,
1439               const gchar         *text,
1440               gsize                text_len,  
1441               gpointer             user_data,
1442               GError             **error)
1443 {
1444   const gchar *p;
1445   const gchar *end;
1446
1447   p = text;
1448   end = text + text_len;
1449   while (p != end && xml_isspace (*p))
1450     ++p;
1451   
1452   if (p != end)
1453     {
1454       gint line_number, char_number;
1455       
1456       g_markup_parse_context_get_position (context,
1457                                            &line_number, &char_number);
1458       g_set_error (error,
1459                    G_MARKUP_ERROR,
1460                    G_MARKUP_ERROR_INVALID_CONTENT,
1461                    _("Unexpected character data on line %d char %d"),
1462                    line_number, char_number);
1463     }
1464 }
1465
1466
1467 static GMarkupParser ui_parser = {
1468   start_element_handler,
1469   end_element_handler,
1470   text_handler,
1471   NULL,
1472   cleanup
1473 };
1474
1475 static guint
1476 add_ui_from_string (GtkUIManager *self,
1477                     const gchar  *buffer, 
1478                     gssize        length,
1479                     gboolean      needs_root,
1480                     GError      **error)
1481 {
1482   ParseContext ctx = { 0 };
1483   GMarkupParseContext *context;
1484
1485   ctx.state = STATE_START;
1486   ctx.self = self;
1487   ctx.current = NULL;
1488   ctx.merge_id = gtk_ui_manager_new_merge_id (self);
1489
1490   context = g_markup_parse_context_new (&ui_parser, 0, &ctx, NULL);
1491
1492   if (needs_root)
1493     if (!g_markup_parse_context_parse (context, "<ui>", -1, error))
1494       goto out;
1495
1496   if (!g_markup_parse_context_parse (context, buffer, length, error))
1497     goto out;
1498
1499   if (needs_root)
1500     if (!g_markup_parse_context_parse (context, "</ui>", -1, error))
1501       goto out;
1502
1503   if (!g_markup_parse_context_end_parse (context, error))
1504     goto out;
1505
1506   g_markup_parse_context_free (context);
1507
1508   queue_update (self);
1509
1510   g_object_notify (G_OBJECT (self), "ui");      
1511
1512   return ctx.merge_id;
1513
1514  out:
1515
1516   g_markup_parse_context_free (context);
1517
1518   return 0;
1519 }
1520
1521 /**
1522  * gtk_ui_manager_add_ui_from_string:
1523  * @self: a #GtkUIManager object
1524  * @buffer: the string to parse
1525  * @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
1526  * @error: return location for an error
1527  * 
1528  * Parses a string containing a <link linkend="XML-UI">UI definition</link> and 
1529  * merges it with the current contents of @self. An enclosing &lt;ui&gt; 
1530  * element is added if it is missing.
1531  * 
1532  * Return value: The merge id for the merged UI. The merge id can be used
1533  *   to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1534  *   the return value is 0.
1535  *
1536  * Since: 2.4
1537  **/
1538 guint
1539 gtk_ui_manager_add_ui_from_string (GtkUIManager *self,
1540                                    const gchar  *buffer, 
1541                                    gssize        length,
1542                                    GError      **error)
1543 {
1544   gboolean needs_root = TRUE;
1545   const gchar *p;
1546   const gchar *end;
1547
1548   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), 0);
1549   g_return_val_if_fail (buffer != NULL, 0);
1550
1551   if (length < 0)
1552     length = strlen (buffer);
1553
1554   p = buffer;
1555   end = buffer + length;
1556   while (p != end && xml_isspace (*p))
1557     ++p;
1558
1559   if (end - p >= 4 && strncmp (p, "<ui>", 4) == 0)
1560     needs_root = FALSE;
1561   
1562   return add_ui_from_string (self, buffer, length, needs_root, error);
1563 }
1564
1565 /**
1566  * gtk_ui_manager_add_ui_from_file:
1567  * @self: a #GtkUIManager object
1568  * @filename: the name of the file to parse 
1569  * @error: return location for an error
1570  * 
1571  * Parses a file containing a <link linkend="XML-UI">UI definition</link> and 
1572  * merges it with the current contents of @self. 
1573  * 
1574  * Return value: The merge id for the merged UI. The merge id can be used
1575  *   to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1576  *   the return value is 0.
1577  *
1578  * Since: 2.4
1579  **/
1580 guint
1581 gtk_ui_manager_add_ui_from_file (GtkUIManager *self,
1582                                  const gchar  *filename,
1583                                  GError      **error)
1584 {
1585   gchar *buffer;
1586   gsize length;
1587   guint res;
1588
1589   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), 0);
1590
1591   if (!g_file_get_contents (filename, &buffer, &length, error))
1592     return 0;
1593
1594   res = add_ui_from_string (self, buffer, length, FALSE, error);
1595   g_free (buffer);
1596
1597   return res;
1598 }
1599
1600 /**
1601  * gtk_ui_manager_add_ui:
1602  * @self: a #GtkUIManager
1603  * @merge_id: the merge id for the merged UI, see gtk_ui_manager_new_merge_id()
1604  * @path: a path
1605  * @name: the name for the added UI element
1606  * @action: the name of the action to be proxied, or %NULL to add a separator
1607  * @type: the type of UI element to add.
1608  * @top: if %TRUE, the UI element is added before its siblings, otherwise it 
1609  *   is added after its siblings.
1610  * 
1611  * Adds a UI element to the current contents of @self. 
1612  *
1613  * If @type is %GTK_UI_MANAGER_AUTO, GTK+ inserts a menuitem, toolitem or 
1614  * separator if such an element can be inserted at the place determined by 
1615  * @path. Otherwise @type must indicate an element that can be inserted at 
1616  * the place determined by @path.
1617  *
1618  * If @path points to a menuitem or toolitem, the new element will be inserted
1619  * before or after this item, depending on @top.
1620  * 
1621  * Since: 2.4
1622  **/
1623 void
1624 gtk_ui_manager_add_ui (GtkUIManager        *self,
1625                        guint                merge_id,
1626                        const gchar         *path,
1627                        const gchar         *name,
1628                        const gchar         *action,
1629                        GtkUIManagerItemType type,
1630                        gboolean             top)
1631 {
1632   GNode *node;
1633   GNode *sibling;
1634   GNode *child;
1635   NodeType node_type;
1636   GQuark action_quark = 0;
1637
1638   g_return_if_fail (GTK_IS_UI_MANAGER (self));  
1639   g_return_if_fail (merge_id > 0);
1640   g_return_if_fail (name != NULL);
1641
1642   node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
1643   sibling = NULL;
1644
1645   if (node == NULL)
1646     return;
1647
1648   node_type = NODE_TYPE_UNDECIDED;
1649
1650  reswitch:
1651   switch (NODE_INFO (node)->type) 
1652     {
1653     case NODE_TYPE_SEPARATOR:
1654     case NODE_TYPE_MENUITEM:
1655     case NODE_TYPE_TOOLITEM:
1656       sibling = node;
1657       node = node->parent;
1658       goto reswitch;
1659     case NODE_TYPE_MENUBAR:
1660     case NODE_TYPE_MENU:
1661     case NODE_TYPE_POPUP:
1662     case NODE_TYPE_MENU_PLACEHOLDER:
1663       switch (type) 
1664         {
1665         case GTK_UI_MANAGER_AUTO:
1666           if (action != NULL)
1667               node_type = NODE_TYPE_MENUITEM;
1668           else
1669               node_type = NODE_TYPE_SEPARATOR;
1670           break;
1671         case GTK_UI_MANAGER_MENU:
1672           node_type = NODE_TYPE_MENU;
1673           break;
1674         case GTK_UI_MANAGER_MENUITEM:
1675           node_type = NODE_TYPE_MENUITEM;
1676           break;
1677         case GTK_UI_MANAGER_SEPARATOR:
1678           node_type = NODE_TYPE_SEPARATOR;
1679           break;
1680         case GTK_UI_MANAGER_PLACEHOLDER:
1681           node_type = NODE_TYPE_MENU_PLACEHOLDER;
1682           break;
1683         default: ;
1684           /* do nothing */
1685         }
1686       break;
1687     case NODE_TYPE_TOOLBAR:
1688     case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1689       switch (type) 
1690         {
1691         case GTK_UI_MANAGER_AUTO:
1692           if (action != NULL)
1693               node_type = NODE_TYPE_TOOLITEM;
1694           else
1695               node_type = NODE_TYPE_SEPARATOR;
1696           break;
1697         case GTK_UI_MANAGER_TOOLITEM:
1698           node_type = NODE_TYPE_TOOLITEM;
1699           break;
1700         case GTK_UI_MANAGER_SEPARATOR:
1701           node_type = NODE_TYPE_SEPARATOR;
1702           break;
1703         case GTK_UI_MANAGER_PLACEHOLDER:
1704           node_type = NODE_TYPE_TOOLBAR_PLACEHOLDER;
1705           break;
1706         default: ;
1707           /* do nothing */
1708         }
1709       break;
1710     case NODE_TYPE_ROOT:
1711       switch (type) 
1712         {
1713         case GTK_UI_MANAGER_MENUBAR:
1714           node_type = NODE_TYPE_MENUBAR;
1715           break;
1716         case GTK_UI_MANAGER_TOOLBAR:
1717           node_type = NODE_TYPE_TOOLBAR;
1718           break;
1719         case GTK_UI_MANAGER_POPUP:
1720           node_type = NODE_TYPE_POPUP;
1721           break;
1722         case GTK_UI_MANAGER_ACCELERATOR:
1723           node_type = NODE_TYPE_ACCELERATOR;
1724           break;
1725         default: ;
1726           /* do nothing */
1727         }
1728       break;
1729     default: ;
1730       /* do nothing */
1731     }
1732
1733   if (node_type == NODE_TYPE_UNDECIDED)
1734     {
1735       g_warning ("item type %d not suitable for adding at '%s'", 
1736                  type, path);
1737       return;
1738     }
1739    
1740   child = get_child_node (self, node, sibling,
1741                           name, strlen (name),
1742                           node_type, TRUE, top);
1743
1744   if (action != NULL)
1745     action_quark = g_quark_from_string (action);
1746
1747   node_prepend_ui_reference (child, merge_id, action_quark);
1748
1749   if (NODE_INFO (child)->action_name == 0)
1750     NODE_INFO (child)->action_name = action_quark;
1751
1752   queue_update (self);
1753
1754   g_object_notify (G_OBJECT (self), "ui");      
1755 }
1756
1757 static gboolean
1758 remove_ui (GNode   *node, 
1759            gpointer user_data)
1760 {
1761   guint merge_id = GPOINTER_TO_UINT (user_data);
1762
1763   node_remove_ui_reference (node, merge_id);
1764
1765   return FALSE; /* continue */
1766 }
1767
1768 /**
1769  * gtk_ui_manager_remove_ui:
1770  * @self: a #GtkUIManager object
1771  * @merge_id: a merge id as returned by gtk_ui_manager_add_ui_from_string()
1772  * 
1773  * Unmerges the part of @self<!-- -->s content identified by @merge_id.
1774  *
1775  * Since: 2.4
1776  **/
1777 void
1778 gtk_ui_manager_remove_ui (GtkUIManager *self, 
1779                           guint         merge_id)
1780 {
1781   g_node_traverse (self->private_data->root_node, 
1782                    G_POST_ORDER, G_TRAVERSE_ALL, -1,
1783                    remove_ui, GUINT_TO_POINTER (merge_id));
1784
1785   queue_update (self);
1786
1787   g_object_notify (G_OBJECT (self), "ui");      
1788 }
1789
1790 /* -------------------- Updates -------------------- */
1791
1792
1793 static GtkAction *
1794 get_action_by_name (GtkUIManager *merge, 
1795                     const gchar  *action_name)
1796 {
1797   GList *tmp;
1798
1799   if (!action_name)
1800     return NULL;
1801   
1802   /* lookup name */
1803   for (tmp = merge->private_data->action_groups; tmp != NULL; tmp = tmp->next)
1804     {
1805       GtkActionGroup *action_group = tmp->data;
1806       GtkAction *action;
1807       
1808       action = gtk_action_group_get_action (action_group, action_name);
1809
1810       if (action)
1811         return action;
1812     }
1813
1814   return NULL;
1815 }
1816
1817 static gboolean
1818 find_menu_position (GNode      *node, 
1819                     GtkWidget **menushell_p, 
1820                     gint       *pos_p)
1821 {
1822   GtkWidget *menushell;
1823   gint pos;
1824
1825   g_return_val_if_fail (node != NULL, FALSE);
1826   g_return_val_if_fail (NODE_INFO (node)->type == NODE_TYPE_MENU ||
1827                         NODE_INFO (node)->type == NODE_TYPE_POPUP ||
1828                         NODE_INFO (node)->type == NODE_TYPE_MENU_PLACEHOLDER ||
1829                         NODE_INFO (node)->type == NODE_TYPE_MENUITEM ||
1830                         NODE_INFO (node)->type == NODE_TYPE_SEPARATOR,
1831                         FALSE);
1832
1833   /* first sibling -- look at parent */
1834   if (node->prev == NULL)
1835     {
1836       GNode *parent;
1837       GList *siblings;
1838
1839       parent = node->parent;
1840       switch (NODE_INFO (parent)->type)
1841         {
1842         case NODE_TYPE_MENUBAR:
1843         case NODE_TYPE_POPUP:
1844           menushell = NODE_INFO (parent)->proxy;
1845           pos = 0;
1846           break;
1847         case NODE_TYPE_MENU:
1848           menushell = NODE_INFO (parent)->proxy;
1849           if (GTK_IS_MENU_ITEM (menushell))
1850             menushell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menushell));
1851           siblings = gtk_container_get_children (GTK_CONTAINER (menushell));
1852           if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1853             pos = 1;
1854           else
1855             pos = 0;
1856           g_list_free (siblings);
1857           break;
1858         case NODE_TYPE_MENU_PLACEHOLDER:
1859           menushell = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1860           g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1861           pos = g_list_index (GTK_MENU_SHELL (menushell)->children,
1862                               NODE_INFO (parent)->proxy) + 1;
1863           break;
1864         default:
1865           g_warning("%s: bad parent node type %d", G_STRLOC,
1866                     NODE_INFO (parent)->type);
1867           return FALSE;
1868         }
1869     }
1870   else
1871     {
1872       GtkWidget *prev_child;
1873       GNode *sibling;
1874
1875       sibling = node->prev;
1876       if (NODE_INFO (sibling)->type == NODE_TYPE_MENU_PLACEHOLDER)
1877         prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1878       else
1879         prev_child = NODE_INFO (sibling)->proxy;
1880
1881       g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
1882       menushell = gtk_widget_get_parent (prev_child);
1883       g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1884
1885       pos = g_list_index (GTK_MENU_SHELL (menushell)->children, prev_child) + 1;
1886     }
1887
1888   if (menushell_p)
1889     *menushell_p = menushell;
1890   if (pos_p)
1891     *pos_p = pos;
1892
1893   return TRUE;
1894 }
1895
1896 static gboolean
1897 find_toolbar_position (GNode      *node, 
1898                        GtkWidget **toolbar_p, 
1899                        gint       *pos_p)
1900 {
1901   GtkWidget *toolbar;
1902   gint pos;
1903
1904   g_return_val_if_fail (node != NULL, FALSE);
1905   g_return_val_if_fail (NODE_INFO (node)->type == NODE_TYPE_TOOLBAR ||
1906                         NODE_INFO (node)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER ||
1907                         NODE_INFO (node)->type == NODE_TYPE_TOOLITEM ||
1908                         NODE_INFO (node)->type == NODE_TYPE_SEPARATOR,
1909                         FALSE);
1910
1911   /* first sibling -- look at parent */
1912   if (node->prev == NULL)
1913     {
1914       GNode *parent;
1915
1916       parent = node->parent;
1917       switch (NODE_INFO (parent)->type)
1918         {
1919         case NODE_TYPE_TOOLBAR:
1920           toolbar = NODE_INFO (parent)->proxy;
1921           pos = 0;
1922           break;
1923         case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1924           toolbar = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1925           g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
1926           pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
1927                                             GTK_TOOL_ITEM (NODE_INFO (parent)->proxy)) + 1;
1928           break;
1929         default:
1930           g_warning ("%s: bad parent node type %d", G_STRLOC,
1931                      NODE_INFO (parent)->type);
1932           return FALSE;
1933         }
1934     }
1935   else
1936     {
1937       GtkWidget *prev_child;
1938       GNode *sibling;
1939
1940       sibling = node->prev;
1941       if (NODE_INFO (sibling)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
1942         prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1943       else
1944         prev_child = NODE_INFO (sibling)->proxy;
1945
1946       g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
1947       toolbar = gtk_widget_get_parent (prev_child);
1948       g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
1949
1950       pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
1951                                         GTK_TOOL_ITEM (prev_child)) + 1;
1952     }
1953
1954   if (toolbar_p)
1955     *toolbar_p = toolbar;
1956   if (pos_p)
1957     *pos_p = pos;
1958
1959   return TRUE;
1960 }
1961
1962 /**
1963  * _gtk_menu_is_empty:
1964  * @menu: a #GtkMenu or %NULL
1965  * 
1966  * Determines whether @menu is empty. A menu is considered empty if it
1967  * the only visible children are tearoff menu items or "filler" menu 
1968  * items which were inserted to mark the menu as empty.
1969  * 
1970  * This function is used by #GtkAction.
1971  *
1972  * Return value: whether @menu is empty.
1973  **/
1974 gboolean
1975 _gtk_menu_is_empty (GtkWidget *menu)
1976 {
1977   GList *children, *cur;
1978
1979   g_return_val_if_fail (menu == NULL || GTK_IS_MENU (menu), TRUE);
1980
1981   if (!menu)
1982     return FALSE;
1983
1984   children = gtk_container_get_children (GTK_CONTAINER (menu));
1985
1986   cur = children;
1987   while (cur) 
1988     {
1989       if (GTK_WIDGET_VISIBLE (cur->data))
1990         {
1991           if (!GTK_IS_TEAROFF_MENU_ITEM (cur->data) &&
1992               !g_object_get_data (cur->data, "gtk-empty-menu-item"))
1993             return FALSE;
1994         }
1995       cur = cur->next;
1996     }
1997   g_list_free (children);
1998
1999   return TRUE;
2000 }
2001
2002 enum {
2003   SEPARATOR_MODE_SMART,
2004   SEPARATOR_MODE_VISIBLE,
2005   SEPARATOR_MODE_HIDDEN
2006 };
2007
2008 void _gtk_action_sync_menu_visible (GtkAction *action,
2009                                     GtkWidget *proxy,
2010                                     gboolean   empty);
2011
2012 static void
2013 update_smart_separators (GtkWidget *proxy)
2014 {
2015   GtkWidget *parent = NULL;
2016   
2017   if (GTK_IS_MENU (proxy) || GTK_IS_TOOLBAR (proxy))
2018     parent = proxy;
2019   else if (GTK_IS_MENU_ITEM (proxy) || GTK_IS_TOOL_ITEM (proxy))
2020     parent = gtk_widget_get_parent (proxy);
2021
2022   if (parent) 
2023     {
2024       gboolean visible;
2025       gboolean empty;
2026       GList *children, *cur, *last;
2027       GtkWidget *filler;
2028
2029       children = gtk_container_get_children (GTK_CONTAINER (parent));
2030       
2031       visible = FALSE;
2032       last = NULL;
2033       empty = TRUE;
2034       filler = NULL;
2035
2036       cur = children;
2037       while (cur) 
2038         {
2039           if (g_object_get_data (cur->data, "gtk-empty-menu-item"))
2040             {
2041               filler = cur->data;
2042             }
2043           else if (GTK_IS_SEPARATOR_MENU_ITEM (cur->data) ||
2044                    GTK_IS_SEPARATOR_TOOL_ITEM (cur->data))
2045             {
2046               gint mode = 
2047                 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cur->data), 
2048                                                     "gtk-separator-mode"));
2049               switch (mode) 
2050                 {
2051                 case SEPARATOR_MODE_VISIBLE:
2052                   gtk_widget_show (GTK_WIDGET (cur->data));
2053                   last = NULL;
2054                   visible = FALSE;
2055                   break;
2056                 case SEPARATOR_MODE_HIDDEN:
2057                   gtk_widget_hide (GTK_WIDGET (cur->data));
2058                   break;
2059                 case SEPARATOR_MODE_SMART:
2060                   if (visible)
2061                     {
2062                       gtk_widget_show (GTK_WIDGET (cur->data));
2063                       last = cur;
2064                       visible = FALSE;
2065                     }
2066                   else 
2067                     gtk_widget_hide (GTK_WIDGET (cur->data));
2068                   break;
2069                 }
2070             }
2071           else if (GTK_WIDGET_VISIBLE (cur->data))
2072             {
2073               last = NULL;
2074               if (GTK_IS_TEAROFF_MENU_ITEM (cur->data) || cur->data == filler)
2075                 visible = FALSE;
2076               else 
2077                 {
2078                   visible = TRUE;
2079                   empty = FALSE;
2080                 }
2081             }
2082           
2083           cur = cur->next;
2084         }
2085
2086       if (last)
2087         gtk_widget_hide (GTK_WIDGET (last->data));
2088
2089       if (GTK_IS_MENU (parent)) 
2090         {
2091           GtkWidget *item;
2092
2093           item = gtk_menu_get_attach_widget (GTK_MENU (parent));
2094           if (GTK_IS_MENU_ITEM (item))
2095             _gtk_action_sync_menu_visible (NULL, item, empty);
2096           if (GTK_IS_WIDGET (filler))
2097             {
2098               if (empty)
2099                 gtk_widget_show (filler);
2100               else
2101                 gtk_widget_hide (filler);
2102             }
2103         }
2104
2105       g_list_free (children);
2106     }
2107 }
2108
2109 static void
2110 update_node (GtkUIManager *self, 
2111              GNode        *node,
2112              gboolean      in_popup)
2113 {
2114   Node *info;
2115   GNode *child;
2116   GtkAction *action;
2117   const gchar *action_name;
2118   NodeUIReference *ref;
2119   
2120 #ifdef DEBUG_UI_MANAGER
2121   GList *tmp;
2122 #endif
2123
2124   g_return_if_fail (node != NULL);
2125   g_return_if_fail (NODE_INFO (node) != NULL);
2126
2127   info = NODE_INFO (node);
2128   
2129   if (!info->dirty)
2130     return;
2131
2132   in_popup = in_popup || (info->type == NODE_TYPE_POPUP);
2133
2134 #ifdef DEBUG_UI_MANAGER
2135   g_print ("update_node name=%s dirty=%d popup %d (", 
2136            info->name, info->dirty, in_popup);
2137   for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next)
2138     {
2139       NodeUIReference *ref = tmp->data;
2140       g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id);
2141       if (tmp->next)
2142         g_print (", ");
2143     }
2144   g_print (")\n");
2145 #endif
2146
2147   if (info->uifiles == NULL) {
2148     /* We may need to remove this node.
2149      * This must be done in post order
2150      */
2151     goto recurse_children;
2152   }
2153   
2154   ref = info->uifiles->data;
2155   action_name = g_quark_to_string (ref->action_quark);
2156   action = get_action_by_name (self, action_name);
2157   
2158   info->dirty = FALSE;
2159   
2160   /* Check if the node doesn't have an action and must have an action */
2161   if (action == NULL &&
2162       info->type != NODE_TYPE_ROOT &&
2163       info->type != NODE_TYPE_MENUBAR &&
2164       info->type != NODE_TYPE_TOOLBAR &&
2165       info->type != NODE_TYPE_POPUP &&
2166       info->type != NODE_TYPE_SEPARATOR &&
2167       info->type != NODE_TYPE_MENU_PLACEHOLDER &&
2168       info->type != NODE_TYPE_TOOLBAR_PLACEHOLDER)
2169     {
2170       g_warning ("%s: missing action", info->name);
2171       
2172       goto recurse_children;
2173     }
2174   
2175   if (action)
2176     gtk_action_set_accel_group (action, self->private_data->accel_group);
2177   
2178   /* If the widget already has a proxy and the action hasn't changed, then
2179    * we only have to update the tearoff menu items.
2180    */
2181   if (info->proxy != NULL && action == info->action)
2182     {
2183       if (info->type == NODE_TYPE_MENU) 
2184         {
2185           GtkWidget *menu;
2186           GList *siblings;
2187           
2188           if (GTK_IS_MENU (info->proxy))
2189             menu = info->proxy;
2190           else
2191             menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2192           siblings = gtk_container_get_children (GTK_CONTAINER (menu));
2193           if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
2194             {
2195               if (self->private_data->add_tearoffs && !in_popup)
2196                 gtk_widget_show (GTK_WIDGET (siblings->data));
2197               else
2198                 gtk_widget_hide (GTK_WIDGET (siblings->data));
2199             }
2200           g_list_free (siblings);
2201         }
2202       
2203       goto recurse_children;
2204     }
2205   
2206   switch (info->type)
2207     {
2208     case NODE_TYPE_MENUBAR:
2209       if (info->proxy == NULL)
2210         {
2211           info->proxy = gtk_menu_bar_new ();
2212           g_object_ref_sink (info->proxy);
2213           gtk_widget_set_name (info->proxy, info->name);
2214           gtk_widget_show (info->proxy);
2215           g_signal_emit (self, ui_manager_signals[ADD_WIDGET], 0, info->proxy);
2216         }
2217       break;
2218     case NODE_TYPE_POPUP:
2219       if (info->proxy == NULL) 
2220         {
2221           info->proxy = gtk_menu_new ();
2222           g_object_ref_sink (info->proxy);
2223         }
2224       gtk_widget_set_name (info->proxy, info->name);
2225       break;
2226     case NODE_TYPE_MENU:
2227       {
2228         GtkWidget *prev_submenu = NULL;
2229         GtkWidget *menu;
2230         GList *siblings;
2231         /* remove the proxy if it is of the wrong type ... */
2232         if (info->proxy &&  
2233             G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->menu_item_type)
2234           {
2235             if (GTK_IS_MENU_ITEM (info->proxy))
2236               {
2237                 prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2238                 if (prev_submenu)
2239                   {
2240                     g_object_ref (prev_submenu);
2241                     gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
2242                 }
2243               }
2244
2245             gtk_action_disconnect_proxy (info->action, info->proxy);
2246             gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2247                                   info->proxy);
2248             g_object_unref (info->proxy);
2249             info->proxy = NULL;
2250           }
2251         /* create proxy if needed ... */
2252         if (info->proxy == NULL)
2253           {
2254             GtkWidget *menushell;
2255             gint pos;
2256             
2257             if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLITEM ||
2258                 find_menu_position (node, &menushell, &pos))
2259               {
2260                 GtkWidget *tearoff;
2261                 GtkWidget *filler;
2262                 
2263                 menu = gtk_menu_new ();
2264                 gtk_widget_set_name (menu, info->name);
2265                 tearoff = gtk_tearoff_menu_item_new ();
2266                 gtk_widget_set_no_show_all (tearoff, TRUE);
2267                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff);
2268                 filler = gtk_menu_item_new_with_label (_("Empty"));
2269                 g_object_set_data (G_OBJECT (filler),
2270                                    I_("gtk-empty-menu-item"),
2271                                    GINT_TO_POINTER (TRUE));
2272                 gtk_widget_set_sensitive (filler, FALSE);
2273                 gtk_widget_set_no_show_all (filler, TRUE);
2274                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), filler);
2275
2276                 if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLITEM)
2277                   {
2278                     info->proxy = menu;
2279                     g_object_ref_sink (info->proxy);
2280                     gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (NODE_INFO (node->parent)->proxy),
2281                                                    menu);
2282                   }
2283                 else
2284                   {
2285                     info->proxy = gtk_action_create_menu_item (action);
2286                     g_object_ref_sink (info->proxy);
2287                     g_signal_connect (info->proxy, "notify::visible",
2288                                       G_CALLBACK (update_smart_separators), NULL);
2289                     gtk_widget_set_name (info->proxy, info->name);
2290                     
2291                     gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu);
2292                     gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos);
2293                   }
2294               }
2295           }
2296         else
2297           gtk_action_connect_proxy (action, info->proxy);
2298         
2299         if (prev_submenu)
2300           {
2301             gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),
2302                                        prev_submenu);
2303             g_object_unref (prev_submenu);
2304           }
2305         
2306         if (GTK_IS_MENU (info->proxy))
2307           menu = info->proxy;
2308         else
2309           menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2310         siblings = gtk_container_get_children (GTK_CONTAINER (menu));
2311         if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
2312           {
2313             if (self->private_data->add_tearoffs && !in_popup)
2314               gtk_widget_show (GTK_WIDGET (siblings->data));
2315             else
2316               gtk_widget_hide (GTK_WIDGET (siblings->data));
2317           }
2318         g_list_free (siblings);
2319       }
2320       break;
2321     case NODE_TYPE_UNDECIDED:
2322       g_warning ("found undecided node!");
2323       break;
2324     case NODE_TYPE_ROOT:
2325       break;
2326     case NODE_TYPE_TOOLBAR:
2327       if (info->proxy == NULL)
2328         {
2329           info->proxy = gtk_toolbar_new ();
2330           g_object_ref_sink (info->proxy);
2331           gtk_widget_set_name (info->proxy, info->name);
2332           gtk_widget_show (info->proxy);
2333           g_signal_emit (self, ui_manager_signals[ADD_WIDGET], 0, info->proxy);
2334         }
2335       break;
2336     case NODE_TYPE_MENU_PLACEHOLDER:
2337       /* create menu items for placeholders if necessary ... */
2338       if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) ||
2339           !GTK_IS_SEPARATOR_MENU_ITEM (info->extra))
2340         {
2341           if (info->proxy)
2342             {
2343               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2344                                     info->proxy);
2345               g_object_unref (info->proxy);
2346               info->proxy = NULL;
2347             }
2348           if (info->extra)
2349             {
2350               gtk_container_remove (GTK_CONTAINER (info->extra->parent),
2351                                     info->extra);
2352               g_object_unref (info->extra);
2353               info->extra = NULL;
2354             }
2355         }
2356       if (info->proxy == NULL)
2357         {
2358           GtkWidget *menushell;
2359           gint pos;
2360           
2361           if (find_menu_position (node, &menushell, &pos))
2362             {
2363               info->proxy = gtk_separator_menu_item_new ();
2364               g_object_ref_sink (info->proxy);
2365               g_object_set_data (G_OBJECT (info->proxy),
2366                                  I_("gtk-separator-mode"),
2367                                  GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2368               gtk_widget_set_no_show_all (info->proxy, TRUE);
2369               gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2370                                      NODE_INFO (node)->proxy, pos);
2371               
2372               info->extra = gtk_separator_menu_item_new ();
2373               g_object_ref_sink (info->extra);
2374               g_object_set_data (G_OBJECT (info->extra),
2375                                  I_("gtk-separator-mode"),
2376                                  GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2377               gtk_widget_set_no_show_all (info->extra, TRUE);
2378               gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2379                                      NODE_INFO (node)->extra, pos+1);
2380             }
2381         }
2382       break;
2383     case NODE_TYPE_TOOLBAR_PLACEHOLDER:
2384       /* create toolbar items for placeholders if necessary ... */
2385       if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) ||
2386           !GTK_IS_SEPARATOR_TOOL_ITEM (info->extra))
2387         {
2388           if (info->proxy)
2389             {
2390               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2391                                     info->proxy);
2392               g_object_unref (info->proxy);
2393               info->proxy = NULL;
2394             }
2395           if (info->extra)
2396             {
2397               gtk_container_remove (GTK_CONTAINER (info->extra->parent),
2398                                     info->extra);
2399               g_object_unref (info->extra);
2400               info->extra = NULL;
2401             }
2402         }
2403       if (info->proxy == NULL)
2404         {
2405           GtkWidget *toolbar;
2406           gint pos;
2407           
2408           if (find_toolbar_position (node, &toolbar, &pos))
2409             {
2410               GtkToolItem *item;
2411               
2412               item = gtk_separator_tool_item_new ();
2413               gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2414               info->proxy = GTK_WIDGET (item);
2415               g_object_ref_sink (info->proxy);
2416               g_object_set_data (G_OBJECT (info->proxy),
2417                                  I_("gtk-separator-mode"),
2418                                  GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2419               gtk_widget_set_no_show_all (info->proxy, TRUE);
2420               
2421               item = gtk_separator_tool_item_new ();
2422               gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1);
2423               info->extra = GTK_WIDGET (item);
2424               g_object_ref_sink (info->extra);
2425               g_object_set_data (G_OBJECT (info->extra),
2426                                  I_("gtk-separator-mode"),
2427                                  GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2428               gtk_widget_set_no_show_all (info->extra, TRUE);
2429             }
2430         }
2431       break;
2432     case NODE_TYPE_MENUITEM:
2433       /* remove the proxy if it is of the wrong type ... */
2434       if (info->proxy &&  
2435           G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->menu_item_type)
2436         {
2437           g_signal_handlers_disconnect_by_func (info->proxy,
2438                                                 G_CALLBACK (update_smart_separators),
2439                                                 NULL);  
2440           gtk_action_disconnect_proxy (info->action, info->proxy);
2441           gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2442                                 info->proxy);
2443           g_object_unref (info->proxy);
2444           info->proxy = NULL;
2445         }
2446       /* create proxy if needed ... */
2447       if (info->proxy == NULL)
2448         {
2449           GtkWidget *menushell;
2450           gint pos;
2451           
2452           if (find_menu_position (node, &menushell, &pos))
2453             {
2454               info->proxy = gtk_action_create_menu_item (action);
2455               g_object_ref_sink (info->proxy);
2456               gtk_widget_set_name (info->proxy, info->name);
2457               
2458               gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2459                                      info->proxy, pos);
2460             }
2461         }
2462       else
2463         {
2464           g_signal_handlers_disconnect_by_func (info->proxy,
2465                                                 G_CALLBACK (update_smart_separators),
2466                                                 NULL);
2467           gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
2468           gtk_action_connect_proxy (action, info->proxy);
2469         }
2470       g_signal_connect (info->proxy, "notify::visible",
2471                         G_CALLBACK (update_smart_separators), NULL);
2472       if (in_popup) 
2473         {
2474           /* don't show accels in popups */
2475           GtkWidget *label = GTK_BIN (info->proxy)->child;
2476           g_object_set (label,
2477                         "accel_closure", NULL,
2478                         NULL);
2479         }
2480       
2481       break;
2482     case NODE_TYPE_TOOLITEM:
2483       /* remove the proxy if it is of the wrong type ... */
2484       if (info->proxy && 
2485           G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->toolbar_item_type)
2486         {
2487           g_signal_handlers_disconnect_by_func (info->proxy,
2488                                                 G_CALLBACK (update_smart_separators),
2489                                                 NULL);
2490           gtk_action_disconnect_proxy (info->action, info->proxy);
2491           gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2492                                 info->proxy);
2493           g_object_unref (info->proxy);
2494           info->proxy = NULL;
2495         }
2496       /* create proxy if needed ... */
2497       if (info->proxy == NULL)
2498         {
2499           GtkWidget *toolbar;
2500           gint pos;
2501           
2502           if (find_toolbar_position (node, &toolbar, &pos))
2503             {
2504               info->proxy = gtk_action_create_tool_item (action);
2505               g_object_ref_sink (info->proxy);
2506               gtk_widget_set_name (info->proxy, info->name);
2507               
2508               gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
2509                                   GTK_TOOL_ITEM (info->proxy), pos);
2510             }
2511         }
2512       else
2513         {
2514           g_signal_handlers_disconnect_by_func (info->proxy,
2515                                                 G_CALLBACK (update_smart_separators),
2516                                                 NULL);
2517           gtk_action_connect_proxy (action, info->proxy);
2518         }
2519
2520       /* FIXME: we must trigger the notify::tooltip handler, since 
2521        * tooltips on toolitems can't be set before the toolitem 
2522        * is added to the toolbar.
2523        */
2524       g_object_notify (G_OBJECT (action), "tooltip");
2525
2526       g_signal_connect (info->proxy, "notify::visible",
2527                         G_CALLBACK (update_smart_separators), NULL);
2528       break;
2529     case NODE_TYPE_SEPARATOR:
2530       if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR ||
2531           NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
2532         {
2533           GtkWidget *toolbar;
2534           gint pos;
2535           gint separator_mode;
2536           
2537           if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy))
2538             {
2539               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2540                                     info->proxy);
2541               g_object_unref (info->proxy);
2542               info->proxy = NULL;
2543             }
2544           
2545           if (find_toolbar_position (node, &toolbar, &pos))
2546             {
2547               GtkToolItem *item = gtk_separator_tool_item_new ();
2548               gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2549               info->proxy = GTK_WIDGET (item);
2550               g_object_ref_sink (info->proxy);
2551               gtk_widget_set_no_show_all (info->proxy, TRUE);
2552               if (info->expand)
2553                 {
2554                   gtk_tool_item_set_expand (GTK_TOOL_ITEM (item), TRUE);
2555                   gtk_separator_tool_item_set_draw
2556                     (GTK_SEPARATOR_TOOL_ITEM (item), FALSE);
2557                   separator_mode = SEPARATOR_MODE_VISIBLE;
2558                 }
2559                 else
2560                   separator_mode = SEPARATOR_MODE_SMART;
2561
2562               g_object_set_data (G_OBJECT (info->proxy),
2563                                  I_("gtk-separator-mode"),
2564                                  GINT_TO_POINTER (separator_mode));
2565               gtk_widget_show (info->proxy);
2566             }
2567         }
2568       else
2569         {
2570           GtkWidget *menushell;
2571           gint pos;
2572           
2573           if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy))
2574             {
2575               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2576                                     info->proxy);
2577               g_object_unref (info->proxy);
2578               info->proxy = NULL;
2579             }
2580           
2581           if (find_menu_position (node, &menushell, &pos))
2582             {
2583               info->proxy = gtk_separator_menu_item_new ();
2584               g_object_ref_sink (info->proxy);
2585               gtk_widget_set_no_show_all (info->proxy, TRUE);
2586               g_object_set_data (G_OBJECT (info->proxy),
2587                                  I_("gtk-separator-mode"),
2588                                  GINT_TO_POINTER (SEPARATOR_MODE_SMART));
2589               gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2590                                      info->proxy, pos);
2591               gtk_widget_show (info->proxy);
2592             }
2593         }
2594       break;
2595     case NODE_TYPE_ACCELERATOR:
2596       gtk_action_connect_accelerator (action);
2597       break;
2598     }
2599   
2600   if (action)
2601     g_object_ref (action);
2602   if (info->action)
2603     g_object_unref (info->action);
2604   info->action = action;
2605
2606  recurse_children:
2607   /* process children */
2608   child = node->children;
2609   while (child)
2610     {
2611       GNode *current;
2612       
2613       current = child;
2614       child = current->next;
2615       update_node (self, current, in_popup);
2616     }
2617   
2618   if (info->proxy) 
2619     {
2620       if (info->type == NODE_TYPE_MENU && GTK_IS_MENU_ITEM (info->proxy)) 
2621         update_smart_separators (gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy)));
2622       else if (info->type == NODE_TYPE_MENU || 
2623                info->type == NODE_TYPE_TOOLBAR || 
2624                info->type == NODE_TYPE_POPUP) 
2625         update_smart_separators (info->proxy);
2626     }
2627   
2628   /* handle cleanup of dead nodes */
2629   if (node->children == NULL && info->uifiles == NULL)
2630     {
2631       if (info->proxy)
2632         gtk_widget_destroy (info->proxy);
2633       if (info->extra)
2634         gtk_widget_destroy (info->extra);
2635       if (info->type == NODE_TYPE_ACCELERATOR)
2636         gtk_action_disconnect_accelerator (info->action);
2637       free_node (node);
2638       g_node_destroy (node);
2639     }
2640 }
2641
2642 static gboolean
2643 do_updates (GtkUIManager *self)
2644 {
2645   /* this function needs to check through the tree for dirty nodes.
2646    * For such nodes, it needs to do the following:
2647    *
2648    * 1) check if they are referenced by any loaded UI files anymore.
2649    *    In which case, the proxy widget should be destroyed, unless
2650    *    there are any subnodes.
2651    *
2652    * 2) lookup the action for this node again.  If it is different to
2653    *    the current one (or if no previous action has been looked up),
2654    *    the proxy is reconnected to the new action (or a new proxy widget
2655    *    is created and added to the parent container).
2656    */
2657   update_node (self, self->private_data->root_node, FALSE);
2658
2659   self->private_data->update_tag = 0;
2660
2661   return FALSE;
2662 }
2663
2664 static gboolean
2665 do_updates_idle (GtkUIManager *self)
2666 {
2667   GDK_THREADS_ENTER ();
2668   do_updates (self);
2669   GDK_THREADS_LEAVE ();
2670
2671   return FALSE;
2672 }
2673
2674 static void
2675 queue_update (GtkUIManager *self)
2676 {
2677   if (self->private_data->update_tag != 0)
2678     return;
2679
2680   self->private_data->update_tag = g_idle_add ((GSourceFunc)do_updates_idle, 
2681                                                self);
2682 }
2683
2684
2685 /**
2686  * gtk_ui_manager_ensure_update:
2687  * @self: a #GtkUIManager
2688  * 
2689  * Makes sure that all pending updates to the UI have been completed.
2690  *
2691  * This may occasionally be necessary, since #GtkUIManager updates the 
2692  * UI in an idle function. A typical example where this function is
2693  * useful is to enforce that the menubar and toolbar have been added to 
2694  * the main window before showing it:
2695  * <informalexample>
2696  * <programlisting>
2697  * gtk_container_add (GTK_CONTAINER (window), vbox); 
2698  * g_signal_connect (merge, "add_widget", 
2699  *                   G_CALLBACK (add_widget), vbox);
2700  * gtk_ui_manager_add_ui_from_file (merge, "my-menus");
2701  * gtk_ui_manager_add_ui_from_file (merge, "my-toolbars");
2702  * gtk_ui_manager_ensure_update (merge);  
2703  * gtk_widget_show (window);
2704  * </programlisting>
2705  * </informalexample>
2706  *
2707  * Since: 2.4
2708  **/
2709 void
2710 gtk_ui_manager_ensure_update (GtkUIManager *self)
2711 {
2712   if (self->private_data->update_tag != 0)
2713     {
2714       g_source_remove (self->private_data->update_tag);
2715       do_updates (self);
2716     }
2717 }
2718
2719 static gboolean
2720 dirty_traverse_func (GNode   *node,
2721                      gpointer data)
2722 {
2723   NODE_INFO (node)->dirty = TRUE;
2724   return FALSE;
2725 }
2726
2727 static void
2728 dirty_all_nodes (GtkUIManager *self)
2729 {
2730   g_node_traverse (self->private_data->root_node,
2731                    G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2732                    dirty_traverse_func, NULL);
2733   queue_update (self);
2734 }
2735
2736 static void
2737 mark_node_dirty (GNode *node)
2738 {
2739   GNode *p;
2740
2741   /* FIXME could optimize this */
2742   for (p = node; p; p = p->parent)
2743     NODE_INFO (p)->dirty = TRUE;  
2744 }
2745
2746 static const gchar *const open_tag_format[] = {
2747   "%*s<UNDECIDED",
2748   "%*s<ui",
2749   "%*s<menubar",
2750   "%*s<menu",
2751   "%*s<toolbar",
2752   "%*s<placeholder",
2753   "%*s<placeholder",
2754   "%*s<popup",
2755   "%*s<menuitem",
2756   "%*s<toolitem",
2757   "%*s<separator",
2758   "%*s<accelerator"
2759 };
2760
2761 static const gchar *const close_tag_format[] = {
2762   "%*s</UNDECIDED>\n",
2763   "%*s</ui>\n",
2764   "%*s</menubar>\n",
2765   "%*s</menu>\n",
2766   "%*s</toolbar>\n",
2767   "%*s</placeholder>\n",
2768   "%*s</placeholder>\n",
2769   "%*s</popup>\n",
2770   NULL,
2771   NULL,
2772   NULL,
2773   NULL
2774 };
2775
2776 static void
2777 print_node (GtkUIManager *self,
2778             GNode        *node,
2779             gint          indent_level,
2780             GString      *buffer)
2781 {
2782   Node  *mnode;
2783   GNode *child;
2784
2785   mnode = node->data;
2786
2787   g_string_append_printf (buffer, open_tag_format[mnode->type],
2788                           indent_level, "");
2789
2790   if (mnode->type != NODE_TYPE_ROOT)
2791     {
2792       if (mnode->name)
2793         g_string_append_printf (buffer, " name=\"%s\"", mnode->name);
2794       
2795       if (mnode->action_name)
2796         g_string_append_printf (buffer, " action=\"%s\"",
2797                                 g_quark_to_string (mnode->action_name));
2798     }
2799
2800   g_string_append (buffer,
2801                    close_tag_format[mnode->type] ? ">\n" : "/>\n");
2802
2803   for (child = node->children; child != NULL; child = child->next)
2804     print_node (self, child, indent_level + 2, buffer);
2805
2806   if (close_tag_format[mnode->type])
2807     g_string_append_printf (buffer, close_tag_format[mnode->type],
2808                             indent_level, "");
2809 }
2810
2811 /**
2812  * gtk_ui_manager_get_ui:
2813  * @self: a #GtkUIManager
2814  * 
2815  * Creates a <link linkend="XML-UI">UI definition</link> of the merged UI.
2816  * 
2817  * Return value: A newly allocated string containing an XML representation of 
2818  * the merged UI.
2819  *
2820  * Since: 2.4
2821  **/
2822 gchar *
2823 gtk_ui_manager_get_ui (GtkUIManager *self)
2824 {
2825   GString *buffer;
2826
2827   buffer = g_string_new (NULL);
2828
2829   gtk_ui_manager_ensure_update (self); 
2830  
2831   print_node (self, self->private_data->root_node, 0, buffer);  
2832
2833   return g_string_free (buffer, FALSE);
2834 }
2835
2836 #ifdef G_OS_WIN32
2837
2838 #undef gtk_ui_manager_add_ui_from_file
2839
2840 guint
2841 gtk_ui_manager_add_ui_from_file (GtkUIManager *self,
2842                                  const gchar  *filename,
2843                                  GError      **error)
2844 {
2845   gchar *utf8_filename = g_locale_to_utf8 (filename, -1, NULL, NULL, error);
2846   guint retval;
2847
2848   if (utf8_filename == NULL)
2849     return 0;
2850   
2851   retval = gtk_ui_manager_add_ui_from_file_utf8 (self, utf8_filename, error);
2852
2853   g_free (utf8_filename);
2854
2855   return retval;
2856 }
2857
2858 #endif
2859
2860 #define __GTK_UI_MANAGER_C__
2861 #include "gtkaliasdef.c"