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