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