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