gtkuimanager.c 91.7 KB
Newer Older
1
/*
Cody Russell's avatar
Cody Russell committed
2
 * GTK - The GIMP Toolkit
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 * Copyright (C) 1998, 1999 Red Hat, Inc.
 * All rights reserved.
 *
 * This Library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with the Gnome Library; see the file COPYING.LIB.  If not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/*
 * Author: James Henstridge <james@daa.com.au>
 *
 * Modified by the GTK+ Team and others 2003.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
Michael Natterer's avatar
Michael Natterer committed
28
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
29 30
 */

31
#include "config.h"
32 33

#include <string.h>
34
#include "gtkaccellabel.h"
Matthias Clasen's avatar
Matthias Clasen committed
35
#include "gtkactivatable.h"
Johan Dahlin's avatar
Johan Dahlin committed
36
#include "gtkbuildable.h"
37
#include "gtkimagemenuitem.h"
38 39
#include "gtkintl.h"
#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 "gtktearoffmenuitem.h"
47 48
#include "gtktoolbar.h"
#include "gtkuimanager.h"
49
#include "gtkwindow.h"
50
#include "gtkprivate.h"
51

52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305

/**
 * SECTION:gtkuimanager
 * @Short_description: Constructing menus and toolbars from an XML description
 * @Title: GtkUIManager
 * @See_also:#GtkBuilder
 *
 * A #GtkUIManager constructs a user interface (menus and toolbars) from
 * one or more UI definitions, which reference actions from one or more
 * action groups.
 *
 * <refsect2 id="XML-UI">
 * <title>UI Definitions</title>
 * <para>
 * The UI definitions are specified in an XML format which can be
 * roughly described by the following DTD.
 *
 * <note><para>
 * Do not confuse the GtkUIManager UI Definitions described here with
 * the similarly named <link linkend="BUILDER-UI">GtkBuilder UI
 * Definitions</link>.
 * </para></note>
 *
 * <programlisting>
 * <![CDATA[
 * <!ELEMENT ui          (menubar|toolbar|popup|accelerator)* >
 * <!ELEMENT menubar     (menuitem|separator|placeholder|menu)* >
 * <!ELEMENT menu        (menuitem|separator|placeholder|menu)* >
 * <!ELEMENT popup       (menuitem|separator|placeholder|menu)* >
 * <!ELEMENT toolbar     (toolitem|separator|placeholder)* >
 * <!ELEMENT placeholder (menuitem|toolitem|separator|placeholder|menu)* >
 * <!ELEMENT menuitem     EMPTY >
 * <!ELEMENT toolitem     (menu?) >
 * <!ELEMENT separator    EMPTY >
 * <!ELEMENT accelerator  EMPTY >
 * <!ATTLIST menubar      name                      #IMPLIED
 *                        action                    #IMPLIED >
 * <!ATTLIST toolbar      name                      #IMPLIED
 *                        action                    #IMPLIED >
 * <!ATTLIST popup        name                      #IMPLIED
 *                        action                    #IMPLIED
 *                        accelerators (true|false) #IMPLIED >
 * <!ATTLIST placeholder  name                      #IMPLIED
 *                        action                    #IMPLIED >
 * <!ATTLIST separator    name                      #IMPLIED
 *                        action                    #IMPLIED
 *                        expand       (true|false) #IMPLIED >
 * <!ATTLIST menu         name                      #IMPLIED
 *                        action                    #REQUIRED
 *                        position     (top|bot)    #IMPLIED >
 * <!ATTLIST menuitem     name                      #IMPLIED
 *                        action                    #REQUIRED
 *                        position     (top|bot)    #IMPLIED
 *                        always-show-image (true|false) #IMPLIED >
 * <!ATTLIST toolitem     name                      #IMPLIED
 *                        action                    #REQUIRED
 *                        position     (top|bot)    #IMPLIED >
 * <!ATTLIST accelerator  name                      #IMPLIED
 *                        action                    #REQUIRED >
 * ]]>
 * </programlisting>
 * There are some additional restrictions beyond those specified in the
 * DTD, e.g. every toolitem must have a toolbar in its anchestry and
 * every menuitem must have a menubar or popup in its anchestry. Since
 * a #GMarkup parser is used to parse the UI description, it must not only
 * be valid XML, but valid #GMarkup.
 *
 * If a name is not specified, it defaults to the action. If an action is
 * not specified either, the element name is used. The name and action
 * attributes must not contain '/' characters after parsing (since that
 * would mess up path lookup) and must be usable as XML attributes when
 * enclosed in doublequotes, thus they must not '"' characters or references
 * to the &quot; entity.
 *
 * <example>
 * <title>A UI definition</title>
 * <programlisting>
 * <ui>
 *   <menubar>
 *     <menu name="FileMenu" action="FileMenuAction">
 *       <menuitem name="New" action="New2Action" />
 *       <placeholder name="FileMenuAdditions" />
 *     </menu>
 *     <menu name="JustifyMenu" action="JustifyMenuAction">
 *       <menuitem name="Left" action="justify-left"/>
 *       <menuitem name="Centre" action="justify-center"/>
 *       <menuitem name="Right" action="justify-right"/>
 *       <menuitem name="Fill" action="justify-fill"/>
 *     </menu>
 *   </menubar>
 *   <toolbar action="toolbar1">
 *     <placeholder name="JustifyToolItems">
 *       <separator/>
 *       <toolitem name="Left" action="justify-left"/>
 *       <toolitem name="Centre" action="justify-center"/>
 *       <toolitem name="Right" action="justify-right"/>
 *       <toolitem name="Fill" action="justify-fill"/>
 *       <separator/>
 *     </placeholder>
 *   </toolbar>
 * </ui>
 * </programlisting>
 * </example>
 *
 * The constructed widget hierarchy is very similar to the element tree
 * of the XML, with the exception that placeholders are merged into their
 * parents. The correspondence of XML elements to widgets should be
 * almost obvious:
 * <variablelist>
 * <varlistentry>
 * <term>menubar</term>
 * <listitem><para>a #GtkMenuBar</para></listitem>
 * </varlistentry>
 * <varlistentry>
 * <term>toolbar</term>
 * <listitem><para>a #GtkToolbar</para></listitem>
 * </varlistentry>
 * <varlistentry>
 * <term>popup</term>
 * <listitem><para>a toplevel #GtkMenu</para></listitem>
 * </varlistentry>
 * <varlistentry>
 * <term>menu</term>
 * <listitem><para>a #GtkMenu attached to a menuitem</para></listitem>
 * </varlistentry>
 * <varlistentry>
 * <term>menuitem</term>
 * <listitem><para>a #GtkMenuItem subclass, the exact type depends on the
 * action</para></listitem>
 * </varlistentry>
 * <varlistentry>
 * <term>toolitem</term>
 * <listitem><para>a #GtkToolItem subclass, the exact type depends on the
 * action. Note that toolitem elements may contain a menu element, but only
 * if their associated action specifies a #GtkMenuToolButton as proxy.</para></listitem>
 * </varlistentry>
 * <varlistentry>
 * <term>separator</term>
 * <listitem><para>a #GtkSeparatorMenuItem or
 * #GtkSeparatorToolItem</para></listitem>
 * </varlistentry>
 * <varlistentry>
 * <term>accelerator</term>
 * <listitem><para>a keyboard accelerator</para></listitem>
 * </varlistentry>
 * </variablelist>
 *
 * The "position" attribute determines where a constructed widget is positioned
 * wrt. to its siblings in the partially constructed tree. If it is
 * "top", the widget is prepended, otherwise it is appended.
 * </para>
 * </refsect2>
 * <refsect2 id="UI-Merging">
 * <title>UI Merging</title>
 * <para>
 * The most remarkable feature of #GtkUIManager is that it can overlay a set
 * of menuitems and toolitems over another one, and demerge them later.
 *
 * Merging is done based on the names of the XML elements. Each element is
 * identified by a path which consists of the names of its anchestors, separated
 * by slashes. For example, the menuitem named "Left" in the example above
 * has the path <literal>/ui/menubar/JustifyMenu/Left</literal> and the
 * toolitem with the same name has path
 * <literal>/ui/toolbar1/JustifyToolItems/Left</literal>.
 * </para>
 * </refsect2>
 * <refsect2>
 * <title>Accelerators</title>
 * <para>
 * Every action has an accelerator path. Accelerators are installed together with
 * menuitem proxies, but they can also be explicitly added with &lt;accelerator&gt;
 * elements in the UI definition. This makes it possible to have accelerators for
 * actions even if they have no visible proxies.
 * </para>
 * </refsect2>
 * <refsect2 id="Smart-Separators">
 * <title>Smart Separators</title>
 * <para>
 * The separators created by #GtkUIManager are "smart", i.e. they do not show up
 * in the UI unless they end up between two visible menu or tool items. Separators
 * which are located at the very beginning or end of the menu or toolbar
 * containing them, or multiple separators next to each other, are hidden. This
 * is a useful feature, since the merging of UI elements from multiple sources
 * can make it hard or impossible to determine in advance whether a separator
 * will end up in such an unfortunate position.
 *
 * For separators in toolbars, you can set <literal>expand="true"</literal> to
 * turn them from a small, visible separator to an expanding, invisible one.
 * Toolitems following an expanding separator are effectively right-aligned.
 * </para>
 * </refsect2>
 * <refsect2>
 * <title>Empty Menus</title>
 * <para>
 * Submenus pose similar problems to separators inconnection with merging. It is
 * impossible to know in advance whether they will end up empty after merging.
 * #GtkUIManager offers two ways to treat empty submenus:
 * <itemizedlist>
 * <listitem>
 * <para>make them disappear by hiding the menu item they're attached to</para>
 * </listitem>
 * <listitem>
 * <para>add an insensitive "Empty" item</para>
 * </listitem>
 * </itemizedlist>
 * The behaviour is chosen based on the "hide_if_empty" property of the action
 * to which the submenu is associated.
 * </para>
 * </refsect2>
 * <refsect2 id="GtkUIManager-BUILDER-UI">
 * <title>GtkUIManager as GtkBuildable</title>
 * <para>
 * The GtkUIManager implementation of the GtkBuildable interface accepts
 * GtkActionGroup objects as &lt;child&gt; elements in UI definitions.
 *
 * A GtkUIManager UI definition as described above can be embedded in
 * an GtkUIManager &lt;object&gt; element in a GtkBuilder UI definition.
 *
 * The widgets that are constructed by a GtkUIManager can be embedded in
 * other parts of the constructed user interface with the help of the
 * "constructor" attribute. See the example below.
 *
 * <example>
 * <title>An embedded GtkUIManager UI definition</title>
 * <programlisting><![CDATA[
 * <object class="GtkUIManager" id="uiman">
 *   <child>
 *     <object class="GtkActionGroup" id="actiongroup">
 *       <child>
 *         <object class="GtkAction" id="file">
 *           <property name="label">_File</property>
 *         </object>
 *       </child>
 *     </object>
 *   </child>
 *   <ui>
 *     <menubar name="menubar1">
 *       <menu action="file">
 *       </menu>
 *     </menubar>
 *   </ui>
 * </object>
 * <object class="GtkWindow" id="main-window">
 *   <child>
 *     <object class="GtkMenuBar" id="menubar1" constructor="uiman"/>
 *   </child>
 * </object>
 * ]]></programlisting>
 * </example>
 * </para>
 * </refsect2>
 */


