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