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