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