]> Pileus Git - ~andy/gtk/blob - gtk/gtkuimanager.c
add clarification stating that the construct_child function is responsible
[~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 (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   g_return_if_fail (GTK_IS_UI_MANAGER (self));
700   g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
701   g_return_if_fail (g_list_find (self->private_data->action_groups, 
702                                  action_group) == NULL);
703
704   g_object_ref (action_group);
705   self->private_data->action_groups = 
706     g_list_insert (self->private_data->action_groups, action_group, pos);
707   g_object_connect (action_group,
708                     "object_signal::connect_proxy", G_CALLBACK (cb_proxy_connect_proxy), self,
709                     "object_signal::disconnect_proxy", G_CALLBACK (cb_proxy_disconnect_proxy), self,
710                     "object_signal::pre_activate", G_CALLBACK (cb_proxy_pre_activate), self,
711                     "object_signal::post_activate", G_CALLBACK (cb_proxy_post_activate), self,
712                     NULL);
713
714   /* dirty all nodes, as action bindings may change */
715   dirty_all_nodes (self);
716
717   g_signal_emit (self, ui_manager_signals[ACTIONS_CHANGED], 0);
718 }
719
720 /**
721  * gtk_ui_manager_remove_action_group:
722  * @self: a #GtkUIManager object
723  * @action_group: the action group to be removed
724  * 
725  * Removes an action group from the list of action groups associated 
726  * with @self.
727  *
728  * Since: 2.4
729  **/
730 void
731 gtk_ui_manager_remove_action_group (GtkUIManager   *self,
732                                     GtkActionGroup *action_group)
733 {
734   g_return_if_fail (GTK_IS_UI_MANAGER (self));
735   g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
736   g_return_if_fail (g_list_find (self->private_data->action_groups, 
737                                  action_group) != NULL);
738
739   self->private_data->action_groups =
740     g_list_remove (self->private_data->action_groups, action_group);
741
742   g_object_disconnect (action_group,
743                        "any_signal::connect_proxy", G_CALLBACK (cb_proxy_connect_proxy), self,
744                        "any_signal::disconnect_proxy", G_CALLBACK (cb_proxy_disconnect_proxy), self,
745                        "any_signal::pre_activate", G_CALLBACK (cb_proxy_pre_activate), self,
746                        "any_signal::post_activate", G_CALLBACK (cb_proxy_post_activate), self, 
747                        NULL);
748   g_object_unref (action_group);
749
750   /* dirty all nodes, as action bindings may change */
751   dirty_all_nodes (self);
752
753   g_signal_emit (self, ui_manager_signals[ACTIONS_CHANGED], 0);
754 }
755
756 /**
757  * gtk_ui_manager_get_action_groups:
758  * @self: a #GtkUIManager object
759  * 
760  * Returns the list of action groups associated with @self.
761  *
762  * Return value: a #GList of action groups. The list is owned by GTK+ 
763  *   and should not be modified.
764  *
765  * Since: 2.4
766  **/
767 GList *
768 gtk_ui_manager_get_action_groups (GtkUIManager *self)
769 {
770   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
771
772   return self->private_data->action_groups;
773 }
774
775 /**
776  * gtk_ui_manager_get_accel_group:
777  * @self: a #GtkUIManager object
778  * 
779  * Returns the #GtkAccelGroup associated with @self.
780  *
781  * Return value: the #GtkAccelGroup.
782  *
783  * Since: 2.4
784  **/
785 GtkAccelGroup *
786 gtk_ui_manager_get_accel_group (GtkUIManager *self)
787 {
788   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
789
790   return self->private_data->accel_group;
791 }
792
793 /**
794  * gtk_ui_manager_get_widget:
795  * @self: a #GtkUIManager
796  * @path: a path
797  * 
798  * Looks up a widget by following a path. 
799  * The path consists of the names specified in the XML description of the UI. 
800  * separated by '/'. Elements which don't have a name or action attribute in 
801  * the XML (e.g. &lt;popup&gt;) can be addressed by their XML element name 
802  * (e.g. "popup"). The root element ("/ui") can be omitted in the path.
803  *
804  * Note that the widget found by following a path that ends in a &lt;menu&gt;
805  * element is the menuitem to which the menu is attached, not the menu itself.
806  *
807  * Also note that the widgets constructed by a ui manager are not tied to 
808  * the lifecycle of the ui manager. If you add the widgets returned by this 
809  * function to some container or explicitly ref them, they will survive the
810  * destruction of the ui manager.
811  * 
812  * Return value: the widget found by following the path, or %NULL if no widget
813  *   was found.
814  *
815  * Since: 2.4
816  **/
817 GtkWidget *
818 gtk_ui_manager_get_widget (GtkUIManager *self,
819                            const gchar  *path)
820 {
821   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
822   g_return_val_if_fail (path != NULL, NULL);
823
824   return GTK_UI_MANAGER_GET_CLASS (self)->get_widget (self, path);
825 }
826
827 typedef struct {
828   GtkUIManagerItemType types;
829   GSList *list;
830 } ToplevelData;
831
832 static void
833 collect_toplevels (GNode   *node, 
834                    gpointer user_data)
835 {
836   ToplevelData *data = user_data;
837
838   if (NODE_INFO (node)->proxy)
839     {
840       switch (NODE_INFO (node)->type) 
841         {
842         case NODE_TYPE_MENUBAR:
843           if (data->types & GTK_UI_MANAGER_MENUBAR)
844         data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
845           break;
846         case NODE_TYPE_TOOLBAR:
847       if (data->types & GTK_UI_MANAGER_TOOLBAR)
848         data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
849       break;
850         case NODE_TYPE_POPUP:
851           if (data->types & GTK_UI_MANAGER_POPUP)
852             data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
853           break;
854         default: ;
855         }
856     }
857 }
858
859 /**
860  * gtk_ui_manager_get_toplevels:
861  * @self: a #GtkUIManager
862  * @types: specifies the types of toplevel widgets to include. Allowed
863  *   types are #GTK_UI_MANAGER_MENUBAR, #GTK_UI_MANAGER_TOOLBAR and
864  *   #GTK_UI_MANAGER_POPUP.
865  * 
866  * Obtains a list of all toplevel widgets of the requested types.
867  * 
868  * Return value: a newly-allocated of all toplevel widgets of the requested 
869  * types. 
870  *
871  * Since: 2.4
872  **/
873 GSList *
874 gtk_ui_manager_get_toplevels (GtkUIManager         *self,
875                               GtkUIManagerItemType  types)
876 {
877   ToplevelData data;
878
879   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
880   g_return_val_if_fail ((~(GTK_UI_MANAGER_MENUBAR | 
881                            GTK_UI_MANAGER_TOOLBAR |
882                            GTK_UI_MANAGER_POPUP) & types) == 0, NULL);
883   
884       
885   data.types = types;
886   data.list = NULL;
887
888   g_node_children_foreach (self->private_data->root_node, 
889                            G_TRAVERSE_ALL, 
890                            collect_toplevels, &data);
891
892   return data.list;
893 }
894
895
896 /**
897  * gtk_ui_manager_get_action:
898  * @self: a #GtkUIManager
899  * @path: a path
900  * 
901  * Looks up an action by following a path. See gtk_ui_manager_get_widget()
902  * for more information about paths.
903  * 
904  * Return value: the action whose proxy widget is found by following the path, 
905  *     or %NULL if no widget was found.
906  *
907  * Since: 2.4
908  **/
909 GtkAction *
910 gtk_ui_manager_get_action (GtkUIManager *self,
911                            const gchar  *path)
912 {
913   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), NULL);
914   g_return_val_if_fail (path != NULL, NULL);
915
916   return GTK_UI_MANAGER_GET_CLASS (self)->get_action (self, path);
917 }
918
919 static GNode *
920 get_child_node (GtkUIManager *self, 
921                 GNode        *parent,
922                 GNode        *sibling,
923                 const gchar  *childname, 
924                 gint          childname_length,
925                 NodeType      node_type,
926                 gboolean      create, 
927                 gboolean      top)
928 {
929   GNode *child = NULL;
930
931   if (parent)
932     {
933       if (childname)
934         {
935           for (child = parent->children; child != NULL; child = child->next)
936             {
937               if (NODE_INFO (child)->name &&
938                   strlen (NODE_INFO (child)->name) == childname_length &&
939                   !strncmp (NODE_INFO (child)->name, childname, childname_length))
940                 {
941                   /* if undecided about node type, set it */
942                   if (NODE_INFO (child)->type == NODE_TYPE_UNDECIDED)
943                     NODE_INFO (child)->type = node_type;
944                   
945                   /* warn about type mismatch */
946                   if (NODE_INFO (child)->type != NODE_TYPE_UNDECIDED &&
947                       node_type != NODE_TYPE_UNDECIDED &&
948                       NODE_INFO (child)->type != node_type)
949                     g_warning ("node type doesn't match %d (%s is type %d)",
950                                node_type, 
951                                NODE_INFO (child)->name,
952                                NODE_INFO (child)->type);
953                   
954                   return child;
955                 }
956             }
957         }
958       if (!child && create)
959         {
960           Node *mnode;
961           
962           mnode = g_slice_new0 (Node);
963           mnode->type = node_type;
964           mnode->name = g_strndup (childname, childname_length);
965
966           if (sibling)
967             {
968               if (top)
969                 child = g_node_insert_before (parent, sibling, 
970                                               g_node_new (mnode));
971               else
972                 child = g_node_insert_after (parent, sibling, 
973                                              g_node_new (mnode));
974             }
975           else
976             {
977               if (top)
978                 child = g_node_prepend_data (parent, mnode);
979               else
980                 child = g_node_append_data (parent, mnode);
981             }
982
983           mark_node_dirty (child);
984         }
985     }
986   else
987     {
988       /* handle root node */
989       if (self->private_data->root_node)
990         {
991           child = self->private_data->root_node;
992           if (strncmp (NODE_INFO (child)->name, childname, childname_length) != 0)
993             g_warning ("root node name '%s' doesn't match '%s'",
994                        childname, NODE_INFO (child)->name);
995           if (NODE_INFO (child)->type != NODE_TYPE_ROOT)
996             g_warning ("base element must be of type ROOT");
997         }
998       else if (create)
999         {
1000           Node *mnode;
1001
1002           mnode = g_slice_new0 (Node);
1003           mnode->type = node_type;
1004           mnode->name = g_strndup (childname, childname_length);
1005           mnode->dirty = TRUE;
1006           
1007           child = self->private_data->root_node = g_node_new (mnode);
1008         }
1009     }
1010
1011   return child;
1012 }
1013
1014 static GNode *
1015 get_node (GtkUIManager *self, 
1016           const gchar  *path,
1017           NodeType      node_type, 
1018           gboolean      create)
1019 {
1020   const gchar *pos, *end;
1021   GNode *parent, *node;
1022   
1023   if (strncmp ("/ui", path, 3) == 0)
1024     path += 3;
1025   
1026   end = path + strlen (path);
1027   pos = path;
1028   parent = node = NULL;
1029   while (pos < end)
1030     {
1031       const gchar *slash;
1032       gsize length;
1033
1034       slash = strchr (pos, '/');
1035       if (slash)
1036         length = slash - pos;
1037       else
1038         length = strlen (pos);
1039
1040       node = get_child_node (self, parent, NULL, pos, length, NODE_TYPE_UNDECIDED,
1041                              create, FALSE);
1042       if (!node)
1043         return NULL;
1044
1045       pos += length + 1; /* move past the node name and the slash too */
1046       parent = node;
1047     }
1048
1049   if (node != NULL && NODE_INFO (node)->type == NODE_TYPE_UNDECIDED)
1050     NODE_INFO (node)->type = node_type;
1051
1052   return node;
1053 }
1054
1055 static void
1056 node_ui_reference_free (gpointer data)
1057 {
1058   g_slice_free (NodeUIReference, data);
1059 }
1060
1061 static gboolean
1062 free_node (GNode *node)
1063 {
1064   Node *info = NODE_INFO (node);
1065   
1066   g_list_foreach (info->uifiles, (GFunc) node_ui_reference_free, NULL);
1067   g_list_free (info->uifiles);
1068
1069   if (info->action)
1070     g_object_unref (info->action);
1071   if (info->proxy)
1072     g_object_unref (info->proxy);
1073   if (info->extra)
1074     g_object_unref (info->extra);
1075   g_free (info->name);
1076   g_slice_free (Node, info);
1077
1078   return FALSE;
1079 }
1080
1081 /**
1082  * gtk_ui_manager_new_merge_id:
1083  * @self: a #GtkUIManager
1084  * 
1085  * Returns an unused merge id, suitable for use with 
1086  * gtk_ui_manager_add_ui().
1087  * 
1088  * Return value: an unused merge id.
1089  *
1090  * Since: 2.4
1091  **/
1092 guint
1093 gtk_ui_manager_new_merge_id (GtkUIManager *self)
1094 {
1095   self->private_data->last_merge_id++;
1096
1097   return self->private_data->last_merge_id;
1098 }
1099
1100 static void
1101 node_prepend_ui_reference (GNode  *gnode,
1102                            guint   merge_id, 
1103                            GQuark  action_quark)
1104 {
1105   Node *node = NODE_INFO (gnode);
1106   NodeUIReference *reference = NULL;
1107
1108   if (node->uifiles && 
1109       ((NodeUIReference *)node->uifiles->data)->merge_id == merge_id)
1110     reference = node->uifiles->data;
1111   else
1112     {
1113       reference = g_slice_new (NodeUIReference);
1114       node->uifiles = g_list_prepend (node->uifiles, reference);
1115     }
1116
1117   reference->merge_id = merge_id;
1118   reference->action_quark = action_quark;
1119
1120   mark_node_dirty (gnode);
1121 }
1122
1123 static void
1124 node_remove_ui_reference (GNode  *gnode,
1125                           guint  merge_id)
1126 {
1127   Node *node = NODE_INFO (gnode);
1128   GList *p;
1129   
1130   for (p = node->uifiles; p != NULL; p = p->next)
1131     {
1132       NodeUIReference *reference = p->data;
1133       
1134       if (reference->merge_id == merge_id)
1135         {
1136           if (p == node->uifiles)
1137             mark_node_dirty (gnode);
1138           node->uifiles = g_list_delete_link (node->uifiles, p);
1139           g_slice_free (NodeUIReference, reference);
1140
1141           break;
1142         }
1143     }
1144 }
1145
1146 /* -------------------- The UI file parser -------------------- */
1147
1148 typedef enum
1149 {
1150   STATE_START,
1151   STATE_ROOT,
1152   STATE_MENU,
1153   STATE_TOOLBAR,
1154   STATE_MENUITEM,
1155   STATE_TOOLITEM,
1156   STATE_ACCELERATOR,
1157   STATE_END
1158 } ParseState;
1159
1160 typedef struct _ParseContext ParseContext;
1161 struct _ParseContext
1162 {
1163   ParseState state;
1164   ParseState prev_state;
1165
1166   GtkUIManager *self;
1167
1168   GNode *current;
1169
1170   guint merge_id;
1171 };
1172
1173 static void
1174 start_element_handler (GMarkupParseContext *context,
1175                        const gchar         *element_name,
1176                        const gchar        **attribute_names,
1177                        const gchar        **attribute_values,
1178                        gpointer             user_data,
1179                        GError             **error)
1180 {
1181   ParseContext *ctx = user_data;
1182   GtkUIManager *self = ctx->self;
1183
1184   gint i;
1185   const gchar *node_name;
1186   const gchar *action;
1187   GQuark action_quark;
1188   gboolean top;
1189   gboolean expand = FALSE;
1190   
1191   gboolean raise_error = TRUE;
1192
1193   node_name = NULL;
1194   action = NULL;
1195   action_quark = 0;
1196   top = FALSE;
1197
1198   for (i = 0; attribute_names[i] != NULL; i++)
1199     {
1200       if (!strcmp (attribute_names[i], "name"))
1201         {
1202           node_name = attribute_values[i];
1203         }
1204       else if (!strcmp (attribute_names[i], "action"))
1205         {
1206           action = attribute_values[i];
1207           action_quark = g_quark_from_string (attribute_values[i]);
1208         }
1209       else if (!strcmp (attribute_names[i], "position"))
1210         {
1211           top = !strcmp (attribute_values[i], "top");
1212         }
1213       else if (!strcmp (attribute_names[i], "expand"))
1214         {
1215           expand = !strcmp (attribute_values[i], "true");
1216         }
1217       else
1218         {
1219           gint line_number, char_number;
1220           
1221           g_markup_parse_context_get_position (context,
1222                                                &line_number, &char_number);
1223           g_set_error (error,
1224                        G_MARKUP_ERROR,
1225                        G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1226                        _("Unknown attribute '%s' on line %d char %d"),
1227                        attribute_names[i],
1228                        line_number, char_number);
1229           return;
1230         }
1231     }
1232
1233   /* Work out a name for this node.  Either the name attribute, or
1234    * the action, or the element name */
1235   if (node_name == NULL) 
1236     {
1237       if (action != NULL)
1238         node_name = action;
1239       else 
1240         node_name = element_name;
1241     }
1242
1243   switch (element_name[0])
1244     {
1245     case 'a':
1246       if (ctx->state == STATE_ROOT && !strcmp (element_name, "accelerator"))
1247         {
1248           ctx->state = STATE_ACCELERATOR;
1249           ctx->current = get_child_node (self, ctx->current, NULL,
1250                                          node_name, strlen (node_name),
1251                                          NODE_TYPE_ACCELERATOR,
1252                                          TRUE, FALSE);
1253           if (NODE_INFO (ctx->current)->action_name == 0)
1254             NODE_INFO (ctx->current)->action_name = action_quark;
1255
1256           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1257
1258           raise_error = FALSE;
1259         }
1260       break;
1261     case 'u':
1262       if (ctx->state == STATE_START && !strcmp (element_name, "ui"))
1263         {
1264           ctx->state = STATE_ROOT;
1265           ctx->current = self->private_data->root_node;
1266           raise_error = FALSE;
1267
1268           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1269         }
1270       break;
1271     case 'm':
1272       if (ctx->state == STATE_ROOT && !strcmp (element_name, "menubar"))
1273         {
1274           ctx->state = STATE_MENU;
1275           ctx->current = get_child_node (self, ctx->current, NULL,
1276                                          node_name, strlen (node_name),
1277                                          NODE_TYPE_MENUBAR,
1278                                          TRUE, FALSE);
1279           if (NODE_INFO (ctx->current)->action_name == 0)
1280             NODE_INFO (ctx->current)->action_name = action_quark;
1281
1282           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1283           mark_node_dirty (ctx->current);
1284
1285           raise_error = FALSE;
1286         }
1287       else if (ctx->state == STATE_MENU && !strcmp (element_name, "menu"))
1288         {
1289           ctx->current = get_child_node (self, ctx->current, NULL,
1290                                          node_name, strlen (node_name),
1291                                          NODE_TYPE_MENU,
1292                                          TRUE, top);
1293           if (NODE_INFO (ctx->current)->action_name == 0)
1294             NODE_INFO (ctx->current)->action_name = action_quark;
1295
1296           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1297           
1298           raise_error = FALSE;
1299         }
1300       else if (ctx->state == STATE_TOOLITEM && !strcmp (element_name, "menu"))
1301         {
1302           ctx->state = STATE_MENU;
1303           
1304           ctx->current = get_child_node (self, g_node_last_child (ctx->current), NULL,
1305                                          node_name, strlen (node_name),
1306                                          NODE_TYPE_MENU,
1307                                          TRUE, top);
1308           if (NODE_INFO (ctx->current)->action_name == 0)
1309             NODE_INFO (ctx->current)->action_name = action_quark;
1310
1311           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1312           
1313           raise_error = FALSE;
1314         }
1315       else if (ctx->state == STATE_MENU && !strcmp (element_name, "menuitem"))
1316         {
1317           GNode *node;
1318
1319           ctx->state = STATE_MENUITEM;
1320           node = get_child_node (self, ctx->current, NULL,
1321                                  node_name, strlen (node_name),
1322                                  NODE_TYPE_MENUITEM,
1323                                  TRUE, top);
1324           if (NODE_INFO (node)->action_name == 0)
1325             NODE_INFO (node)->action_name = action_quark;
1326           
1327           node_prepend_ui_reference (node, ctx->merge_id, action_quark);
1328           
1329           raise_error = FALSE;
1330         }
1331       break;
1332     case 'p':
1333       if (ctx->state == STATE_ROOT && !strcmp (element_name, "popup"))
1334         {
1335           ctx->state = STATE_MENU;
1336           ctx->current = get_child_node (self, ctx->current, NULL,
1337                                          node_name, strlen (node_name),
1338                                          NODE_TYPE_POPUP,
1339                                          TRUE, FALSE);
1340           if (NODE_INFO (ctx->current)->action_name == 0)
1341             NODE_INFO (ctx->current)->action_name = action_quark;
1342           
1343           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1344           
1345           raise_error = FALSE;
1346         }
1347       else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
1348                !strcmp (element_name, "placeholder"))
1349         {
1350           if (ctx->state == STATE_TOOLBAR)
1351             ctx->current = get_child_node (self, ctx->current, NULL,
1352                                            node_name, strlen (node_name),
1353                                            NODE_TYPE_TOOLBAR_PLACEHOLDER,
1354                                            TRUE, top);
1355           else
1356             ctx->current = get_child_node (self, ctx->current, NULL,
1357                                            node_name, strlen (node_name),
1358                                            NODE_TYPE_MENU_PLACEHOLDER,
1359                                            TRUE, top);
1360           
1361           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1362           
1363           raise_error = FALSE;
1364         }
1365       break;
1366     case 's':
1367       if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
1368           !strcmp (element_name, "separator"))
1369         {
1370           GNode *node;
1371           gint length;
1372
1373           if (ctx->state == STATE_TOOLBAR)
1374             ctx->state = STATE_TOOLITEM;
1375           else
1376             ctx->state = STATE_MENUITEM;
1377           if (!strcmp (node_name, "separator"))
1378             {
1379               node_name = NULL;
1380               length = 0;
1381             }
1382           else
1383             length = strlen (node_name);
1384           node = get_child_node (self, ctx->current, NULL,
1385                                  node_name, length,
1386                                  NODE_TYPE_SEPARATOR,
1387                                  TRUE, top);
1388
1389           NODE_INFO (node)->expand = expand;
1390
1391           if (NODE_INFO (node)->action_name == 0)
1392             NODE_INFO (node)->action_name = action_quark;
1393
1394           node_prepend_ui_reference (node, ctx->merge_id, action_quark);
1395           
1396           raise_error = FALSE;
1397         }
1398       break;
1399     case 't':
1400       if (ctx->state == STATE_ROOT && !strcmp (element_name, "toolbar"))
1401         {
1402           ctx->state = STATE_TOOLBAR;
1403           ctx->current = get_child_node (self, ctx->current, NULL,
1404                                          node_name, strlen (node_name),
1405                                          NODE_TYPE_TOOLBAR,
1406                                          TRUE, FALSE);
1407           if (NODE_INFO (ctx->current)->action_name == 0)
1408             NODE_INFO (ctx->current)->action_name = action_quark;
1409           
1410           node_prepend_ui_reference (ctx->current, ctx->merge_id, action_quark);
1411           
1412           raise_error = FALSE;
1413         }
1414       else if (ctx->state == STATE_TOOLBAR && !strcmp (element_name, "toolitem"))
1415         {
1416           GNode *node;
1417
1418           ctx->state = STATE_TOOLITEM;
1419           node = get_child_node (self, ctx->current, NULL,
1420                                 node_name, strlen (node_name),
1421                                  NODE_TYPE_TOOLITEM,
1422                                  TRUE, top);
1423           if (NODE_INFO (node)->action_name == 0)
1424             NODE_INFO (node)->action_name = action_quark;
1425           
1426           node_prepend_ui_reference (node, ctx->merge_id, action_quark);
1427
1428           raise_error = FALSE;
1429         }
1430       break;
1431     default:
1432       break;
1433     }
1434   if (raise_error)
1435     {
1436       gint line_number, char_number;
1437  
1438       g_markup_parse_context_get_position (context,
1439                                            &line_number, &char_number);
1440       g_set_error (error,
1441                    G_MARKUP_ERROR,
1442                    G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1443                    _("Unexpected start tag '%s' on line %d char %d"),
1444                    element_name,
1445                    line_number, char_number);
1446     }
1447 }
1448
1449 static void
1450 end_element_handler (GMarkupParseContext *context,
1451                      const gchar         *element_name,
1452                      gpointer             user_data,
1453                      GError             **error)
1454 {
1455   ParseContext *ctx = user_data;
1456
1457   switch (ctx->state)
1458     {
1459     case STATE_START:
1460     case STATE_END:
1461       /* no need to GError here, GMarkup already catches this */
1462       break;
1463     case STATE_ROOT:
1464       ctx->current = NULL;
1465       ctx->state = STATE_END;
1466       break;
1467     case STATE_MENU:
1468     case STATE_TOOLBAR:
1469     case STATE_ACCELERATOR:
1470       ctx->current = ctx->current->parent;
1471       if (NODE_INFO (ctx->current)->type == NODE_TYPE_ROOT) 
1472         ctx->state = STATE_ROOT;
1473       else if (NODE_INFO (ctx->current)->type == NODE_TYPE_TOOLITEM)
1474         {
1475           ctx->current = ctx->current->parent;
1476           ctx->state = STATE_TOOLITEM;
1477         }
1478       /* else, stay in same state */
1479       break;
1480     case STATE_MENUITEM:
1481       ctx->state = STATE_MENU;
1482       break;
1483     case STATE_TOOLITEM:
1484       ctx->state = STATE_TOOLBAR;
1485       break;
1486     }
1487 }
1488
1489 static void
1490 cleanup (GMarkupParseContext *context,
1491          GError              *error,
1492          gpointer             user_data)
1493 {
1494   ParseContext *ctx = user_data;
1495
1496   ctx->current = NULL;
1497   /* should also walk through the tree and get rid of nodes related to
1498    * this UI file's tag */
1499
1500   gtk_ui_manager_remove_ui (ctx->self, ctx->merge_id);
1501 }
1502
1503 static gboolean
1504 xml_isspace (char c)
1505 {
1506   return c == ' ' || c == '\t' || c == '\n' || c == '\r';
1507 }
1508
1509 static void 
1510 text_handler (GMarkupParseContext *context,
1511               const gchar         *text,
1512               gsize                text_len,  
1513               gpointer             user_data,
1514               GError             **error)
1515 {
1516   const gchar *p;
1517   const gchar *end;
1518
1519   p = text;
1520   end = text + text_len;
1521   while (p != end && xml_isspace (*p))
1522     ++p;
1523   
1524   if (p != end)
1525     {
1526       gint line_number, char_number;
1527       
1528       g_markup_parse_context_get_position (context,
1529                                            &line_number, &char_number);
1530       g_set_error (error,
1531                    G_MARKUP_ERROR,
1532                    G_MARKUP_ERROR_INVALID_CONTENT,
1533                    _("Unexpected character data on line %d char %d"),
1534                    line_number, char_number);
1535     }
1536 }
1537
1538
1539 static const GMarkupParser ui_parser = {
1540   start_element_handler,
1541   end_element_handler,
1542   text_handler,
1543   NULL,
1544   cleanup
1545 };
1546
1547 static guint
1548 add_ui_from_string (GtkUIManager *self,
1549                     const gchar  *buffer, 
1550                     gssize        length,
1551                     gboolean      needs_root,
1552                     GError      **error)
1553 {
1554   ParseContext ctx = { 0 };
1555   GMarkupParseContext *context;
1556
1557   ctx.state = STATE_START;
1558   ctx.self = self;
1559   ctx.current = NULL;
1560   ctx.merge_id = gtk_ui_manager_new_merge_id (self);
1561
1562   context = g_markup_parse_context_new (&ui_parser, 0, &ctx, NULL);
1563
1564   if (needs_root)
1565     if (!g_markup_parse_context_parse (context, "<ui>", -1, error))
1566       goto out;
1567
1568   if (!g_markup_parse_context_parse (context, buffer, length, error))
1569     goto out;
1570
1571   if (needs_root)
1572     if (!g_markup_parse_context_parse (context, "</ui>", -1, error))
1573       goto out;
1574
1575   if (!g_markup_parse_context_end_parse (context, error))
1576     goto out;
1577
1578   g_markup_parse_context_free (context);
1579
1580   queue_update (self);
1581
1582   g_object_notify (G_OBJECT (self), "ui");
1583
1584   return ctx.merge_id;
1585
1586  out:
1587
1588   g_markup_parse_context_free (context);
1589
1590   return 0;
1591 }
1592
1593 /**
1594  * gtk_ui_manager_add_ui_from_string:
1595  * @self: a #GtkUIManager object
1596  * @buffer: the string to parse
1597  * @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
1598  * @error: return location for an error
1599  * 
1600  * Parses a string containing a <link linkend="XML-UI">UI definition</link> and 
1601  * merges it with the current contents of @self. An enclosing &lt;ui&gt; 
1602  * element is added if it is missing.
1603  * 
1604  * Return value: The merge id for the merged UI. The merge id can be used
1605  *   to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1606  *   the return value is 0.
1607  *
1608  * Since: 2.4
1609  **/
1610 guint
1611 gtk_ui_manager_add_ui_from_string (GtkUIManager *self,
1612                                    const gchar  *buffer, 
1613                                    gssize        length,
1614                                    GError      **error)
1615 {
1616   gboolean needs_root = TRUE;
1617   const gchar *p;
1618   const gchar *end;
1619
1620   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), 0);
1621   g_return_val_if_fail (buffer != NULL, 0);
1622
1623   if (length < 0)
1624     length = strlen (buffer);
1625
1626   p = buffer;
1627   end = buffer + length;
1628   while (p != end && xml_isspace (*p))
1629     ++p;
1630
1631   if (end - p >= 4 && strncmp (p, "<ui>", 4) == 0)
1632     needs_root = FALSE;
1633   
1634   return add_ui_from_string (self, buffer, length, needs_root, error);
1635 }
1636
1637 /**
1638  * gtk_ui_manager_add_ui_from_file:
1639  * @self: a #GtkUIManager object
1640  * @filename: the name of the file to parse 
1641  * @error: return location for an error
1642  * 
1643  * Parses a file containing a <link linkend="XML-UI">UI definition</link> and 
1644  * merges it with the current contents of @self. 
1645  * 
1646  * Return value: The merge id for the merged UI. The merge id can be used
1647  *   to unmerge the UI with gtk_ui_manager_remove_ui(). If an error occurred,
1648  *   the return value is 0.
1649  *
1650  * Since: 2.4
1651  **/
1652 guint
1653 gtk_ui_manager_add_ui_from_file (GtkUIManager *self,
1654                                  const gchar  *filename,
1655                                  GError      **error)
1656 {
1657   gchar *buffer;
1658   gsize length;
1659   guint res;
1660
1661   g_return_val_if_fail (GTK_IS_UI_MANAGER (self), 0);
1662
1663   if (!g_file_get_contents (filename, &buffer, &length, error))
1664     return 0;
1665
1666   res = add_ui_from_string (self, buffer, length, FALSE, error);
1667   g_free (buffer);
1668
1669   return res;
1670 }
1671
1672 /**
1673  * gtk_ui_manager_add_ui:
1674  * @self: a #GtkUIManager
1675  * @merge_id: the merge id for the merged UI, see gtk_ui_manager_new_merge_id()
1676  * @path: a path
1677  * @name: the name for the added UI element
1678  * @action: the name of the action to be proxied, or %NULL to add a separator
1679  * @type: the type of UI element to add.
1680  * @top: if %TRUE, the UI element is added before its siblings, otherwise it 
1681  *   is added after its siblings.
1682  * 
1683  * Adds a UI element to the current contents of @self. 
1684  *
1685  * If @type is %GTK_UI_MANAGER_AUTO, GTK+ inserts a menuitem, toolitem or 
1686  * separator if such an element can be inserted at the place determined by 
1687  * @path. Otherwise @type must indicate an element that can be inserted at 
1688  * the place determined by @path.
1689  *
1690  * If @path points to a menuitem or toolitem, the new element will be inserted
1691  * before or after this item, depending on @top.
1692  * 
1693  * Since: 2.4
1694  **/
1695 void
1696 gtk_ui_manager_add_ui (GtkUIManager        *self,
1697                        guint                merge_id,
1698                        const gchar         *path,
1699                        const gchar         *name,
1700                        const gchar         *action,
1701                        GtkUIManagerItemType type,
1702                        gboolean             top)
1703 {
1704   GNode *node;
1705   GNode *sibling;
1706   GNode *child;
1707   NodeType node_type;
1708   GQuark action_quark = 0;
1709
1710   g_return_if_fail (GTK_IS_UI_MANAGER (self));  
1711   g_return_if_fail (merge_id > 0);
1712   g_return_if_fail (name != NULL || type == GTK_UI_MANAGER_SEPARATOR);
1713
1714   node = get_node (self, path, NODE_TYPE_UNDECIDED, FALSE);
1715   sibling = NULL;
1716
1717   if (node == NULL)
1718     return;
1719
1720   node_type = NODE_TYPE_UNDECIDED;
1721
1722  reswitch:
1723   switch (NODE_INFO (node)->type) 
1724     {
1725     case NODE_TYPE_SEPARATOR:
1726     case NODE_TYPE_MENUITEM:
1727     case NODE_TYPE_TOOLITEM:
1728       sibling = node;
1729       node = node->parent;
1730       goto reswitch;
1731     case NODE_TYPE_MENUBAR:
1732     case NODE_TYPE_MENU:
1733     case NODE_TYPE_POPUP:
1734     case NODE_TYPE_MENU_PLACEHOLDER:
1735       switch (type) 
1736         {
1737         case GTK_UI_MANAGER_AUTO:
1738           if (action != NULL)
1739               node_type = NODE_TYPE_MENUITEM;
1740           else
1741               node_type = NODE_TYPE_SEPARATOR;
1742           break;
1743         case GTK_UI_MANAGER_MENU:
1744           node_type = NODE_TYPE_MENU;
1745           break;
1746         case GTK_UI_MANAGER_MENUITEM:
1747           node_type = NODE_TYPE_MENUITEM;
1748           break;
1749         case GTK_UI_MANAGER_SEPARATOR:
1750           node_type = NODE_TYPE_SEPARATOR;
1751           break;
1752         case GTK_UI_MANAGER_PLACEHOLDER:
1753           node_type = NODE_TYPE_MENU_PLACEHOLDER;
1754           break;
1755         default: ;
1756           /* do nothing */
1757         }
1758       break;
1759     case NODE_TYPE_TOOLBAR:
1760     case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1761       switch (type) 
1762         {
1763         case GTK_UI_MANAGER_AUTO:
1764           if (action != NULL)
1765               node_type = NODE_TYPE_TOOLITEM;
1766           else
1767               node_type = NODE_TYPE_SEPARATOR;
1768           break;
1769         case GTK_UI_MANAGER_TOOLITEM:
1770           node_type = NODE_TYPE_TOOLITEM;
1771           break;
1772         case GTK_UI_MANAGER_SEPARATOR:
1773           node_type = NODE_TYPE_SEPARATOR;
1774           break;
1775         case GTK_UI_MANAGER_PLACEHOLDER:
1776           node_type = NODE_TYPE_TOOLBAR_PLACEHOLDER;
1777           break;
1778         default: ;
1779           /* do nothing */
1780         }
1781       break;
1782     case NODE_TYPE_ROOT:
1783       switch (type) 
1784         {
1785         case GTK_UI_MANAGER_MENUBAR:
1786           node_type = NODE_TYPE_MENUBAR;
1787           break;
1788         case GTK_UI_MANAGER_TOOLBAR:
1789           node_type = NODE_TYPE_TOOLBAR;
1790           break;
1791         case GTK_UI_MANAGER_POPUP:
1792           node_type = NODE_TYPE_POPUP;
1793           break;
1794         case GTK_UI_MANAGER_ACCELERATOR:
1795           node_type = NODE_TYPE_ACCELERATOR;
1796           break;
1797         default: ;
1798           /* do nothing */
1799         }
1800       break;
1801     default: ;
1802       /* do nothing */
1803     }
1804
1805   if (node_type == NODE_TYPE_UNDECIDED)
1806     {
1807       g_warning ("item type %d not suitable for adding at '%s'", 
1808                  type, path);
1809       return;
1810     }
1811    
1812   child = get_child_node (self, node, sibling,
1813                           name, name ? strlen (name) : 0,
1814                           node_type, TRUE, top);
1815
1816   if (action != NULL)
1817     action_quark = g_quark_from_string (action);
1818
1819   node_prepend_ui_reference (child, merge_id, action_quark);
1820
1821   if (NODE_INFO (child)->action_name == 0)
1822     NODE_INFO (child)->action_name = action_quark;
1823
1824   queue_update (self);
1825
1826   g_object_notify (G_OBJECT (self), "ui");      
1827 }
1828
1829 static gboolean
1830 remove_ui (GNode   *node, 
1831            gpointer user_data)
1832 {
1833   guint merge_id = GPOINTER_TO_UINT (user_data);
1834
1835   node_remove_ui_reference (node, merge_id);
1836
1837   return FALSE; /* continue */
1838 }
1839
1840 /**
1841  * gtk_ui_manager_remove_ui:
1842  * @self: a #GtkUIManager object
1843  * @merge_id: a merge id as returned by gtk_ui_manager_add_ui_from_string()
1844  * 
1845  * Unmerges the part of @self<!-- -->s content identified by @merge_id.
1846  *
1847  * Since: 2.4
1848  **/
1849 void
1850 gtk_ui_manager_remove_ui (GtkUIManager *self, 
1851                           guint         merge_id)
1852 {
1853   g_node_traverse (self->private_data->root_node, 
1854                    G_POST_ORDER, G_TRAVERSE_ALL, -1,
1855                    remove_ui, GUINT_TO_POINTER (merge_id));
1856
1857   queue_update (self);
1858
1859   g_object_notify (G_OBJECT (self), "ui");      
1860 }
1861
1862 /* -------------------- Updates -------------------- */
1863
1864
1865 static GtkAction *
1866 get_action_by_name (GtkUIManager *merge, 
1867                     const gchar  *action_name)
1868 {
1869   GList *tmp;
1870
1871   if (!action_name)
1872     return NULL;
1873   
1874   /* lookup name */
1875   for (tmp = merge->private_data->action_groups; tmp != NULL; tmp = tmp->next)
1876     {
1877       GtkActionGroup *action_group = tmp->data;
1878       GtkAction *action;
1879       
1880       action = gtk_action_group_get_action (action_group, action_name);
1881
1882       if (action)
1883         return action;
1884     }
1885
1886   return NULL;
1887 }
1888
1889 static gboolean
1890 find_menu_position (GNode      *node, 
1891                     GtkWidget **menushell_p, 
1892                     gint       *pos_p)
1893 {
1894   GtkWidget *menushell;
1895   gint pos = 0;
1896
1897   g_return_val_if_fail (node != NULL, FALSE);
1898   g_return_val_if_fail (NODE_INFO (node)->type == NODE_TYPE_MENU ||
1899                         NODE_INFO (node)->type == NODE_TYPE_POPUP ||
1900                         NODE_INFO (node)->type == NODE_TYPE_MENU_PLACEHOLDER ||
1901                         NODE_INFO (node)->type == NODE_TYPE_MENUITEM ||
1902                         NODE_INFO (node)->type == NODE_TYPE_SEPARATOR,
1903                         FALSE);
1904
1905   /* first sibling -- look at parent */
1906   if (node->prev == NULL)
1907     {
1908       GNode *parent;
1909       GList *siblings;
1910
1911       parent = node->parent;
1912       switch (NODE_INFO (parent)->type)
1913         {
1914         case NODE_TYPE_MENUBAR:
1915         case NODE_TYPE_POPUP:
1916           menushell = NODE_INFO (parent)->proxy;
1917           pos = 0;
1918           break;
1919         case NODE_TYPE_MENU:
1920           menushell = NODE_INFO (parent)->proxy;
1921           if (GTK_IS_MENU_ITEM (menushell))
1922             menushell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menushell));
1923           siblings = gtk_container_get_children (GTK_CONTAINER (menushell));
1924           if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
1925             pos = 1;
1926           else
1927             pos = 0;
1928           g_list_free (siblings);
1929           break;
1930         case NODE_TYPE_MENU_PLACEHOLDER:
1931           menushell = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
1932           g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
1933           pos = g_list_index (GTK_MENU_SHELL (menushell)->children,
1934                               NODE_INFO (parent)->proxy) + 1;
1935           break;
1936         default:
1937           g_warning ("%s: bad parent node type %d", G_STRLOC,
1938                      NODE_INFO (parent)->type);
1939           return FALSE;
1940         }
1941     }
1942   else
1943     {
1944       GtkWidget *prev_child;
1945       GNode *sibling;
1946
1947       sibling = node->prev;
1948       if (NODE_INFO (sibling)->type == NODE_TYPE_MENU_PLACEHOLDER)
1949         prev_child = NODE_INFO (sibling)->extra; /* second Separator */
1950       else
1951         prev_child = NODE_INFO (sibling)->proxy;
1952
1953       if (!GTK_IS_WIDGET (prev_child))
1954         return FALSE;
1955
1956       menushell = gtk_widget_get_parent (prev_child);
1957       if (!GTK_IS_MENU_SHELL (menushell))
1958         return FALSE;
1959
1960       pos = g_list_index (GTK_MENU_SHELL (menushell)->children, prev_child) + 1;
1961     }
1962
1963   if (menushell_p)
1964     *menushell_p = menushell;
1965   if (pos_p)
1966     *pos_p = pos;
1967
1968   return TRUE;
1969 }
1970
1971 static gboolean
1972 find_toolbar_position (GNode      *node, 
1973                        GtkWidget **toolbar_p, 
1974                        gint       *pos_p)
1975 {
1976   GtkWidget *toolbar;
1977   gint pos;
1978
1979   g_return_val_if_fail (node != NULL, FALSE);
1980   g_return_val_if_fail (NODE_INFO (node)->type == NODE_TYPE_TOOLBAR ||
1981                         NODE_INFO (node)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER ||
1982                         NODE_INFO (node)->type == NODE_TYPE_TOOLITEM ||
1983                         NODE_INFO (node)->type == NODE_TYPE_SEPARATOR,
1984                         FALSE);
1985   
1986   /* first sibling -- look at parent */
1987   if (node->prev == NULL)
1988     {
1989       GNode *parent;
1990
1991       parent = node->parent;
1992       switch (NODE_INFO (parent)->type)
1993         {
1994         case NODE_TYPE_TOOLBAR:
1995           toolbar = NODE_INFO (parent)->proxy;
1996           pos = 0;
1997           break;
1998         case NODE_TYPE_TOOLBAR_PLACEHOLDER:
1999           toolbar = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
2000           g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
2001           pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
2002                                             GTK_TOOL_ITEM (NODE_INFO (parent)->proxy)) + 1;
2003           break;
2004         default:
2005           g_warning ("%s: bad parent node type %d", G_STRLOC,
2006                      NODE_INFO (parent)->type);
2007           return FALSE;
2008         }
2009     }
2010   else
2011     {
2012       GtkWidget *prev_child;
2013       GNode *sibling;
2014
2015       sibling = node->prev;
2016       if (NODE_INFO (sibling)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
2017         prev_child = NODE_INFO (sibling)->extra; /* second Separator */
2018       else
2019         prev_child = NODE_INFO (sibling)->proxy;
2020
2021       if (!GTK_IS_WIDGET (prev_child))
2022         return FALSE;
2023
2024       toolbar = gtk_widget_get_parent (prev_child);
2025       if (!GTK_IS_TOOLBAR (toolbar))
2026         return FALSE;
2027
2028       pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
2029                                         GTK_TOOL_ITEM (prev_child)) + 1;
2030     }
2031   
2032   if (toolbar_p)
2033     *toolbar_p = toolbar;
2034   if (pos_p)
2035     *pos_p = pos;
2036
2037   return TRUE;
2038 }
2039
2040 /**
2041  * _gtk_menu_is_empty:
2042  * @menu: a #GtkMenu or %NULL
2043  * 
2044  * Determines whether @menu is empty. A menu is considered empty if it
2045  * the only visible children are tearoff menu items or "filler" menu 
2046  * items which were inserted to mark the menu as empty.
2047  * 
2048  * This function is used by #GtkAction.
2049  *
2050  * Return value: whether @menu is empty.
2051  **/
2052 gboolean
2053 _gtk_menu_is_empty (GtkWidget *menu)
2054 {
2055   GList *children, *cur;
2056   gboolean result = TRUE;
2057
2058   g_return_val_if_fail (menu == NULL || GTK_IS_MENU (menu), TRUE);
2059
2060   if (!menu)
2061     return FALSE;
2062
2063   children = gtk_container_get_children (GTK_CONTAINER (menu));
2064
2065   cur = children;
2066   while (cur) 
2067     {
2068       if (GTK_WIDGET_VISIBLE (cur->data))
2069         {
2070           if (!GTK_IS_TEAROFF_MENU_ITEM (cur->data) &&
2071               !g_object_get_data (cur->data, "gtk-empty-menu-item"))
2072             {
2073               result = FALSE;
2074               break;
2075             }
2076         }
2077       cur = cur->next;
2078     }
2079   g_list_free (children);
2080
2081   return result;
2082 }
2083
2084 enum {
2085   SEPARATOR_MODE_SMART,
2086   SEPARATOR_MODE_VISIBLE,
2087   SEPARATOR_MODE_HIDDEN
2088 };
2089
2090 static void
2091 update_smart_separators (GtkWidget *proxy)
2092 {
2093   GtkWidget *parent = NULL;
2094   
2095   if (GTK_IS_MENU (proxy) || GTK_IS_TOOLBAR (proxy))
2096     parent = proxy;
2097   else if (GTK_IS_MENU_ITEM (proxy) || GTK_IS_TOOL_ITEM (proxy))
2098     parent = gtk_widget_get_parent (proxy);
2099
2100   if (parent) 
2101     {
2102       gboolean visible;
2103       gboolean empty;
2104       GList *children, *cur, *last;
2105       GtkWidget *filler;
2106
2107       children = gtk_container_get_children (GTK_CONTAINER (parent));
2108       
2109       visible = FALSE;
2110       last = NULL;
2111       empty = TRUE;
2112       filler = NULL;
2113
2114       cur = children;
2115       while (cur) 
2116         {
2117           if (g_object_get_data (cur->data, "gtk-empty-menu-item"))
2118             {
2119               filler = cur->data;
2120             }
2121           else if (GTK_IS_SEPARATOR_MENU_ITEM (cur->data) ||
2122                    GTK_IS_SEPARATOR_TOOL_ITEM (cur->data))
2123             {
2124               gint mode = 
2125                 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cur->data), 
2126                                                     "gtk-separator-mode"));
2127               switch (mode) 
2128                 {
2129                 case SEPARATOR_MODE_VISIBLE:
2130                   gtk_widget_show (GTK_WIDGET (cur->data));
2131                   last = NULL;
2132                   visible = FALSE;
2133                   break;
2134                 case SEPARATOR_MODE_HIDDEN:
2135                   gtk_widget_hide (GTK_WIDGET (cur->data));
2136                   break;
2137                 case SEPARATOR_MODE_SMART:
2138                   if (visible)
2139                     {
2140                       gtk_widget_show (GTK_WIDGET (cur->data));
2141                       last = cur;
2142                       visible = FALSE;
2143                     }
2144                   else 
2145                     gtk_widget_hide (GTK_WIDGET (cur->data));
2146                   break;
2147                 }
2148             }
2149           else if (GTK_WIDGET_VISIBLE (cur->data))
2150             {
2151               last = NULL;
2152               if (GTK_IS_TEAROFF_MENU_ITEM (cur->data) || cur->data == filler)
2153                 visible = FALSE;
2154               else 
2155                 {
2156                   visible = TRUE;
2157                   empty = FALSE;
2158                 }
2159             }
2160           
2161           cur = cur->next;
2162         }
2163
2164       if (last)
2165         gtk_widget_hide (GTK_WIDGET (last->data));
2166
2167       if (GTK_IS_MENU (parent)) 
2168         {
2169           GtkWidget *item;
2170
2171           item = gtk_menu_get_attach_widget (GTK_MENU (parent));
2172           if (GTK_IS_MENU_ITEM (item))
2173             _gtk_action_sync_menu_visible (NULL, item, empty);
2174           if (GTK_IS_WIDGET (filler))
2175             {
2176               if (empty)
2177                 gtk_widget_show (filler);
2178               else
2179                 gtk_widget_hide (filler);
2180             }
2181         }
2182
2183       g_list_free (children);
2184     }
2185 }
2186
2187 static void
2188 update_node (GtkUIManager *self, 
2189              GNode        *node,
2190              gboolean      in_popup)
2191 {
2192   Node *info;
2193   GNode *child;
2194   GtkAction *action;
2195   const gchar *action_name;
2196   NodeUIReference *ref;
2197   
2198 #ifdef DEBUG_UI_MANAGER
2199   GList *tmp;
2200 #endif
2201
2202   g_return_if_fail (node != NULL);
2203   g_return_if_fail (NODE_INFO (node) != NULL);
2204
2205   info = NODE_INFO (node);
2206   
2207   if (!info->dirty)
2208     return;
2209
2210   in_popup = in_popup || (info->type == NODE_TYPE_POPUP);
2211
2212 #ifdef DEBUG_UI_MANAGER
2213   g_print ("update_node name=%s dirty=%d popup %d (", 
2214            info->name, info->dirty, in_popup);
2215   for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next)
2216     {
2217       NodeUIReference *ref = tmp->data;
2218       g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id);
2219       if (tmp->next)
2220         g_print (", ");
2221     }
2222   g_print (")\n");
2223 #endif
2224
2225   if (info->uifiles == NULL) {
2226     /* We may need to remove this node.
2227      * This must be done in post order
2228      */
2229     goto recurse_children;
2230   }
2231   
2232   ref = info->uifiles->data;
2233   action_name = g_quark_to_string (ref->action_quark);
2234   action = get_action_by_name (self, action_name);
2235   
2236   info->dirty = FALSE;
2237   
2238   /* Check if the node doesn't have an action and must have an action */
2239   if (action == NULL &&
2240       info->type != NODE_TYPE_ROOT &&
2241       info->type != NODE_TYPE_MENUBAR &&
2242       info->type != NODE_TYPE_TOOLBAR &&
2243       info->type != NODE_TYPE_POPUP &&
2244       info->type != NODE_TYPE_SEPARATOR &&
2245       info->type != NODE_TYPE_MENU_PLACEHOLDER &&
2246       info->type != NODE_TYPE_TOOLBAR_PLACEHOLDER)
2247     {
2248       g_warning ("%s: missing action %s", info->name, action_name);
2249       
2250       return;
2251     }
2252   
2253   if (action)
2254     gtk_action_set_accel_group (action, self->private_data->accel_group);
2255   
2256   /* If the widget already has a proxy and the action hasn't changed, then
2257    * we only have to update the tearoff menu items.
2258    */
2259   if (info->proxy != NULL && action == info->action)
2260     {
2261       if (info->type == NODE_TYPE_MENU) 
2262         {
2263           GtkWidget *menu;
2264           GList *siblings;
2265           
2266           if (GTK_IS_MENU (info->proxy))
2267             menu = info->proxy;
2268           else
2269             menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2270           siblings = gtk_container_get_children (GTK_CONTAINER (menu));
2271           if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
2272             {
2273               if (self->private_data->add_tearoffs && !in_popup)
2274                 gtk_widget_show (GTK_WIDGET (siblings->data));
2275               else
2276                 gtk_widget_hide (GTK_WIDGET (siblings->data));
2277             }
2278           g_list_free (siblings);
2279         }
2280       
2281       goto recurse_children;
2282     }
2283   
2284   switch (info->type)
2285     {
2286     case NODE_TYPE_MENUBAR:
2287       if (info->proxy == NULL)
2288         {
2289           info->proxy = gtk_menu_bar_new ();
2290           g_object_ref_sink (info->proxy);
2291           gtk_widget_set_name (info->proxy, info->name);
2292           gtk_widget_show (info->proxy);
2293           g_signal_emit (self, ui_manager_signals[ADD_WIDGET], 0, info->proxy);
2294         }
2295       break;
2296     case NODE_TYPE_POPUP:
2297       if (info->proxy == NULL) 
2298         {
2299           info->proxy = gtk_menu_new ();
2300           g_object_ref_sink (info->proxy);
2301         }
2302       gtk_widget_set_name (info->proxy, info->name);
2303       break;
2304     case NODE_TYPE_MENU:
2305       {
2306         GtkWidget *prev_submenu = NULL;
2307         GtkWidget *menu = NULL;
2308         GList *siblings;
2309
2310         /* remove the proxy if it is of the wrong type ... */
2311         if (info->proxy &&  
2312             G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->menu_item_type)
2313           {
2314             if (GTK_IS_MENU_ITEM (info->proxy))
2315               {
2316                 prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2317                 if (prev_submenu)
2318                   {
2319                     g_object_ref (prev_submenu);
2320                     gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
2321                 }
2322               }
2323
2324             gtk_action_disconnect_proxy (info->action, info->proxy);
2325             gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2326                                   info->proxy);
2327             g_object_unref (info->proxy);
2328             info->proxy = NULL;
2329           }
2330
2331         /* create proxy if needed ... */
2332         if (info->proxy == NULL)
2333           {
2334             /* ... if the action already provides a menu, then use
2335              * that menu instead of creating an empty one
2336              */
2337             if ((NODE_INFO (node->parent)->type == NODE_TYPE_TOOLITEM ||
2338                  NODE_INFO (node->parent)->type == NODE_TYPE_MENUITEM) &&
2339                 GTK_ACTION_GET_CLASS (action)->create_menu)
2340               {
2341                 menu = gtk_action_create_menu (action);
2342               }
2343
2344             if (!menu)
2345               {
2346                 GtkWidget *tearoff;
2347                 GtkWidget *filler;
2348             
2349                 menu = gtk_menu_new ();
2350                 gtk_widget_set_name (menu, info->name);
2351                 tearoff = gtk_tearoff_menu_item_new ();
2352                 gtk_widget_set_no_show_all (tearoff, TRUE);
2353                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff);
2354                 filler = gtk_menu_item_new_with_label (_("Empty"));
2355                 g_object_set_data (G_OBJECT (filler),
2356                                    I_("gtk-empty-menu-item"),
2357                                    GINT_TO_POINTER (TRUE));
2358                 gtk_widget_set_sensitive (filler, FALSE);
2359                 gtk_widget_set_no_show_all (filler, TRUE);
2360                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), filler);
2361               }
2362             
2363             if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLITEM)
2364               {
2365                 info->proxy = menu;
2366                 g_object_ref_sink (info->proxy);
2367                 gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (NODE_INFO (node->parent)->proxy),
2368                                                menu);
2369               }
2370             else
2371               {
2372                 GtkWidget *menushell;
2373                 gint pos;
2374                 
2375                 if (find_menu_position (node, &menushell, &pos))
2376                   {
2377                      info->proxy = gtk_action_create_menu_item (action);
2378                      g_object_ref_sink (info->proxy);
2379                      g_signal_connect (info->proxy, "notify::visible",
2380                                        G_CALLBACK (update_smart_separators), NULL);
2381                      gtk_widget_set_name (info->proxy, info->name);
2382                 
2383                      gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu);
2384                      gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos);
2385                  }
2386               }
2387           }
2388         else
2389           gtk_action_connect_proxy (action, info->proxy);
2390         
2391         if (prev_submenu)
2392           {
2393             gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),
2394                                        prev_submenu);
2395             g_object_unref (prev_submenu);
2396           }
2397         
2398         if (GTK_IS_MENU (info->proxy))
2399           menu = info->proxy;
2400         else
2401           menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
2402
2403         siblings = gtk_container_get_children (GTK_CONTAINER (menu));
2404         if (siblings != NULL && GTK_IS_TEAROFF_MENU_ITEM (siblings->data))
2405           {
2406             if (self->private_data->add_tearoffs && !in_popup)
2407               gtk_widget_show (GTK_WIDGET (siblings->data));
2408             else
2409               gtk_widget_hide (GTK_WIDGET (siblings->data));
2410           }
2411         g_list_free (siblings);
2412       }
2413       break;
2414     case NODE_TYPE_UNDECIDED:
2415       g_warning ("found undecided node!");
2416       break;
2417     case NODE_TYPE_ROOT:
2418       break;
2419     case NODE_TYPE_TOOLBAR:
2420       if (info->proxy == NULL)
2421         {
2422           info->proxy = gtk_toolbar_new ();
2423           g_object_ref_sink (info->proxy);
2424           gtk_widget_set_name (info->proxy, info->name);
2425           gtk_widget_show (info->proxy);
2426           g_signal_emit (self, ui_manager_signals[ADD_WIDGET], 0, info->proxy);
2427         }
2428       break;
2429     case NODE_TYPE_MENU_PLACEHOLDER:
2430       /* create menu items for placeholders if necessary ... */
2431       if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) ||
2432           !GTK_IS_SEPARATOR_MENU_ITEM (info->extra))
2433         {
2434           if (info->proxy)
2435             {
2436               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2437                                     info->proxy);
2438               g_object_unref (info->proxy);
2439               info->proxy = NULL;
2440             }
2441           if (info->extra)
2442             {
2443               gtk_container_remove (GTK_CONTAINER (info->extra->parent),
2444                                     info->extra);
2445               g_object_unref (info->extra);
2446               info->extra = NULL;
2447             }
2448         }
2449       if (info->proxy == NULL)
2450         {
2451           GtkWidget *menushell;
2452           gint pos;
2453           
2454           if (find_menu_position (node, &menushell, &pos))
2455             {
2456               info->proxy = gtk_separator_menu_item_new ();
2457               g_object_ref_sink (info->proxy);
2458               g_object_set_data (G_OBJECT (info->proxy),
2459                                  I_("gtk-separator-mode"),
2460                                  GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2461               gtk_widget_set_no_show_all (info->proxy, TRUE);
2462               gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2463                                      NODE_INFO (node)->proxy, pos);
2464           
2465               info->extra = gtk_separator_menu_item_new ();
2466               g_object_ref_sink (info->extra);
2467               g_object_set_data (G_OBJECT (info->extra),
2468                                  I_("gtk-separator-mode"),
2469                                  GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2470               gtk_widget_set_no_show_all (info->extra, TRUE);
2471               gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2472                                      NODE_INFO (node)->extra, pos + 1);
2473             }
2474         }
2475       break;
2476     case NODE_TYPE_TOOLBAR_PLACEHOLDER:
2477       /* create toolbar items for placeholders if necessary ... */
2478       if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) ||
2479           !GTK_IS_SEPARATOR_TOOL_ITEM (info->extra))
2480         {
2481           if (info->proxy)
2482             {
2483               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2484                                     info->proxy);
2485               g_object_unref (info->proxy);
2486               info->proxy = NULL;
2487             }
2488           if (info->extra)
2489             {
2490               gtk_container_remove (GTK_CONTAINER (info->extra->parent),
2491                                     info->extra);
2492               g_object_unref (info->extra);
2493               info->extra = NULL;
2494             }
2495         }
2496       if (info->proxy == NULL)
2497         {
2498           GtkWidget *toolbar;
2499           gint pos;
2500           GtkToolItem *item;    
2501           
2502           if (find_toolbar_position (node, &toolbar, &pos))
2503             {
2504               item = gtk_separator_tool_item_new ();
2505               gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2506               info->proxy = GTK_WIDGET (item);
2507               g_object_ref_sink (info->proxy);
2508               g_object_set_data (G_OBJECT (info->proxy),
2509                                  I_("gtk-separator-mode"),
2510                                  GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2511               gtk_widget_set_no_show_all (info->proxy, TRUE);
2512           
2513               item = gtk_separator_tool_item_new ();
2514               gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1);
2515               info->extra = GTK_WIDGET (item);
2516               g_object_ref_sink (info->extra);
2517               g_object_set_data (G_OBJECT (info->extra),
2518                                  I_("gtk-separator-mode"),
2519                                  GINT_TO_POINTER (SEPARATOR_MODE_HIDDEN));
2520               gtk_widget_set_no_show_all (info->extra, TRUE);
2521             }
2522         }
2523       break;
2524     case NODE_TYPE_MENUITEM:
2525       /* remove the proxy if it is of the wrong type ... */
2526       if (info->proxy &&  
2527           G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->menu_item_type)
2528         {
2529           g_signal_handlers_disconnect_by_func (info->proxy,
2530                                                 G_CALLBACK (update_smart_separators),
2531                                                 NULL);  
2532           gtk_action_disconnect_proxy (info->action, info->proxy);
2533           gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2534                                 info->proxy);
2535           g_object_unref (info->proxy);
2536           info->proxy = NULL;
2537         }
2538       /* create proxy if needed ... */
2539       if (info->proxy == NULL)
2540         {
2541           GtkWidget *menushell;
2542           gint pos;
2543           
2544           if (find_menu_position (node, &menushell, &pos))
2545             {
2546               info->proxy = gtk_action_create_menu_item (action);
2547               g_object_ref_sink (info->proxy);
2548               gtk_widget_set_name (info->proxy, info->name);
2549           
2550               gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2551                                      info->proxy, pos);
2552            }
2553         }
2554       else
2555         {
2556           g_signal_handlers_disconnect_by_func (info->proxy,
2557                                                 G_CALLBACK (update_smart_separators),
2558                                                 NULL);
2559           gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
2560           gtk_action_connect_proxy (action, info->proxy);
2561         }
2562
2563       if (info->proxy)
2564         {
2565           g_signal_connect (info->proxy, "notify::visible",
2566                             G_CALLBACK (update_smart_separators), NULL);
2567           if (in_popup) 
2568             {
2569               /* don't show accels in popups */
2570               GtkWidget *label = GTK_BIN (info->proxy)->child;
2571               g_object_set (label, "accel-closure", NULL, NULL);
2572             }
2573         }
2574       
2575       break;
2576     case NODE_TYPE_TOOLITEM:
2577       /* remove the proxy if it is of the wrong type ... */
2578       if (info->proxy && 
2579           G_OBJECT_TYPE (info->proxy) != GTK_ACTION_GET_CLASS (action)->toolbar_item_type)
2580         {
2581           g_signal_handlers_disconnect_by_func (info->proxy,
2582                                                 G_CALLBACK (update_smart_separators),
2583                                                 NULL);
2584           gtk_action_disconnect_proxy (info->action, info->proxy);
2585           gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2586                                 info->proxy);
2587           g_object_unref (info->proxy);
2588           info->proxy = NULL;
2589         }
2590       /* create proxy if needed ... */
2591       if (info->proxy == NULL)
2592         {
2593           GtkWidget *toolbar;
2594           gint pos;
2595           
2596           if (find_toolbar_position (node, &toolbar, &pos))
2597             {
2598               info->proxy = gtk_action_create_tool_item (action);
2599               g_object_ref_sink (info->proxy);
2600               gtk_widget_set_name (info->proxy, info->name);
2601               
2602               gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
2603                                   GTK_TOOL_ITEM (info->proxy), pos);
2604             }
2605         }
2606       else
2607         {
2608           g_signal_handlers_disconnect_by_func (info->proxy,
2609                                                 G_CALLBACK (update_smart_separators),
2610                                                 NULL);
2611           gtk_action_connect_proxy (action, info->proxy);
2612         }
2613
2614       if (info->proxy)
2615         {
2616           g_signal_connect (info->proxy, "notify::visible",
2617                             G_CALLBACK (update_smart_separators), NULL);
2618         }
2619       break;
2620     case NODE_TYPE_SEPARATOR:
2621       if (NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR ||
2622           NODE_INFO (node->parent)->type == NODE_TYPE_TOOLBAR_PLACEHOLDER)
2623         {
2624           GtkWidget *toolbar;
2625           gint pos;
2626           gint separator_mode;
2627           GtkToolItem *item;
2628
2629           if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy))
2630             {
2631               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2632                                     info->proxy);
2633               g_object_unref (info->proxy);
2634               info->proxy = NULL;
2635             }
2636           
2637           if (find_toolbar_position (node, &toolbar, &pos))
2638             {
2639               item  = gtk_separator_tool_item_new ();
2640               gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
2641               info->proxy = GTK_WIDGET (item);
2642               g_object_ref_sink (info->proxy);
2643               gtk_widget_set_no_show_all (info->proxy, TRUE);
2644               if (info->expand)
2645                 {
2646                   gtk_tool_item_set_expand (GTK_TOOL_ITEM (item), TRUE);
2647                   gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (item), FALSE);
2648                   separator_mode = SEPARATOR_MODE_VISIBLE;
2649                 }
2650               else
2651                 separator_mode = SEPARATOR_MODE_SMART;
2652           
2653               g_object_set_data (G_OBJECT (info->proxy),
2654                                  I_("gtk-separator-mode"),
2655                                  GINT_TO_POINTER (separator_mode));
2656               gtk_widget_show (info->proxy);
2657             }
2658         }
2659       else
2660         {
2661           GtkWidget *menushell;
2662           gint pos;
2663           
2664           if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy))
2665             {
2666               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
2667                                     info->proxy);
2668               g_object_unref (info->proxy);
2669               info->proxy = NULL;
2670             }
2671           
2672           if (find_menu_position (node, &menushell, &pos))
2673             {
2674               info->proxy = gtk_separator_menu_item_new ();
2675               g_object_ref_sink (info->proxy);
2676               gtk_widget_set_no_show_all (info->proxy, TRUE);
2677               g_object_set_data (G_OBJECT (info->proxy),
2678                                  I_("gtk-separator-mode"),
2679                                  GINT_TO_POINTER (SEPARATOR_MODE_SMART));
2680               gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
2681                                      info->proxy, pos);
2682               gtk_widget_show (info->proxy);
2683             }
2684         }
2685       break;
2686     case NODE_TYPE_ACCELERATOR:
2687       gtk_action_connect_accelerator (action);
2688       break;
2689     }
2690   
2691   if (action)
2692     g_object_ref (action);
2693   if (info->action)
2694     g_object_unref (info->action);
2695   info->action = action;
2696
2697  recurse_children:
2698   /* process children */
2699   child = node->children;
2700   while (child)
2701     {
2702       GNode *current;
2703       
2704       current = child;
2705       child = current->next;
2706       update_node (self, current, in_popup);
2707     }
2708   
2709   if (info->proxy) 
2710     {
2711       if (info->type == NODE_TYPE_MENU && GTK_IS_MENU_ITEM (info->proxy)) 
2712         update_smart_separators (gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy)));
2713       else if (info->type == NODE_TYPE_MENU || 
2714                info->type == NODE_TYPE_TOOLBAR || 
2715                info->type == NODE_TYPE_POPUP) 
2716         update_smart_separators (info->proxy);
2717     }
2718   
2719   /* handle cleanup of dead nodes */
2720   if (node->children == NULL && info->uifiles == NULL)
2721     {
2722       if (info->proxy)
2723         gtk_widget_destroy (info->proxy);
2724       if (info->extra)
2725         gtk_widget_destroy (info->extra);
2726       if (info->type == NODE_TYPE_ACCELERATOR)
2727         gtk_action_disconnect_accelerator (info->action);
2728       free_node (node);
2729       g_node_destroy (node);
2730     }
2731 }
2732
2733 static gboolean
2734 do_updates (GtkUIManager *self)
2735 {
2736   /* this function needs to check through the tree for dirty nodes.
2737    * For such nodes, it needs to do the following:
2738    *
2739    * 1) check if they are referenced by any loaded UI files anymore.
2740    *    In which case, the proxy widget should be destroyed, unless
2741    *    there are any subnodes.
2742    *
2743    * 2) lookup the action for this node again.  If it is different to
2744    *    the current one (or if no previous action has been looked up),
2745    *    the proxy is reconnected to the new action (or a new proxy widget
2746    *    is created and added to the parent container).
2747    */
2748   update_node (self, self->private_data->root_node, FALSE);
2749
2750   self->private_data->update_tag = 0;
2751
2752   return FALSE;
2753 }
2754
2755 static gboolean
2756 do_updates_idle (GtkUIManager *self)
2757 {
2758   do_updates (self);
2759
2760   return FALSE;
2761 }
2762
2763 static void
2764 queue_update (GtkUIManager *self)
2765 {
2766   if (self->private_data->update_tag != 0)
2767     return;
2768
2769   self->private_data->update_tag = gdk_threads_add_idle (
2770                                                (GSourceFunc)do_updates_idle, 
2771                                                self);
2772 }
2773
2774
2775 /**
2776  * gtk_ui_manager_ensure_update:
2777  * @self: a #GtkUIManager
2778  * 
2779  * Makes sure that all pending updates to the UI have been completed.
2780  *
2781  * This may occasionally be necessary, since #GtkUIManager updates the 
2782  * UI in an idle function. A typical example where this function is
2783  * useful is to enforce that the menubar and toolbar have been added to 
2784  * the main window before showing it:
2785  * |[
2786  * gtk_container_add (GTK_CONTAINER (window), vbox); 
2787  * g_signal_connect (merge, "add_widget", 
2788  *                   G_CALLBACK (add_widget), vbox);
2789  * gtk_ui_manager_add_ui_from_file (merge, "my-menus");
2790  * gtk_ui_manager_add_ui_from_file (merge, "my-toolbars");
2791  * gtk_ui_manager_ensure_update (merge);  
2792  * gtk_widget_show (window);
2793  * ]|
2794  *
2795  * Since: 2.4
2796  **/
2797 void
2798 gtk_ui_manager_ensure_update (GtkUIManager *self)
2799 {
2800   if (self->private_data->update_tag != 0)
2801     {
2802       g_source_remove (self->private_data->update_tag);
2803       do_updates (self);
2804     }
2805 }
2806
2807 static gboolean
2808 dirty_traverse_func (GNode   *node,
2809                      gpointer data)
2810 {
2811   NODE_INFO (node)->dirty = TRUE;
2812   return FALSE;
2813 }
2814
2815 static void
2816 dirty_all_nodes (GtkUIManager *self)
2817 {
2818   g_node_traverse (self->private_data->root_node,
2819                    G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2820                    dirty_traverse_func, NULL);
2821   queue_update (self);
2822 }
2823
2824 static void
2825 mark_node_dirty (GNode *node)
2826 {
2827   GNode *p;
2828
2829   /* FIXME could optimize this */
2830   for (p = node; p; p = p->parent)
2831     NODE_INFO (p)->dirty = TRUE;  
2832 }
2833
2834 static const gchar *
2835 open_tag_format (NodeType type)
2836 {
2837   switch (type)
2838     {
2839     case NODE_TYPE_UNDECIDED: return "%*s<UNDECIDED"; 
2840     case NODE_TYPE_ROOT: return "%*s<ui"; 
2841     case NODE_TYPE_MENUBAR: return "%*s<menubar";
2842     case NODE_TYPE_MENU: return "%*s<menu";
2843     case NODE_TYPE_TOOLBAR: return "%*s<toolbar";
2844     case NODE_TYPE_MENU_PLACEHOLDER:
2845     case NODE_TYPE_TOOLBAR_PLACEHOLDER: return "%*s<placeholder";
2846     case NODE_TYPE_POPUP: return "%*s<popup";
2847     case NODE_TYPE_MENUITEM: return "%*s<menuitem";
2848     case NODE_TYPE_TOOLITEM: return "%*s<toolitem";
2849     case NODE_TYPE_SEPARATOR: return "%*s<separator";
2850     case NODE_TYPE_ACCELERATOR: return "%*s<accelerator";
2851     default: return NULL;
2852     }
2853 }
2854
2855 static const gchar *
2856 close_tag_format (NodeType type)
2857 {
2858   switch (type)
2859     {
2860     case NODE_TYPE_UNDECIDED: return "%*s</UNDECIDED>\n";
2861     case NODE_TYPE_ROOT: return "%*s</ui>\n";
2862     case NODE_TYPE_MENUBAR: return "%*s</menubar>\n";
2863     case NODE_TYPE_MENU: return "%*s</menu>\n";
2864     case NODE_TYPE_TOOLBAR: return "%*s</toolbar>\n";
2865     case NODE_TYPE_MENU_PLACEHOLDER:
2866     case NODE_TYPE_TOOLBAR_PLACEHOLDER: return "%*s</placeholder>\n";
2867     case NODE_TYPE_POPUP: return "%*s</popup>\n";
2868     default: return NULL;
2869     }
2870 }
2871
2872 static void
2873 print_node (GtkUIManager *self,
2874             GNode        *node,
2875             gint          indent_level,
2876             GString      *buffer)
2877 {
2878   Node  *mnode;
2879   GNode *child;
2880   const gchar *open_fmt;
2881   const gchar *close_fmt;
2882
2883   mnode = node->data;
2884
2885   open_fmt = open_tag_format (mnode->type);
2886   close_fmt = close_tag_format (mnode->type);
2887
2888   g_string_append_printf (buffer, open_fmt, indent_level, "");
2889
2890   if (mnode->type != NODE_TYPE_ROOT)
2891     {
2892       if (mnode->name)
2893         g_string_append_printf (buffer, " name=\"%s\"", mnode->name);
2894       
2895       if (mnode->action_name)
2896         g_string_append_printf (buffer, " action=\"%s\"",
2897                                 g_quark_to_string (mnode->action_name));
2898     }
2899
2900   g_string_append (buffer, close_fmt ? ">\n" : "/>\n");
2901
2902   for (child = node->children; child != NULL; child = child->next)
2903     print_node (self, child, indent_level + 2, buffer);
2904
2905   if (close_fmt)
2906     g_string_append_printf (buffer, close_fmt, indent_level, "");
2907 }
2908
2909 static gboolean
2910 gtk_ui_manager_buildable_custom_tag_start (GtkBuildable  *buildable,
2911                                            GtkBuilder    *builder,
2912                                            GObject       *child,
2913                                            const gchar   *tagname,
2914                                            GMarkupParser *parser,
2915                                            gpointer      *data)
2916 {
2917   if (child)
2918     return FALSE;
2919
2920   if (strcmp (tagname, "ui") == 0)
2921     {
2922       ParseContext *ctx;
2923
2924       ctx = g_new0 (ParseContext, 1);
2925       ctx->state = STATE_START;
2926       ctx->self = GTK_UI_MANAGER (buildable);
2927       ctx->current = NULL;
2928       ctx->merge_id = gtk_ui_manager_new_merge_id (GTK_UI_MANAGER (buildable));
2929
2930       *data = ctx;
2931       *parser = ui_parser;
2932
2933       return TRUE;
2934     }
2935
2936   return FALSE;
2937
2938 }
2939
2940 static void
2941 gtk_ui_manager_buildable_custom_tag_end (GtkBuildable *buildable,
2942                                          GtkBuilder   *builder,
2943                                          GObject      *child,
2944                                          const gchar  *tagname,
2945                                          gpointer     *data)
2946 {
2947   queue_update (GTK_UI_MANAGER (buildable));
2948   g_object_notify (G_OBJECT (buildable), "ui");
2949   g_free (data);
2950 }
2951
2952 /**
2953  * gtk_ui_manager_get_ui:
2954  * @self: a #GtkUIManager
2955  * 
2956  * Creates a <link linkend="XML-UI">UI definition</link> of the merged UI.
2957  * 
2958  * Return value: A newly allocated string containing an XML representation of 
2959  * the merged UI.
2960  *
2961  * Since: 2.4
2962  **/
2963 gchar *
2964 gtk_ui_manager_get_ui (GtkUIManager *self)
2965 {
2966   GString *buffer;
2967
2968   buffer = g_string_new (NULL);
2969
2970   gtk_ui_manager_ensure_update (self); 
2971  
2972   print_node (self, self->private_data->root_node, 0, buffer);  
2973
2974   return g_string_free (buffer, FALSE);
2975 }
2976
2977 #ifdef G_OS_WIN32
2978
2979 #undef gtk_ui_manager_add_ui_from_file
2980
2981 guint
2982 gtk_ui_manager_add_ui_from_file (GtkUIManager *self,
2983                                  const gchar  *filename,
2984                                  GError      **error)
2985 {
2986   gchar *utf8_filename = g_locale_to_utf8 (filename, -1, NULL, NULL, error);
2987   guint retval;
2988
2989   if (utf8_filename == NULL)
2990     return 0;
2991   
2992   retval = gtk_ui_manager_add_ui_from_file_utf8 (self, utf8_filename, error);
2993
2994   g_free (utf8_filename);
2995
2996   return retval;
2997 }
2998
2999 #endif
3000
3001 #define __GTK_UI_MANAGER_C__
3002 #include "gtkaliasdef.c"