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