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