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