Matthias Clasen's avatar
Matthias Clasen committed
306
#undef DEBUG_UI_MANAGER
307

Michael Natterer's avatar
Michael Natterer committed
308
typedef enum
309
{
310 311 312 313 314 315 316 317 318 319 320
  NODE_TYPE_UNDECIDED,
  NODE_TYPE_ROOT,
  NODE_TYPE_MENUBAR,
  NODE_TYPE_MENU,
  NODE_TYPE_TOOLBAR,
  NODE_TYPE_MENU_PLACEHOLDER,
  NODE_TYPE_TOOLBAR_PLACEHOLDER,
  NODE_TYPE_POPUP,
  NODE_TYPE_MENUITEM,
  NODE_TYPE_TOOLITEM,
  NODE_TYPE_SEPARATOR,
321
  NODE_TYPE_ACCELERATOR
322
} NodeType;
323

324
typedef struct _Node Node;
325

326 327
struct _Node {
  NodeType type;
328

Matthias Clasen's avatar
Matthias Clasen committed
329
  gchar *name;
330 331 332 333

  GQuark action_name;
  GtkAction *action;
  GtkWidget *proxy;
334
  GtkWidget *extra; /* second separator for placeholders */
335 336 337 338

  GList *uifiles;

  guint dirty : 1;
339
  guint expand : 1;  /* used for separators */
340
  guint popup_accels : 1;
341 342
  guint always_show_image_set : 1; /* used for menu items */
  guint always_show_image     : 1; /* used for menu items */
343 344 345
};


