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