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