346
struct _GtkUIManagerPrivate 
347 348 349 350 351 352 353 354 355
{
  GtkAccelGroup *accel_group;

  GNode *root_node;
  GList *action_groups;

  guint last_merge_id;

  guint update_tag;  
356 357

  gboolean add_tearoffs;
358 359
};

360
#define NODE_INFO(node) ((Node *)node->data)
361 362 363 364 365 366 367 368 369

typedef struct _NodeUIReference NodeUIReference;

struct _NodeUIReference 
{
  guint merge_id;
  GQuark action_quark;
};

370 371 372 373 374 375 376 377 378 379 380 381 382
static void        gtk_ui_manager_finalize        (GObject           *object);
static void        gtk_ui_manager_set_property    (GObject           *object,
                                                   guint              prop_id,
                                                   const GValue      *value,
                                                   GParamSpec        *pspec);
static void        gtk_ui_manager_get_property    (GObject           *object,
                                                   guint              prop_id,
                                                   GValue            *value,
                                                   GParamSpec        *pspec);
static GtkWidget * gtk_ui_manager_real_get_widget (GtkUIManager      *manager,
                                                   const gchar       *path);
static GtkAction * gtk_ui_manager_real_get_action (GtkUIManager      *manager,
                                                   const gchar       *path);
383 384
static void        queue_update                   (GtkUIManager      *manager);
static void        dirty_all_nodes                (GtkUIManager      *manager);
385
static void        mark_node_dirty                (GNode             *node);
386
static GNode     * get_child_node                 (GtkUIManager      *manager,
387
                                                   GNode             *parent,
388
						   GNode             *sibling,
389 390 391 392 393
                                                   const gchar       *childname,
                                                   gint               childname_length,
                                                   NodeType           node_type,
                                                   gboolean           create,
                                                   gboolean           top);
394
static GNode     * get_node                       (GtkUIManager      *manager,
395 396 397 398 399 400 401 402 403
                                                   const gchar       *path,
                                                   NodeType           node_type,
                                                   gboolean           create);
static gboolean    free_node                      (GNode             *node);
static void        node_prepend_ui_reference      (GNode             *node,
                                                   guint              merge_id,
                                                   GQuark             action_quark);
static void        node_remove_ui_reference       (GNode             *node,
                                                   guint              merge_id);
404

Johan Dahlin's avatar
Johan Dahlin committed
405
/* GtkBuildable */
406 407 408 409 410
static void gtk_ui_manager_buildable_init      (GtkBuildableIface *iface);
static void gtk_ui_manager_buildable_add_child (GtkBuildable  *buildable,
						GtkBuilder    *builder,
						GObject       *child,
						const gchar   *type);
Johan Dahlin's avatar
Johan Dahlin committed
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
static GObject* gtk_ui_manager_buildable_construct_child (GtkBuildable *buildable,
							  GtkBuilder   *builder,
							  const gchar  *name);
static gboolean gtk_ui_manager_buildable_custom_tag_start (GtkBuildable  *buildable,
							   GtkBuilder    *builder,
							   GObject       *child,
							   const gchar   *tagname,
							   GMarkupParser *parser,
							   gpointer      *data);
static void     gtk_ui_manager_buildable_custom_tag_end (GtkBuildable 	 *buildable,
							 GtkBuilder   	 *builder,
							 GObject      	 *child,
							 const gchar  	 *tagname,
							 gpointer     	 *data);


427 428 429 430

enum 
{
  ADD_WIDGET,
431
  ACTIONS_CHANGED,
432 433 434 435
  CONNECT_PROXY,
  DISCONNECT_PROXY,
  PRE_ACTIVATE,
  POST_ACTIVATE,
436 437 438
  LAST_SIGNAL
};

439 440 441
enum
{
  PROP_0,
442 443
  PROP_ADD_TEAROFFS,
  PROP_UI
444 445
};

446
static guint ui_manager_signals[LAST_SIGNAL] = { 0 };
447

448
G_DEFINE_TYPE_WITH_CODE (GtkUIManager, gtk_ui_manager, G_TYPE_OBJECT,
Johan Dahlin's avatar
Johan Dahlin committed
449 450
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
						gtk_ui_manager_buildable_init))
451 452

static void
453
gtk_ui_manager_class_init (GtkUIManagerClass *klass)
454 455 456 457 458
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);

459
  gobject_class->finalize = gtk_ui_manager_finalize;
460 461
  gobject_class->set_property = gtk_ui_manager_set_property;
  gobject_class->get_property = gtk_ui_manager_get_property;
462 463 464
  klass->get_widget = gtk_ui_manager_real_get_widget;
  klass->get_action = gtk_ui_manager_real_get_action;

465 466 467
  /**
   * GtkUIManager:add-tearoffs:
   *
468
   * The "add-tearoffs" property controls whether generated menus 
469 470 471 472 473 474 475 476 477
   * have tearoff menu items. 
   *
   * Note that this only affects regular menus. Generated popup 
   * menus never have tearoff menu items.   
   *
   * Since: 2.4
   */
  g_object_class_install_property (gobject_class,
                                   PROP_ADD_TEAROFFS,
478
                                   g_param_spec_boolean ("add-tearoffs",
479 480
							 P_("Add tearoffs to menus"),
							 P_("Whether tearoff menu items should be added to menus"),
481
                                                         FALSE,
482
							 GTK_PARAM_READWRITE));
