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