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