483 484 485 486

  g_object_class_install_property (gobject_class,
				   PROP_UI,
				   g_param_spec_string ("ui",
487 488
 							P_("Merged UI definition"),
							P_("An XML string describing the merged UI"),
489
							"<ui>\n</ui>\n",
490
							GTK_PARAM_READABLE));
491 492


493 494
  /**
   * GtkUIManager::add-widget:
495
   * @manager: a #GtkUIManager
496 497
   * @widget: the added widget
   *
498
   * The ::add-widget signal is emitted for each generated menubar and toolbar.
499 500 501 502 503
   * It is not emitted for generated popup menus, which can be obtained by 
   * gtk_ui_manager_get_widget().
   *
   * Since: 2.4
   */
504
  ui_manager_signals[ADD_WIDGET] =
505
    g_signal_new (I_("add-widget"),
506 507
		  G_OBJECT_CLASS_TYPE (klass),
		  G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
508 509
		  G_STRUCT_OFFSET (GtkUIManagerClass, add_widget),
		  NULL, NULL,
510
		  g_cclosure_marshal_VOID__OBJECT,
511
		  G_TYPE_NONE, 1, 
512
		  GTK_TYPE_WIDGET);
513

514
  /**
515
   * GtkUIManager::actions-changed:
516
   * @manager: a #GtkUIManager
517
   *
518
   * The ::actions-changed signal is emitted whenever the set of actions
519
   * changes.
520 521 522
   *
   * Since: 2.4
   */
523
  ui_manager_signals[ACTIONS_CHANGED] =
524
    g_signal_new (I_("actions-changed"),
525 526
		  G_OBJECT_CLASS_TYPE (klass),
		  G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
527 528
		  G_STRUCT_OFFSET (GtkUIManagerClass, actions_changed),  
		  NULL, NULL,
529 530
		  g_cclosure_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);
531
  
532
  /**
533
   * GtkUIManager::connect-proxy:
534
   * @manager: the ui manager
535 536 537
   * @action: the action
   * @proxy: the proxy
   *
538
   * The ::connect-proxy signal is emitted after connecting a proxy to
539 540 541 542 543 544 545 546 547
   * an action in the group. 
   *
   * This is intended for simple customizations for which a custom action
   * class would be too clumsy, e.g. showing tooltips for menuitems in the
   * statusbar.
   *
   * Since: 2.4
   */
  ui_manager_signals[CONNECT_PROXY] =
548
    g_signal_new (I_("connect-proxy"),
549
		  G_OBJECT_CLASS_TYPE (klass),
550 551
		  G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
		  G_STRUCT_OFFSET (GtkUIManagerClass, connect_proxy),
552
		  NULL, NULL,
553 554
		  _gtk_marshal_VOID__OBJECT_OBJECT,
		  G_TYPE_NONE, 2, 
555 556
		  GTK_TYPE_ACTION,
		  GTK_TYPE_WIDGET);
557 558

  /**
559
   * GtkUIManager::disconnect-proxy:
560
   * @manager: the ui manager
561 562 563
   * @action: the action
   * @proxy: the proxy
   *
564
   * The ::disconnect-proxy signal is emitted after disconnecting a proxy
565 566 567 568 569
   * from an action in the group. 
   *
   * Since: 2.4
   */
  ui_manager_signals[DISCONNECT_PROXY] =
570
    g_signal_new (I_("disconnect-proxy"),
571
		  G_OBJECT_CLASS_TYPE (klass),
572 573 574
		  G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
		  G_STRUCT_OFFSET (GtkUIManagerClass, disconnect_proxy),
		  NULL, NULL,
575
		  _gtk_marshal_VOID__OBJECT_OBJECT,
576 577 578
		  G_TYPE_NONE, 2,
		  GTK_TYPE_ACTION,
		  GTK_TYPE_WIDGET);
579 580

  /**
581
   * GtkUIManager::pre-activate:
582
   * @manager: the ui manager
583 584
   * @action: the action
   *
585
   * The ::pre-activate signal is emitted just before the @action
586 587 588 589 590 591 592 593
   * is activated.
   *
   * This is intended for applications to get notification
   * just before any action is activated.
   *
   * Since: 2.4
   */
  ui_manager_signals[PRE_ACTIVATE] =
594
    g_signal_new (I_("pre-activate"),
595
		  G_OBJECT_CLASS_TYPE (klass),
596 597 598
		  G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
		  G_STRUCT_OFFSET (GtkUIManagerClass, pre_activate),
		  NULL, NULL,
599
		  _gtk_marshal_VOID__OBJECT,
600
		  G_TYPE_NONE, 1,
601 602 603
		  GTK_TYPE_ACTION);

  /**
604
   * GtkUIManager::post-activate:
605
   * @manager: the ui manager
606 607
   * @action: the action
   *
608
   * The ::post-activate signal is emitted just after the @action
609 610 611 612 613 614 615 616
   * is activated.
   *
   * This is intended for applications to get notification
   * just after any action is activated.
   *
   * Since: 2.4
   */
  ui_manager_signals[POST_ACTIVATE] =
617
    g_signal_new (I_("post-activate"),
618
		  G_OBJECT_CLASS_TYPE (klass),
619 620 621
		  G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
		  G_STRUCT_OFFSET (GtkUIManagerClass, post_activate),
		  NULL, NULL,
622
		  _gtk_marshal_VOID__OBJECT,
623
		  G_TYPE_NONE, 1,
624 625
		  GTK_TYPE_ACTION);

626 627 628 629 630 631 632
  klass->add_widget = NULL;
  klass->actions_changed = NULL;
  klass->connect_proxy = NULL;
  klass->disconnect_proxy = NULL;
  klass->pre_activate = NULL;
  klass->post_activate = NULL;

633
  g_type_class_add_private (gobject_class, sizeof (GtkUIManagerPrivate));
634 635 636 637
}


