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