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