static void
638
gtk_ui_manager_init (GtkUIManager *manager)
639 640 641 642
{
  guint merge_id;
  GNode *node;

643 644 645
  manager->private_data = G_TYPE_INSTANCE_GET_PRIVATE (manager,
                                                       GTK_TYPE_UI_MANAGER,
                                                       GtkUIManagerPrivate);
646

647
  manager->private_data->accel_group = gtk_accel_group_new ();
648

649 650
  manager->private_data->root_node = NULL;
  manager->private_data->action_groups = NULL;
651

652 653
  manager->private_data->last_merge_id = 0;
  manager->private_data->add_tearoffs = FALSE;
654

655 656
  merge_id = gtk_ui_manager_new_merge_id (manager);
  node = get_child_node (manager, NULL, NULL, "ui", 2,
Matthias Clasen's avatar
Matthias Clasen committed
657
			 NODE_TYPE_ROOT, TRUE, FALSE);
658
  node_prepend_ui_reference (node, merge_id, 0);
659 660
}

661 662 663
static void
gtk_ui_manager_finalize (GObject *object)
{
664
  GtkUIManager *manager = GTK_UI_MANAGER (object);
665
  
666
  if (manager->private_data->update_tag != 0)
667
    {
668 669
      g_source_remove (manager->private_data->update_tag);
      manager->private_data->update_tag = 0;
670
    }
Matthias Clasen's avatar
Matthias Clasen committed
671
  
672
  g_node_traverse (manager->private_data->root_node, 
Matthias Clasen's avatar
Matthias Clasen committed
673
		   G_POST_ORDER, G_TRAVERSE_ALL, -1,
Matthias Clasen's avatar
Matthias Clasen committed
674
		   (GNodeTraverseFunc)free_node, NULL);
675 676
  g_node_destroy (manager->private_data->root_node);
  manager->private_data->root_node = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
677
  
678
  g_list_foreach (manager->private_data->action_groups,
Matthias Clasen's avatar
Matthias Clasen committed
679
                  (GFunc) g_object_unref, NULL);
680 681
  g_list_free (manager->private_data->action_groups);
  manager->private_data->action_groups = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
682

683 684
  g_object_unref (manager->private_data->accel_group);
  manager->private_data->accel_group = NULL;
685

Matthias Clasen's avatar
Matthias Clasen committed
686
  G_OBJECT_CLASS (gtk_ui_manager_parent_class)->finalize (object);
687 688
}

Johan Dahlin's avatar
Johan Dahlin committed
689 690 691
static void
gtk_ui_manager_buildable_init (GtkBuildableIface *iface)
{
692
  iface->add_child = gtk_ui_manager_buildable_add_child;
Johan Dahlin's avatar
Johan Dahlin committed
693 694 695 696 697 698
  iface->construct_child = gtk_ui_manager_buildable_construct_child;
  iface->custom_tag_start = gtk_ui_manager_buildable_custom_tag_start;
  iface->custom_tag_end = gtk_ui_manager_buildable_custom_tag_end;
}

static void
699 700 701 702
gtk_ui_manager_buildable_add_child (GtkBuildable  *buildable,
				    GtkBuilder    *builder,
				    GObject       *child,
				    const gchar   *type)
Johan Dahlin's avatar
Johan Dahlin committed
703
{
704
  GtkUIManager *manager = GTK_UI_MANAGER (buildable);
Johan Dahlin's avatar
Johan Dahlin committed
705 706 707 708
  guint pos;

  g_return_if_fail (GTK_IS_ACTION_GROUP (child));

709
  pos = g_list_length (manager->private_data->action_groups);
Johan Dahlin's avatar
Johan Dahlin committed
710 711

  g_object_ref (child);
712
  gtk_ui_manager_insert_action_group (manager,
Johan Dahlin's avatar
Johan Dahlin committed
713 714 715 716
				      GTK_ACTION_GROUP (child),
				      pos);
}

717 718 719 720 721 722 723 724 725 726 727 728 729 730
static void
child_hierarchy_changed_cb (GtkWidget *widget,
			    GtkWidget *unused,
			    GtkUIManager *uimgr)
{
  GtkWidget *toplevel;
  GtkAccelGroup *group;
  GSList *groups;

  toplevel = gtk_widget_get_toplevel (widget);
  if (!toplevel || !GTK_IS_WINDOW (toplevel))
    return;
  
  group = gtk_ui_manager_get_accel_group (uimgr);
Michael Natterer's avatar
Michael Natterer committed
731
  groups = gtk_accel_groups_from_object (G_OBJECT (toplevel));
732 733 734 735 736 737 738 739
  if (g_slist_find (groups, group) == NULL)
    gtk_window_add_accel_group (GTK_WINDOW (toplevel), group);

  g_signal_handlers_disconnect_by_func (widget,
					child_hierarchy_changed_cb,
					uimgr);
}

Johan Dahlin's avatar
Johan Dahlin committed
740 741 742 743 744 745 746 747 748 749 750
static GObject *
gtk_ui_manager_buildable_construct_child (GtkBuildable *buildable,
					  GtkBuilder   *builder,
					  const gchar  *id)
{
  GtkWidget *widget;
  char *name;

  name = g_strdup_printf ("ui/%s", id);
  widget = gtk_ui_manager_get_widget (GTK_UI_MANAGER (buildable), name);
  if (!widget)
751 752 753 754 755
    {
      g_error ("Unknown ui manager child: %s\n", name);
      g_free (name);
      return NULL;
    }
Johan Dahlin's avatar
Johan Dahlin committed
756 757
  g_free (name);

758 759 760
  g_signal_connect (widget, "hierarchy-changed",
		    G_CALLBACK (child_hierarchy_changed_cb),
		    GTK_UI_MANAGER (buildable));
761
  return g_object_ref (widget);
Johan Dahlin's avatar
Johan Dahlin committed
762 763
}

764 765 766 767 768 769
static void
gtk_ui_manager_set_property (GObject         *object,
			     guint            prop_id,
			     const GValue    *value,
			     GParamSpec      *pspec)
{
770
  GtkUIManager *manager = GTK_UI_MANAGER (object);
771 772 773 774
 
  switch (prop_id)
    {
    case PROP_ADD_TEAROFFS:
775
      gtk_ui_manager_set_add_tearoffs (manager, g_value_get_boolean (value));
776 777 778 779 780 781 782 783 784 785 786 787 788
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gtk_ui_manager_get_property (GObject         *object,
			     guint            prop_id,
			     GValue          *value,
			     GParamSpec      *pspec)
{
789
  GtkUIManager *manager = GTK_UI_MANAGER (object);
790 791 792 793

  switch (prop_id)
    {
    case PROP_ADD_TEAROFFS:
794
      g_value_set_boolean (value, manager->private_data->add_tearoffs);
795
      break;
796
    case PROP_UI:
797
      g_value_take_string (value, gtk_ui_manager_get_ui (manager));
798
      break;
799 800 801 802 803 804
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

805
static GtkWidget *
806
gtk_ui_manager_real_get_widget (GtkUIManager *manager,
807 808 809 810 811 812
                                const gchar  *path)
{
  GNode *node;

  /* ensure that there are no pending updates before we get the
   * widget */
813
  gtk_ui_manager_ensure_update (manager);
814

815
  node = get_node (manager, path, NODE_TYPE_UNDECIDED, FALSE);
816 817 818 819 820 821 822 823

  if (node == NULL)
    return NULL;

  return NODE_INFO (node)->proxy;
}

static GtkAction *
824
gtk_ui_manager_real_get_action (GtkUIManager *manager,
825 826 827 828 829 830
                                const gchar  *path)
{
  GNode *node;

  /* ensure that there are no pending updates before we get
   * the action */
831
  gtk_ui_manager_ensure_update (manager);
832

833
  node = get_node (manager, path, NODE_TYPE_UNDECIDED, FALSE);
834 835 836 837 838 839 840

  if (node == NULL)
    return NULL;

  return NODE_INFO (node)->action;
}

841 842

/**
843
 * gtk_ui_manager_new:
844
 * 
Matthias Clasen's avatar
Matthias Clasen committed
845
 * Creates a new ui manager object.
846
 * 
Matthias Clasen's avatar
Matthias Clasen committed
847
 * Return value: a new ui manager object.
848 849 850
 *
 * Since: 2.4
 **/
851 852
GtkUIManager*
gtk_ui_manager_new (void)
853
{
854
  return g_object_new (GTK_TYPE_UI_MANAGER, NULL);
855 856 857
}


858 859
/**
 * gtk_ui_manager_get_add_tearoffs:
860
 * @manager: a #GtkUIManager
861 862 863 864 865 866 867 868 869
 * 
 * Returns whether menus generated by this #GtkUIManager
 * will have tearoff menu items. 
 * 
 * Return value: whether tearoff menu items are added
 *
 * Since: 2.4
 **/
gboolean 
870
gtk_ui_manager_get_add_tearoffs (GtkUIManager *manager)
871
{
872
  g_return_val_if_fail (GTK_IS_UI_MANAGER (manager), FALSE);
873
  
874
  return manager->private_data->add_tearoffs;
875 876 877 878 879
}


/**
 * gtk_ui_manager_set_add_tearoffs:
880
 * @manager: a #GtkUIManager
881 882
 * @add_tearoffs: whether tearoff menu items are added
 * 
883
 * Sets the "add_tearoffs" property, which controls whether menus 
884 885 886 887 888 889 890 891
 * generated by this #GtkUIManager will have tearoff menu items. 
 *
 * Note that this only affects regular menus. Generated popup 
 * menus never have tearoff menu items.
 *
 * Since: 2.4
 **/
void 
892
gtk_ui_manager_set_add_tearoffs (GtkUIManager *manager,
893 894
				 gboolean      add_tearoffs)
{
895
  g_return_if_fail (GTK_IS_UI_MANAGER (manager));
896 897 898

  add_tearoffs = add_tearoffs != FALSE;

899
  if (add_tearoffs != manager->private_data->add_tearoffs)
900
    {
901
      manager->private_data->add_tearoffs = add_tearoffs;
902
      
903
      dirty_all_nodes (manager);
904

905
      g_object_notify (G_OBJECT (manager), "add-tearoffs");
906 907 908
    }
}

909 910 911 912
static void
cb_proxy_connect_proxy (GtkActionGroup *group, 
                        GtkAction      *action,
                        GtkWidget      *proxy, 
913
                        GtkUIManager *manager)
914
{
915
  g_signal_emit (manager, ui_manager_signals[CONNECT_PROXY], 0, action, proxy);
916 917 918 919 920 921
}

static void
cb_proxy_disconnect_proxy (GtkActionGroup *group, 
                           GtkAction      *action,
                           GtkWidget      *proxy, 
922
                           GtkUIManager *manager)
923
{
924
  g_signal_emit (manager, ui_manager_signals[DISCONNECT_PROXY], 0, action, proxy);
925 926 927 928 929
}

static void
cb_proxy_pre_activate (GtkActionGroup *group, 
                       GtkAction      *action,
930
                       GtkUIManager   *manager)
931
{
932
  g_signal_emit (manager, ui_manager_signals[PRE_ACTIVATE], 0, action);
933 934 935 936 937
}

static void
cb_proxy_post_activate (GtkActionGroup *group, 
                        GtkAction      *action,
938
                        GtkUIManager   *manager)
939
{
940
  g_signal_emit (manager, ui_manager_signals[POST_ACTIVATE], 0, action);
941 942
}

943
/**
944
 * gtk_ui_manager_insert_action_group:
945
 * @manager: a #GtkUIManager object
946
 * @action_group: the action group to be inserted
947
 * @pos: the position at which the group will be inserted.
948
 * 
949
 * Inserts an action group into the list of action groups associated 
950
 * with @manager. Actions in earlier groups hide actions with the same 
951
 * name in later groups. 
952 953 954 955
 *
 * Since: 2.4
 **/
void
956
gtk_ui_manager_insert_action_group (GtkUIManager   *manager,
957 958 959
				    GtkActionGroup *action_group, 
				    gint            pos)
{
960 961 962 963 964
#ifdef G_ENABLE_DEBUG
  GList *l;
  const char *group_name;
#endif 

965
  g_return_if_fail (GTK_IS_UI_MANAGER (manager));
966
  g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
967
  g_return_if_fail (g_list_find (manager->private_data->action_groups, 
968
				 action_group) == NULL);
969

970 971 972
#ifdef G_ENABLE_DEBUG
  group_name  = gtk_action_group_get_name (action_group);

973
  for (l = manager->private_data->action_groups; l; l = l->next) 
974 975 976 977 978 979 980 981 982 983 984 985
    {
      GtkActionGroup *group = l->data;

      if (strcmp (gtk_action_group_get_name (group), group_name) == 0)
        {
          g_warning ("Inserting action group '%s' into UI manager which "
		     "already has a group with this name\n", group_name);
          break;
        }
    }
#endif /* G_ENABLE_DEBUG */

986
  g_object_ref (action_group);
987 988
  manager->private_data->action_groups = 
    g_list_insert (manager->private_data->action_groups, action_group, pos);
989
  g_object_connect (action_group,
990 991 992 993
		    "object-signal::connect-proxy", G_CALLBACK (cb_proxy_connect_proxy), manager,
		    "object-signal::disconnect-proxy", G_CALLBACK (cb_proxy_disconnect_proxy), manager,
		    "object-signal::pre-activate", G_CALLBACK (cb_proxy_pre_activate), manager,
		    "object-signal::post-activate", G_CALLBACK (cb_proxy_post_activate), manager,
994
		    NULL);
995 996

  /* dirty all nodes, as action bindings may change */
997
  dirty_all_nodes (manager);
998

999
  g_signal_emit (manager, ui_manager_signals[ACTIONS_CHANGED], 0);
1000 1001 1002
}

/**
1003
 * gtk_ui_manager_remove_action_group:
1004
 * @manager: a #GtkUIManager object
1005 1006
 * @action_group: the action group to be removed
 * 
1007
 * Removes an action group from the list of action groups associated 
1008
 * with @manager.
1009 1010 1011 1012
 *
 * Since: 2.4
 **/
void
1013
gtk_ui_manager_remove_action_group (GtkUIManager   *manager,
1014 1015
				    GtkActionGroup *action_group)
{
1016
  g_return_if_fail (GTK_IS_UI_MANAGER (manager));
1017
  g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
1018
  g_return_if_fail (g_list_find (manager->private_data->action_groups, 
1019 1020
				 action_group) != NULL);

1021 1022
  manager->private_data->action_groups =
    g_list_remove (manager->private_data->action_groups, action_group);
1023 1024

  g_object_disconnect (action_group,
1025 1026 1027 1028
                       "any-signal::connect-proxy", G_CALLBACK (cb_proxy_connect_proxy), manager,
                       "any-signal::disconnect-proxy", G_CALLBACK (cb_proxy_disconnect_proxy), manager,
                       "any-signal::pre-activate", G_CALLBACK (cb_proxy_pre_activate), manager,
                       "any-signal::post-activate", G_CALLBACK (cb_proxy_post_activate), manager, 
1029
                       NULL);
1030 1031 1032
  g_object_unref (action_group);

  /* dirty all nodes, as action bindings may change */
1033
  dirty_all_nodes (manager);
1034

1035
  g_signal_emit (manager, ui_manager_signals[ACTIONS_CHANGED], 0);
1036 1037 1038
}

/**
1039
 * gtk_ui_manager_get_action_groups:
1040
 * @manager: a #GtkUIManager object
1041
 * 
1042
 * Returns the list of action groups associated with @manager.
1043
 *
1044 1045
 * Return value:  (element-type GtkActionGroup) (transfer none): a #GList of
 *   action groups. The list is owned by GTK+
1046 1047 1048 1049 1050
 *   and should not be modified.
 *
 * Since: 2.4
 **/
GList *
1051
gtk_ui_manager_get_action_groups (GtkUIManager *manager)
1052
{
1053
  g_return_val_if_fail (GTK_IS_UI_MANAGER (manager), NULL);
1054

1055
  return manager->private_data->action_groups;
1056 1057 1058
}

/**
1059
 * gtk_ui_manager_get_accel_group:
1060
 * @manager: a #GtkUIManager object
1061
 * 
1062
 * Returns the #GtkAccelGroup associated with @manager.
1063
 *
1064
 * Return value: (transfer none): the #GtkAccelGroup.
1065 1066 1067 1068
 *
 * Since: 2.4
 **/
GtkAccelGroup *
1069
gtk_ui_manager_get_accel_group (GtkUIManager *manager)
1070
{
1071
  g_return_val_if_fail (GTK_IS_UI_MANAGER (manager), NULL);
1072

1073
  return manager->private_data->accel_group;
1074 1075 1076
}

/**
1077
 * gtk_ui_manager_get_widget:
1078
 * @manager: a #GtkUIManager
1079 1080
 * @path: a path
 * 
Matthias Clasen's avatar
Matthias Clasen committed
1081 1082 1083 1084
 * Looks up a widget by following a path. 
 * The path consists of the names specified in the XML description of the UI. 
 * separated by '/'. Elements which don't have a name or action attribute in 
 * the XML (e.g. &lt;popup&gt;) can be addressed by their XML element name 
1085
 * (e.g. "popup"). The root element ("/ui") can be omitted in the path.
1086 1087
 *
 * Note that the widget found by following a path that ends in a &lt;menu&gt;
1088
 * element is the menuitem to which the menu is attached, not the menu itmanager.
1089 1090 1091 1092 1093
 *
 * Also note that the widgets constructed by a ui manager are not tied to 
 * the lifecycle of the ui manager. If you add the widgets returned by this 
 * function to some container or explicitly ref them, they will survive the
 * destruction of the ui manager.
1094 1095
 *
 * Return value: (transfer none): the widget found by following the path, or %NULL if no widget
1096 1097 1098 1099 1100
 *   was found.
 *
 * Since: 2.4
 **/
GtkWidget *
1101
gtk_ui_manager_get_widget (GtkUIManager *manager,
1102 1103
			   const gchar  *path)
{
1104
  g_return_val_if_fail (GTK_IS_UI_MANAGER (manager), NULL);
1105
  g_return_val_if_fail (path != NULL, NULL);
1106

1107
  return GTK_UI_MANAGER_GET_CLASS (manager)->get_widget (manager, path);
1108 1109
}

1110 1111 1112 1113 1114
typedef struct {
  GtkUIManagerItemType types;
  GSList *list;
} ToplevelData;

1115 1116 1117 1118
static void
collect_toplevels (GNode   *node, 
		   gpointer user_data)
{
1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
  ToplevelData *data = user_data;

  if (NODE_INFO (node)->proxy)
    {
      switch (NODE_INFO (node)->type) 
	{
	case NODE_TYPE_MENUBAR:
	  if (data->types & GTK_UI_MANAGER_MENUBAR)
	data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
	  break;
	case NODE_TYPE_TOOLBAR:
      if (data->types & GTK_UI_MANAGER_TOOLBAR)
	data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
      break;
	case NODE_TYPE_POPUP:
	  if (data->types & GTK_UI_MANAGER_POPUP)
	    data->list = g_slist_prepend (data->list, NODE_INFO (node)->proxy);
	  break;
	default: ;
	}
    }
1140 1141 1142 1143
}

/**
 * gtk_ui_manager_get_toplevels:
1144
 * @manager: a #GtkUIManager
1145 1146 1147 1148 1149
 * @types: specifies the types of toplevel widgets to include. Allowed
 *   types are #GTK_UI_MANAGER_MENUBAR, #GTK_UI_MANAGER_TOOLBAR and
 *   #GTK_UI_MANAGER_POPUP.
 * 
 * Obtains a list of all toplevel widgets of the requested types.
1150 1151 1152
 *
 * Return value: (element-type GtkWidget) (transfer container): a newly-allocated #GSList of
 * all toplevel widgets of the requested types.  Free the returned list with g_slist_free().
1153 1154 1155 1156
 *
 * Since: 2.4
 **/
GSList *
1157
gtk_ui_manager_get_toplevels (GtkUIManager         *manager,
1158 1159
			      GtkUIManagerItemType  types)
{
1160
  ToplevelData data;
1161

1162
  g_return_val_if_fail (GTK_IS_UI_MANAGER (manager), NULL);
1163 1164 1165 1166 1167 1168 1169 1170
  g_return_val_if_fail ((~(GTK_UI_MANAGER_MENUBAR | 
			   GTK_UI_MANAGER_TOOLBAR |
			   GTK_UI_MANAGER_POPUP) & types) == 0, NULL);
  
      
  data.types = types;
  data.list = NULL;

1171
  g_node_children_foreach (manager->private_data->root_node, 
1172 1173 1174 1175 1176 1177 1178
			   G_TRAVERSE_ALL, 
			   collect_toplevels, &data);

  return data.list;
}


1179 1180
/**
 * gtk_ui_manager_get_action:
1181
 * @manager: a #GtkUIManager
1182 1183
 * @path: a path
 * 
Matthias Clasen's avatar
Matthias Clasen committed
1184 1185
 * Looks up an action by following a path. See gtk_ui_manager_get_widget()
 * for more information about paths.
1186
 * 
1187
 * Return value: (transfer none): the action whose proxy widget is found by following the path, 
1188 1189 1190 1191
 *     or %NULL if no widget was found.
 *
 * Since: 2.4
 **/
1192
GtkAction *
1193
gtk_ui_manager_get_action (GtkUIManager *manager,
Matthias Clasen's avatar
Matthias Clasen committed
1194
			   const gchar  *path)
1195
{
1196
  g_return_val_if_fail (GTK_IS_UI_MANAGER (manager), NULL);
1197
  g_return_val_if_fail (path != NULL, NULL);
1198

1199
  return GTK_UI_MANAGER_GET_CLASS (manager)->get_action (manager, path);
1200 1201
}

1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218
static gboolean
node_is_dead (GNode *node)
{
  GNode *child;

  if (NODE_INFO (node)->uifiles != NULL)
    return FALSE;

  for (child = node->children; child != NULL; child = child->next)
    {
      if (!node_is_dead (child))
	return FALSE;
    }

  return TRUE;
}

1219
static GNode *
1220
get_child_node (GtkUIManager *manager, 
Matthias Clasen's avatar
Matthias Clasen committed
1221
		GNode        *parent,
1222
		GNode        *sibling,
Matthias Clasen's avatar
Matthias Clasen committed
1223 1224 1225 1226 1227
		const gchar  *childname, 
		gint          childname_length,
		NodeType      node_type,
		gboolean      create, 
		gboolean      top)
1228 1229 1230 1231 1232 1233 1234 1235 1236
{
  GNode *child = NULL;

  if (parent)
    {
      if (childname)
	{
	  for (child = parent->children; child != NULL; child = child->next)
	    {
1237 1238
	      if (NODE_INFO (child)->name &&
		  strlen (NODE_INFO (child)->name) == childname_length &&
1239 1240 1241
		  !strncmp (NODE_INFO (child)->name, childname, childname_length))
		{
		  /* if undecided about node type, set it */
1242
		  if (NODE_INFO (child)->type == NODE_TYPE_UNDECIDED)
1243 1244 1245
		    NODE_INFO (child)->type = node_type;
		  
		  /* warn about type mismatch */
1246 1247
		  if (NODE_INFO (child)->type != NODE_TYPE_UNDECIDED &&
		      node_type != NODE_TYPE_UNDECIDED &&
1248 1249 1250 1251 1252
		      NODE_INFO (child)->type != node_type)
		    g_warning ("node type doesn't match %d (%s is type %d)",
			       node_type, 
			       NODE_INFO (child)->name,
			       NODE_INFO (child)->type);
1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264

                    if (node_is_dead (child))
                      {
                        /* This node was removed but is still dirty so
                         * it is still in the tree. We want to treat this
                         * as if it didn't exist, which means we move it
                         * to the position it would have been created at.
                         */
                        g_node_unlink (child);
                        goto insert_child;
                      }

1265 1266 1267 1268 1269 1270
		  return child;
		}
	    }
	}
      if (!child && create)
	{
1271
	  Node *mnode;
1272
	  
1273
	  mnode = g_slice_new0 (Node);
1274 1275 1276
	  mnode->type = node_type;
	  mnode->name = g_strndup (childname, childname_length);

1277 1278
	  child = g_node_new (mnode);
	insert_child:
1279 1280 1281
	  if (sibling)
	    {
	      if (top)
1282
		g_node_insert_before (parent, sibling, child);