summaryrefslogtreecommitdiff
path: root/gtk
diff options
context:
space:
mode:
authorMatthias Clasen <maclas@gmx.de>2003-08-24 19:58:30 +0000
committerMatthias Clasen <matthiasc@src.gnome.org>2003-08-24 19:58:30 +0000
commite7e395652cfac4aa5385aa91fd6079fae13dd129 (patch)
tree0673f20be9d82055803dd59a058961c0f0d8b964 /gtk
parent9897d659c082f5e2661b5c26a24c3ed8d0096f0d (diff)
downloadgtk+-e7e395652cfac4aa5385aa91fd6079fae13dd129.tar.gz
A model-view separation for menus and toolbars, using the EggMenu code by
2003-08-24 Matthias Clasen <maclas@gmx.de> * gtk/gtkaction.[ch]: * gtk/gtktoggleaction.[ch]: * gtk/gtktoggleactionprivate.h: * gtk/gtkradioaction.[ch]: * gtk/gtkactiongroup.[ch]: * gtk/gtkmenumerge.[ch]: A model-view separation for menus and toolbars, using the EggMenu code by James Henstridge. * gtk/gtk.h: Include new headers. * gtk/Makefile.am: Add new files. * tests/testactions.c: Test for actions. * tests/testmerge.c: Test for menu merging. * tests/merge-[123].ui: Test data for testmerge. * tests/Makefile.am: Add testactions and testmerge. * demos/gtk-demo/appwindow.c: Use GtkMenuMerge to construct the menubar and toolbar.
Diffstat (limited to 'gtk')
-rw-r--r--gtk/Makefile.am15
-rw-r--r--gtk/gtk.h6
-rw-r--r--gtk/gtkaction.c902
-rw-r--r--gtk/gtkaction.h107
-rw-r--r--gtk/gtkactiongroup.c356
-rw-r--r--gtk/gtkactiongroup.h109
-rw-r--r--gtk/gtkmenumerge.c1652
-rw-r--r--gtk/gtkmenumerge.h109
-rw-r--r--gtk/gtkradioaction.c240
-rw-r--r--gtk/gtkradioaction.h73
-rw-r--r--gtk/gtktoggleaction.c246
-rw-r--r--gtk/gtktoggleaction.h76
-rw-r--r--gtk/gtktoggleactionprivate.h42
13 files changed, 3932 insertions, 1 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index abca8097bc..8b59f9e9e3 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -91,6 +91,8 @@ gtk_public_h_sources = \
gtkaccellabel.h \
gtkaccelmap.h \
gtkaccessible.h \
+ gtkaction.h \
+ gtkactiongroup.h \
gtkadjustment.h \
gtkalignment.h \
gtkarrow.h \
@@ -165,6 +167,7 @@ gtk_public_h_sources = \
gtkmenu.h \
gtkmenubar.h \
gtkmenuitem.h \
+ gtkmenumerge.h \
gtkmenushell.h \
gtkmessagedialog.h \
gtkmisc.h \
@@ -179,11 +182,13 @@ gtk_public_h_sources = \
gtkprivate.h \
gtkprogress.h \
gtkprogressbar.h \
+ gtkradioaction.h \
gtkradiobutton.h \
gtkradiomenuitem.h \
gtkradiotoolbutton.h \
gtkrange.h \
gtkrc.h \
+ gtkresizegrip.h \
gtkruler.h \
gtkscale.h \
gtkscrollbar.h \
@@ -213,6 +218,7 @@ gtk_public_h_sources = \
gtktexttagtable.h \
gtktextview.h \
gtktipsquery.h \
+ gtktoggleaction.h \
gtktogglebutton.h \
gtktoggletoolbutton.h \
gtktoolbar.h \
@@ -257,13 +263,16 @@ gtk_private_h_sources = \
gtkthemes.h \
gtktreedatalist.h \
gtktreeprivate.h \
- gtkwindow-decorate.h
+ gtkwindow-decorate.h \
+ gtktoggleactionprivate.h
# GTK+ C sources to build the library from
gtk_c_sources = \
gtkaccelgroup.c \
gtkaccelmap.c \
gtkaccellabel.c \
+ gtkaction.c \
+ gtkactiongroup.c \
gtkradiotoolbutton.c \
gtktoggletoolbutton.c \
gtktoolbar.c \
@@ -350,6 +359,7 @@ gtk_c_sources = \
gtkmarshalers.c \
gtkmarshal.c \
gtkmenu.c \
+ gtkmenumerge.c \
gtkmenubar.c \
gtkmenuitem.c \
gtkmenushell.c \
@@ -364,11 +374,13 @@ gtk_c_sources = \
gtkpreview.c \
gtkprogress.c \
gtkprogressbar.c \
+ gtkradioaction.c \
gtkradiobutton.c \
gtkradiomenuitem.c \
gtkrange.c \
gtkrbtree.c \
gtkrc.c \
+ gtkresizegrip.c \
gtkruler.c \
gtkscale.c \
gtkscrollbar.c \
@@ -402,6 +414,7 @@ gtk_c_sources = \
gtktextview.c \
gtkthemes.c \
gtktipsquery.c \
+ gtktoggleaction.c \
gtktogglebutton.c \
gtktooltips.c \
gtktree.c \
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 051839def3..ef5a8b99b6 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -33,6 +33,8 @@
#include <gtk/gtkaccellabel.h>
#include <gtk/gtkaccelmap.h>
#include <gtk/gtkaccessible.h>
+#include <gtk/gtkaction.h>
+#include <gtk/gtkactiongroup.h>
#include <gtk/gtkadjustment.h>
#include <gtk/gtkalignment.h>
#include <gtk/gtkarrow.h>
@@ -103,6 +105,7 @@
#include <gtk/gtkmenu.h>
#include <gtk/gtkmenubar.h>
#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkmenumerge.h>
#include <gtk/gtkmenushell.h>
#include <gtk/gtkmessagedialog.h>
#include <gtk/gtkmisc.h>
@@ -116,11 +119,13 @@
#include <gtk/gtkpreview.h>
#include <gtk/gtkprogress.h>
#include <gtk/gtkprogressbar.h>
+#include <gtk/gtkradioaction.h>
#include <gtk/gtkradiobutton.h>
#include <gtk/gtkradiomenuitem.h>
#include <gtk/gtkradiotoolbutton.h>
#include <gtk/gtkrange.h>
#include <gtk/gtkrc.h>
+#include <gtk/gtkresizegrip.h>
#include <gtk/gtkruler.h>
#include <gtk/gtkscale.h>
#include <gtk/gtkscrollbar.h>
@@ -143,6 +148,7 @@
#include <gtk/gtktextbuffer.h>
#include <gtk/gtktextview.h>
#include <gtk/gtktipsquery.h>
+#include <gtk/gtktoggleaction.h>
#include <gtk/gtktogglebutton.h>
#include <gtk/gtktoggletoolbutton.h>
#include <gtk/gtktoolbar.h>
diff --git a/gtk/gtkaction.c b/gtk/gtkaction.c
new file mode 100644
index 0000000000..580742d76f
--- /dev/null
+++ b/gtk/gtkaction.c
@@ -0,0 +1,902 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+
+#include "gtkaction.h"
+#include "gtktoolbutton.h"
+#include "gtkmenuitem.h"
+#include "gtkimagemenuitem.h"
+#include "gtkstock.h"
+#include "gtklabel.h"
+#include "gtkimage.h"
+#include "gtkaccellabel.h"
+#include "gtkintl.h"
+
+
+#define GTK_ACTION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ACTION, GtkActionPrivate))
+
+struct _GtkActionPrivate
+{
+ gchar *name;
+ gchar *label;
+ gchar *short_label;
+ gchar *tooltip;
+ gchar *stock_id; /* icon */
+
+ guint sensitive : 1;
+ guint visible : 1;
+ guint label_set : 1; /* these two used so we can set label */
+ guint short_label_set : 1; /* based on stock id */
+
+ /* accelerator */
+ GQuark accel_quark;
+
+ /* list of proxy widgets */
+ GSList *proxies;
+};
+
+enum
+{
+ ACTIVATE,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_NAME,
+ PROP_LABEL,
+ PROP_SHORT_LABEL,
+ PROP_TOOLTIP,
+ PROP_STOCK_ID,
+ PROP_SENSITIVE,
+ PROP_VISIBLE,
+};
+
+static void gtk_action_init (GtkAction *action);
+static void gtk_action_class_init (GtkActionClass *class);
+
+static GQuark accel_path_id = 0;
+static const gchar *accel_path_key = "GtkAction::accel_path";
+
+GType
+gtk_action_get_type (void)
+{
+ static GtkType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo type_info =
+ {
+ sizeof (GtkActionClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gtk_action_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+
+ sizeof (GtkAction),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gtk_action_init,
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GtkAction",
+ &type_info, 0);
+ }
+ return type;
+}
+
+static void gtk_action_finalize (GObject *object);
+static void gtk_action_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gtk_action_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static GtkWidget *create_menu_item (GtkAction *action);
+static GtkWidget *create_tool_item (GtkAction *action);
+static void connect_proxy (GtkAction *action,
+ GtkWidget *proxy);
+static void disconnect_proxy (GtkAction *action,
+ GtkWidget *proxy);
+
+static GObjectClass *parent_class = NULL;
+static guint action_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gtk_action_class_init (GtkActionClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ accel_path_id = g_quark_from_static_string (accel_path_key);
+
+ parent_class = g_type_class_peek_parent (klass);
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gtk_action_finalize;
+ gobject_class->set_property = gtk_action_set_property;
+ gobject_class->get_property = gtk_action_get_property;
+
+ klass->activate = NULL;
+
+ klass->create_menu_item = create_menu_item;
+ klass->create_tool_item = create_tool_item;
+ klass->connect_proxy = connect_proxy;
+ klass->disconnect_proxy = disconnect_proxy;
+
+ klass->menu_item_type = GTK_TYPE_IMAGE_MENU_ITEM;
+ klass->toolbar_item_type = GTK_TYPE_TOOL_BUTTON;
+
+ g_object_class_install_property (gobject_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ _("Name"),
+ _("A unique name for the action."),
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (gobject_class,
+ PROP_LABEL,
+ g_param_spec_string ("label",
+ _("Label"),
+ _("The label used for menu items and buttons that activate this action."),
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class,
+ PROP_SHORT_LABEL,
+ g_param_spec_string ("short_label",
+ _("Short label"),
+ _("A shorter label that may be used on toolbar buttons."),
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class,
+ PROP_TOOLTIP,
+ g_param_spec_string ("tooltip",
+ _("Tooltip"),
+ _("A tooltip for this action."),
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class,
+ PROP_STOCK_ID,
+ g_param_spec_string ("stock_id",
+ _("Stock Icon"),
+ _("The stock icon displayed in widgets representing this action."),
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class,
+ PROP_SENSITIVE,
+ g_param_spec_boolean ("sensitive",
+ _("Sensitive"),
+ _("Whether the action is enabled."),
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_VISIBLE,
+ g_param_spec_boolean ("visible",
+ _("Visible"),
+ _("Whether the action is visible."),
+ TRUE,
+ G_PARAM_READWRITE));
+
+ action_signals[ACTIVATE] =
+ g_signal_new ("activate",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
+ G_STRUCT_OFFSET (GtkActionClass, activate), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (gobject_class, sizeof (GtkActionPrivate));
+}
+
+
+static void
+gtk_action_init (GtkAction *action)
+{
+ action->private_data = GTK_ACTION_GET_PRIVATE (action);
+
+ action->private_data->name = NULL;
+ action->private_data->label = NULL;
+ action->private_data->short_label = NULL;
+ action->private_data->tooltip = NULL;
+ action->private_data->stock_id = NULL;
+
+ action->private_data->sensitive = TRUE;
+ action->private_data->visible = TRUE;
+
+ action->private_data->label_set = FALSE;
+ action->private_data->short_label_set = FALSE;
+
+ action->private_data->accel_quark = 0;
+
+ action->private_data->proxies = NULL;
+}
+
+static void
+gtk_action_finalize (GObject *object)
+{
+ GtkAction *action;
+
+ action = GTK_ACTION (object);
+
+ g_free (action->private_data->name);
+ g_free (action->private_data->label);
+ g_free (action->private_data->short_label);
+ g_free (action->private_data->tooltip);
+ g_free (action->private_data->stock_id);
+}
+
+static void
+gtk_action_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAction *action;
+ gchar *tmp;
+
+ action = GTK_ACTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ tmp = action->private_data->name;
+ action->private_data->name = g_value_dup_string (value);
+ g_free (tmp);
+ break;
+ case PROP_LABEL:
+ tmp = action->private_data->label;
+ action->private_data->label = g_value_dup_string (value);
+ g_free (tmp);
+ action->private_data->label_set = (action->private_data->label != NULL);
+ /* if label is unset, then use the label from the stock item */
+ if (!action->private_data->label_set && action->private_data->stock_id)
+ {
+ GtkStockItem stock_item;
+
+ if (gtk_stock_lookup (action->private_data->stock_id, &stock_item))
+ action->private_data->label = g_strdup (stock_item.label);
+ }
+ /* if short_label is unset, set short_label=label */
+ if (!action->private_data->short_label_set)
+ {
+ tmp = action->private_data->short_label;
+ action->private_data->short_label = g_strdup (action->private_data->label);
+ g_free (tmp);
+ g_object_notify (object, "short_label");
+ }
+ break;
+ case PROP_SHORT_LABEL:
+ tmp = action->private_data->short_label;
+ action->private_data->short_label = g_value_dup_string (value);
+ g_free (tmp);
+ action->private_data->short_label_set = (action->private_data->short_label != NULL);
+ /* if short_label is unset, then use the value of label */
+ if (!action->private_data->short_label_set)
+ {
+ action->private_data->short_label = g_strdup (action->private_data->label);
+ }
+ break;
+ case PROP_TOOLTIP:
+ tmp = action->private_data->tooltip;
+ action->private_data->tooltip = g_value_dup_string (value);
+ g_free (tmp);
+ break;
+ case PROP_STOCK_ID:
+ tmp = action->private_data->stock_id;
+ action->private_data->stock_id = g_value_dup_string (value);
+ g_free (tmp);
+ /* update label and short_label if appropriate */
+ if (!action->private_data->label_set)
+ {
+ GtkStockItem stock_item;
+
+ g_free (action->private_data->label);
+ if (gtk_stock_lookup (action->private_data->stock_id, &stock_item))
+ action->private_data->label = g_strdup (stock_item.label);
+ else
+ action->private_data->label = NULL;
+ g_object_notify (object, "label");
+ }
+ if (!action->private_data->short_label_set)
+ {
+ tmp = action->private_data->short_label;
+ action->private_data->short_label = g_strdup (action->private_data->label);
+ g_free (tmp);
+ g_object_notify (object, "short_label");
+ }
+ break;
+ case PROP_SENSITIVE:
+ action->private_data->sensitive = g_value_get_boolean (value);
+ break;
+ case PROP_VISIBLE:
+ action->private_data->visible = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_action_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAction *action;
+
+ action = GTK_ACTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, action->private_data->name);
+ break;
+ case PROP_LABEL:
+ g_value_set_string (value, action->private_data->label);
+ break;
+ case PROP_SHORT_LABEL:
+ g_value_set_string (value, action->private_data->short_label);
+ break;
+ case PROP_TOOLTIP:
+ g_value_set_string (value, action->private_data->tooltip);
+ break;
+ case PROP_STOCK_ID:
+ g_value_set_string (value, action->private_data->stock_id);
+ break;
+ case PROP_SENSITIVE:
+ g_value_set_boolean (value, action->private_data->sensitive);
+ break;
+ case PROP_VISIBLE:
+ g_value_set_boolean (value, action->private_data->visible);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GtkWidget *
+create_menu_item (GtkAction *action)
+{
+ GType menu_item_type;
+
+ menu_item_type = GTK_ACTION_GET_CLASS (action)->menu_item_type;
+
+ return g_object_new (menu_item_type, NULL);
+}
+
+static GtkWidget *
+create_tool_item (GtkAction *action)
+{
+ GType toolbar_item_type;
+
+ toolbar_item_type = GTK_ACTION_GET_CLASS (action)->toolbar_item_type;
+
+ return g_object_new (toolbar_item_type, NULL);
+}
+
+static void
+gtk_action_remove_proxy (GtkWidget *widget,
+ GtkAction *action)
+{
+ action->private_data->proxies = g_slist_remove (action->private_data->proxies, widget);
+}
+
+static void
+gtk_action_sync_property (GtkAction *action,
+ GParamSpec *pspec,
+ GtkWidget *proxy)
+{
+ const gchar *property;
+ GValue value = { 0, };
+
+ property = g_param_spec_get_name (pspec);
+
+ g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+ g_object_get_property (G_OBJECT (action), property, &value);
+
+ g_object_set_property (G_OBJECT (proxy), property, &value);
+ g_value_unset (&value);
+}
+
+static void
+gtk_action_sync_label (GtkAction *action,
+ GParamSpec *pspec,
+ GtkWidget *proxy)
+{
+ GtkWidget *label = NULL;
+
+ g_return_if_fail (GTK_IS_MENU_ITEM (proxy));
+ label = GTK_BIN (proxy)->child;
+
+ if (GTK_IS_LABEL (label))
+ gtk_label_set_label (GTK_LABEL (label), action->private_data->label);
+}
+
+static void
+gtk_action_sync_short_label (GtkAction *action,
+ GParamSpec *pspec,
+ GtkWidget *proxy)
+{
+ GValue value = { 0, };
+
+ g_value_init (&value, G_TYPE_STRING);
+ g_object_get_property (G_OBJECT (action), "short_label", &value);
+
+ g_object_set_property (G_OBJECT (proxy), "label", &value);
+ g_value_unset (&value);
+}
+
+static void
+gtk_action_sync_stock_id (GtkAction *action,
+ GParamSpec *pspec,
+ GtkWidget *proxy)
+{
+ GtkWidget *image = NULL;
+
+ if (GTK_IS_IMAGE_MENU_ITEM (proxy))
+ {
+ image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (proxy));
+
+ if (GTK_IS_IMAGE (image))
+ gtk_image_set_from_stock (GTK_IMAGE (image),
+ action->private_data->stock_id, GTK_ICON_SIZE_MENU);
+ }
+}
+
+static gboolean
+gtk_action_create_menu_proxy (GtkToolItem *tool_item,
+ GtkAction *action)
+{
+ GtkWidget *menu_item = gtk_action_create_menu_item (action);
+
+ g_object_ref (menu_item);
+ gtk_object_sink (GTK_OBJECT (menu_item));
+
+ gtk_tool_item_set_proxy_menu_item (tool_item, "gtk-action-menu-item", menu_item);
+ g_object_unref (menu_item);
+
+ return TRUE;
+}
+
+static void
+connect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ g_object_ref (action);
+ g_object_set_data_full (G_OBJECT (proxy), "gtk-action", action,
+ g_object_unref);
+
+ /* add this widget to the list of proxies */
+ action->private_data->proxies = g_slist_prepend (action->private_data->proxies, proxy);
+ g_signal_connect (proxy, "destroy",
+ G_CALLBACK (gtk_action_remove_proxy), action);
+
+ g_signal_connect_object (action, "notify::sensitive",
+ G_CALLBACK (gtk_action_sync_property), proxy, 0);
+ gtk_widget_set_sensitive (proxy, action->private_data->sensitive);
+
+ g_signal_connect_object (action, "notify::visible",
+ G_CALLBACK (gtk_action_sync_property), proxy, 0);
+ if (action->private_data->visible)
+ gtk_widget_show (proxy);
+ else
+ gtk_widget_hide (proxy);
+
+ if (GTK_IS_MENU_ITEM (proxy))
+ {
+ GtkWidget *label;
+ /* menu item specific synchronisers ... */
+
+ label = GTK_BIN (proxy)->child;
+
+ /* make sure label is a label */
+ if (label && !GTK_IS_LABEL (label))
+ {
+ gtk_container_remove (GTK_CONTAINER (proxy), label);
+ label = NULL;
+ }
+ if (!label)
+ {
+ label = g_object_new (GTK_TYPE_ACCEL_LABEL,
+ "use_underline", TRUE,
+ "xalign", 0.0,
+ "visible", TRUE,
+ "parent", proxy,
+ "accel_widget", proxy,
+ NULL);
+ }
+ gtk_label_set_label (GTK_LABEL (label), action->private_data->label);
+ g_signal_connect_object (action, "notify::label",
+ G_CALLBACK (gtk_action_sync_label), proxy, 0);
+
+ if (GTK_IS_IMAGE_MENU_ITEM (proxy))
+ {
+ GtkWidget *image;
+
+ image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (proxy));
+ if (image && !GTK_IS_IMAGE (image))
+ {
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy), NULL);
+ image = NULL;
+ }
+ if (!image)
+ {
+ image = gtk_image_new_from_stock (NULL,
+ GTK_ICON_SIZE_MENU);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy),
+ image);
+ gtk_widget_show (image);
+ }
+ gtk_image_set_from_stock (GTK_IMAGE (image),
+ action->private_data->stock_id, GTK_ICON_SIZE_MENU);
+ g_signal_connect_object (action, "notify::stock_id",
+ G_CALLBACK (gtk_action_sync_stock_id),
+ proxy, 0);
+ }
+
+ if (action->private_data->accel_quark)
+ {
+ gtk_menu_item_set_accel_path (GTK_MENU_ITEM (proxy),
+ g_quark_to_string (action->private_data->accel_quark));
+ }
+
+ g_signal_connect_object (proxy, "activate",
+ G_CALLBACK (gtk_action_activate), action,
+ G_CONNECT_SWAPPED);
+ }
+ else if (GTK_IS_TOOL_BUTTON (proxy))
+ {
+ /* toolbar button specific synchronisers ... */
+
+ /* synchronise the label */
+ g_object_set (G_OBJECT (proxy),
+ "label", action->private_data->short_label,
+ "use_underline", TRUE,
+ NULL);
+ g_signal_connect_object (action, "notify::short_label",
+ G_CALLBACK (gtk_action_sync_short_label),
+ proxy, 0);
+
+ g_object_set (G_OBJECT (proxy), "stock_id", action->private_data->stock_id, NULL);
+ g_signal_connect_object (action, "notify::stock_id",
+ G_CALLBACK (gtk_action_sync_property), proxy, 0);
+
+ g_signal_connect_object (proxy, "create_menu_proxy",
+ G_CALLBACK (gtk_action_create_menu_proxy),
+ action, 0);
+
+ g_signal_connect_object (proxy, "clicked",
+ G_CALLBACK (gtk_action_activate), action,
+ G_CONNECT_SWAPPED);
+ }
+}
+
+static void
+disconnect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ static guint notify_id = 0;
+
+ if (!notify_id)
+ notify_id = g_signal_lookup ("notify", G_TYPE_OBJECT);
+
+ g_object_set_data (G_OBJECT (proxy), "gtk-action", NULL);
+
+ /* remove proxy from list of proxies */
+ g_signal_handlers_disconnect_by_func (proxy,
+ G_CALLBACK (gtk_action_remove_proxy),
+ action);
+ gtk_action_remove_proxy (proxy, action);
+
+ /* disconnect the activate handler */
+ g_signal_handlers_disconnect_by_func (proxy,
+ G_CALLBACK (gtk_action_activate),
+ action);
+
+ /* disconnect handlers for notify::* signals */
+ g_signal_handlers_disconnect_by_func (proxy,
+ G_CALLBACK (gtk_action_sync_property),
+ action);
+
+ g_signal_handlers_disconnect_by_func (action,
+ G_CALLBACK (gtk_action_sync_stock_id), proxy);
+
+ /* menu item specific synchronisers ... */
+ g_signal_handlers_disconnect_by_func (action,
+ G_CALLBACK (gtk_action_sync_label),
+ proxy);
+
+ gtk_menu_item_set_accel_path (GTK_MENU_ITEM (proxy), NULL);
+
+ /* toolbar button specific synchronisers ... */
+ g_signal_handlers_disconnect_by_func (action,
+ G_CALLBACK (gtk_action_sync_short_label),
+ proxy);
+ g_signal_handlers_disconnect_by_func (proxy,
+ G_CALLBACK (gtk_action_create_menu_proxy),
+ action);
+}
+
+/**
+ * gtk_action_activate:
+ * @action: the action object
+ *
+ * Emits the "activate" signal on the specified action.
+ * This gets called by the proxy widgets when they get activated.
+ *
+ * It can also be used to manually activate an action.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_activate (GtkAction *action)
+{
+ g_signal_emit (action, action_signals[ACTIVATE], 0);
+}
+
+/**
+ * gtk_action_create_icon:
+ * @action: the action object
+ * @icon_size: the size of the icon that should be created.
+ *
+ * This function is intended for use by action implementations to
+ * create icons displayed in the proxy widgets.
+ *
+ * Returns: a widget that displays the icon for this action.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+gtk_action_create_icon (GtkAction *action, GtkIconSize icon_size)
+{
+ g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
+
+ if (action->private_data->stock_id)
+ return gtk_image_new_from_stock (action->private_data->stock_id, icon_size);
+ else
+ return NULL;
+}
+
+/**
+ * gtk_action_create_menu_item:
+ * @action: the action object
+ *
+ * Creates a menu item widget that proxies for the given action.
+ *
+ * Returns: a menu item connected to the action.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+gtk_action_create_menu_item (GtkAction *action)
+{
+ GtkWidget *menu_item;
+
+ g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
+
+ menu_item = (* GTK_ACTION_GET_CLASS (action)->create_menu_item) (action);
+
+ (* GTK_ACTION_GET_CLASS (action)->connect_proxy) (action, menu_item);
+
+ return menu_item;
+}
+
+/**
+ * gtk_action_create_tool_item:
+ * @action: the action object
+ *
+ * Creates a toolbar item widget that proxies for the given action.
+ *
+ * Returns: a toolbar item connected to the action.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+gtk_action_create_tool_item (GtkAction *action)
+{
+ GtkWidget *button;
+
+ g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
+
+ button = (* GTK_ACTION_GET_CLASS (action)->create_tool_item) (action);
+
+ (* GTK_ACTION_GET_CLASS (action)->connect_proxy) (action, button);
+
+ return button;
+}
+
+/**
+ * gtk_action_connect_proxy:
+ * @action: the action object
+ * @proxy: the proxy widget
+ *
+ * Connects a widget to an action object as a proxy. Synchronises
+ * various properties of the action with the widget (such as label
+ * text, icon, tooltip, etc), and attaches a callback so that the
+ * action gets activated when the proxy widget does.
+ *
+ * If the widget is already connected to an action, it is disconnected
+ * first.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_connect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ GtkAction *prev_action;
+
+ g_return_if_fail (GTK_IS_ACTION (action));
+ g_return_if_fail (GTK_IS_WIDGET (proxy));
+
+ prev_action = g_object_get_data (G_OBJECT (proxy), "gtk-action");
+
+ if (prev_action)
+ {
+ (* GTK_ACTION_GET_CLASS (action)->disconnect_proxy) (action, proxy);
+ }
+
+ (* GTK_ACTION_GET_CLASS (action)->connect_proxy) (action, proxy);
+}
+
+/**
+ * gtk_action_disconnect_proxy:
+ * @action: the action object
+ * @proxy: the proxy widget
+ *
+ * Disconnects a proxy widget from an action.
+ * Does <emphasis>not</emphasis> destroy the widget, however.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_disconnect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ g_return_if_fail (GTK_IS_ACTION (action));
+ g_return_if_fail (GTK_IS_WIDGET (proxy));
+
+ g_return_if_fail (g_object_get_data (G_OBJECT (proxy), "gtk-action") != action);
+
+ (* GTK_ACTION_GET_CLASS (action)->disconnect_proxy) (action, proxy);
+}
+
+/**
+ * gtk_action_get_proxies:
+ * @action: the action object
+ *
+ * Returns the proxy widgets for an action.
+ *
+ * Return value: a #GSList of proxy widgets. The list is owned by the action and
+ * must not be modified.
+ *
+ * Since: 2.4
+ **/
+GSList*
+gtk_action_get_proxies (GtkAction *action)
+{
+ g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
+
+ return action->private_data->proxies;
+}
+
+
+/**
+ * gtk_action_get_name:
+ * @action: the action object
+ *
+ * Returns the name of the action.
+ *
+ * Return value: the name of the action. The string belongs to GTK+ and should not
+ * be freed.
+ *
+ * Since: 2.4
+ **/
+const gchar *
+gtk_action_get_name (GtkAction *action)
+{
+ g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
+
+ return action->private_data->name;
+}
+
+/**
+ * gtk_action_block_activate_from:
+ * @action: the action object
+ * @proxy: a proxy widget
+ *
+ * Disables calls to the gtk_action_activate()
+ * function by signals on the given proxy widget. This is used to
+ * break notification loops for things like check or radio actions.
+ *
+ * This function is intended for use by action implementations.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_block_activate_from (GtkAction *action,
+ GtkWidget *proxy)
+{
+ g_return_if_fail (GTK_IS_ACTION (action));
+
+ g_signal_handlers_block_by_func (proxy, G_CALLBACK (gtk_action_activate),
+ action);
+}
+
+/**
+ * gtk_action_unblock_activate_from:
+ * @action: the action object
+ * @proxy: a proxy widget
+ *
+ * Re-enables calls to the gtk_action_activate()
+ * function by signals on the given proxy widget. This undoes the
+ * blocking done by gtk_action_block_activate_from().
+ *
+ * This function is intended for use by action implementations.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_unblock_activate_from (GtkAction *action,
+ GtkWidget *proxy)
+{
+ g_return_if_fail (GTK_IS_ACTION (action));
+
+ g_signal_handlers_unblock_by_func (proxy, G_CALLBACK (gtk_action_activate),
+ action);
+}
+
+/**
+ * gtk_action_set_accel_path:
+ * @action: the action object
+ * @accel_path: the accelerator path
+ *
+ * Sets the accel path for this action. All proxy widgets associated
+ * with the action will have this accel path, so that their
+ * accelerators are consistent.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_set_accel_path (GtkAction *action,
+ const gchar *accel_path)
+{
+ action->private_data->accel_quark = g_quark_from_string (accel_path);
+}
diff --git a/gtk/gtkaction.h b/gtk/gtkaction.h
new file mode 100644
index 0000000000..7d8a402241
--- /dev/null
+++ b/gtk/gtkaction.h
@@ -0,0 +1,107 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+#ifndef __GTK_ACTION_H__
+#define __GTK_ACTION_H__
+
+#include <gtk/gtkwidget.h>
+#include <glib-object.h>
+
+#define GTK_TYPE_ACTION (gtk_action_get_type ())
+#define GTK_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ACTION, GtkAction))
+#define GTK_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ACTION, GtkActionClass))
+#define GTK_IS_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ACTION))
+#define GTK_IS_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GTK_TYPE_ACTION))
+#define GTK_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_ACTION, GtkActionClass))
+
+typedef struct _GtkAction GtkAction;
+typedef struct _GtkActionClass GtkActionClass;
+typedef struct _GtkActionPrivate GtkActionPrivate;
+
+struct _GtkAction
+{
+ GObject object;
+
+ /*< private >*/
+
+ GtkActionPrivate *private_data;
+};
+
+struct _GtkActionClass
+{
+ GObjectClass parent_class;
+
+ /* activation signal */
+ void (* activate) (GtkAction *action);
+
+ GType menu_item_type;
+ GType toolbar_item_type;
+
+ /* widget creation routines (not signals) */
+ GtkWidget *(* create_menu_item) (GtkAction *action);
+ GtkWidget *(* create_tool_item) (GtkAction *action);
+ void (* connect_proxy) (GtkAction *action,
+ GtkWidget *proxy);
+ void (* disconnect_proxy) (GtkAction *action,
+ GtkWidget *proxy);
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType gtk_action_get_type (void);
+
+const gchar* gtk_action_get_name (GtkAction *action);
+void gtk_action_activate (GtkAction *action);
+
+GtkWidget *gtk_action_create_icon (GtkAction *action,
+ GtkIconSize icon_size);
+GtkWidget *gtk_action_create_menu_item (GtkAction *action);
+GtkWidget *gtk_action_create_tool_item (GtkAction *action);
+void gtk_action_connect_proxy (GtkAction *action,
+ GtkWidget *proxy);
+void gtk_action_disconnect_proxy (GtkAction *action,
+ GtkWidget *proxy);
+GSList *gtk_action_get_proxies (GtkAction *action);
+
+/* protected ... for use by child actions */
+void gtk_action_block_activate_from (GtkAction *action,
+ GtkWidget *proxy);
+void gtk_action_unblock_activate_from (GtkAction *action,
+ GtkWidget *proxy);
+
+/* protected ... for use by action groups */
+void gtk_action_set_accel_path (GtkAction *action,
+ const gchar *accel_path);
+
+
+#endif /* __GTK_ACTION_H__ */
diff --git a/gtk/gtkactiongroup.c b/gtk/gtkactiongroup.c
new file mode 100644
index 0000000000..69122755ef
--- /dev/null
+++ b/gtk/gtkactiongroup.c
@@ -0,0 +1,356 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+
+#include "gtkactiongroup.h"
+#include "gtktoggleaction.h"
+#include "gtkradioaction.h"
+#include "gtkaccelmap.h"
+#include "gtkintl.h"
+
+#define GTK_ACTION_GROUP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ACTION_GROUP, GtkActionGroupPrivate))
+
+struct _GtkActionGroupPrivate
+{
+ gchar *name;
+ GHashTable *actions;
+};
+
+static void gtk_action_group_init (GtkActionGroup *self);
+static void gtk_action_group_class_init (GtkActionGroupClass *class);
+
+GType
+gtk_action_group_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo type_info =
+ {
+ sizeof (GtkActionGroupClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) gtk_action_group_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (GtkActionGroup),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gtk_action_group_init,
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT, "GtkActionGroup",
+ &type_info, 0);
+ }
+
+ return type;
+}
+
+static GObjectClass *parent_class = NULL;
+static void gtk_action_group_finalize (GObject *object);
+static GtkAction *gtk_action_group_real_get_action (GtkActionGroup *self,
+ const gchar *name);
+
+static void
+gtk_action_group_class_init (GtkActionGroupClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class->finalize = gtk_action_group_finalize;
+ klass->get_action = gtk_action_group_real_get_action;
+
+ g_type_class_add_private (gobject_class, sizeof (GtkActionGroupPrivate));
+}
+
+static void
+gtk_action_group_init (GtkActionGroup *self)
+{
+ self->private_data = GTK_ACTION_GROUP_GET_PRIVATE (self);
+ self->private_data->name = NULL;
+ self->private_data->actions = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+}
+
+/**
+ * gtk_action_group_new:
+ * @name: the name of the action group
+ *
+ * Creates a new #GtkActionGroup object.
+ *
+ * Returns: the new #GtkActionGroup
+ *
+ * Since: 2.4
+ */
+GtkActionGroup *
+gtk_action_group_new (const gchar *name)
+{
+ GtkActionGroup *self;
+
+ self = g_object_new (GTK_TYPE_ACTION_GROUP, NULL);
+ self->private_data->name = g_strdup (name);
+
+ return self;
+}
+
+static void
+gtk_action_group_finalize (GObject *object)
+{
+ GtkActionGroup *self;
+
+ self = GTK_ACTION_GROUP (object);
+
+ g_free (self->private_data->name);
+ self->private_data->name = NULL;
+
+ g_hash_table_destroy (self->private_data->actions);
+ self->private_data->actions = NULL;
+
+ if (parent_class->finalize)
+ (* parent_class->finalize) (object);
+}
+
+static GtkAction *
+gtk_action_group_real_get_action (GtkActionGroup *self,
+ const gchar *action_name)
+{
+ return g_hash_table_lookup (self->private_data->actions, action_name);
+}
+
+/**
+ * gtk_action_group_get_name:
+ * @action_group: the action group
+ *
+ * Gets the name of the action group.
+ *
+ * Returns: the name of the action group.
+ *
+ * Since: 2.4
+ */
+const gchar *
+gtk_action_group_get_name (GtkActionGroup *action_group)
+{
+ g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), NULL);
+
+ return action_group->private_data->name;
+}
+
+/**
+ * gtk_action_group_get_action:
+ * @action_group: the action group
+ * @action_name: the name of the action
+ *
+ * Looks up an action in the action group by name.
+ *
+ * Returns: the action, or %NULL if no action by that name exists
+ *
+ * Since: 2.4
+ */
+GtkAction *
+gtk_action_group_get_action (GtkActionGroup *action_group,
+ const gchar *action_name)
+{
+ g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), NULL);
+ g_return_val_if_fail (GTK_ACTION_GROUP_GET_CLASS (action_group)->get_action != NULL, NULL);
+
+ return (* GTK_ACTION_GROUP_GET_CLASS (action_group)->get_action)
+ (action_group, action_name);
+}
+
+/**
+ * gtk_action_group_add_action:
+ * @action_group: the action group
+ * @action: an action
+ *
+ * Adds an action object to the action group.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_group_add_action (GtkActionGroup *action_group,
+ GtkAction *action)
+{
+ g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+ g_return_if_fail (GTK_IS_ACTION (action));
+ g_return_if_fail (gtk_action_get_name (action) != NULL);
+
+ g_hash_table_insert (action_group->private_data->actions,
+ g_strdup (gtk_action_get_name (action)),
+ g_object_ref (action));
+}
+
+/**
+ * gtk_action_group_removes_action:
+ * @action_group: the action group
+ * @action: an action
+ *
+ * Removes an action object from the action group.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_group_remove_action (GtkActionGroup *action_group,
+ GtkAction *action)
+{
+ g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+ g_return_if_fail (GTK_IS_ACTION (action));
+ g_return_if_fail (gtk_action_get_name (action) != NULL);
+
+ /* extra protection to make sure action->name is valid */
+ g_object_ref (action);
+ g_hash_table_remove (action_group->private_data->actions, gtk_action_get_name (action));
+ g_object_unref (action);
+}
+
+static void
+add_single_action (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GList **list = user_data;
+
+ *list = g_list_prepend (*list, value);
+}
+
+/**
+ * gtk_action_group_list_actions:
+ * @action_group: the action group
+ *
+ * Lists the actions in the action group.
+ *
+ * Returns: an allocated list of the action objects in the action group
+ *
+ * Since: 2.4
+ */
+GList *
+gtk_action_group_list_actions (GtkActionGroup *action_group)
+{
+ GList *actions = NULL;
+
+ g_hash_table_foreach (action_group->private_data->actions, add_single_action, &actions);
+
+ return g_list_reverse (actions);
+}
+
+
+/**
+ * gtk_action_group_add_actions:
+ * @action_group: the action group
+ * @entries: an array of action descriptions
+ * @n_entries: the number of entries
+ *
+ * This is a convenience routine to create a number of actions and add
+ * them to the action group. Each member of the array describes an
+ * action to create.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_group_add_actions (GtkActionGroup *action_group,
+ GtkActionGroupEntry *entries,
+ guint n_entries)
+{
+ guint i;
+
+ for (i = 0; i < n_entries; i++)
+ {
+ GtkAction *action;
+ GType action_type;
+ gchar *accel_path;
+
+ switch (entries[i].entry_type) {
+ case NORMAL_ACTION:
+ action_type = GTK_TYPE_ACTION;
+ break;
+ case TOGGLE_ACTION:
+ action_type = GTK_TYPE_TOGGLE_ACTION;
+ break;
+ case RADIO_ACTION:
+ action_type = GTK_TYPE_RADIO_ACTION;
+ break;
+ default:
+ g_warning ("unsupported action type");
+ action_type = GTK_TYPE_ACTION;
+ }
+
+ action = g_object_new (action_type,
+ "name", entries[i].name,
+ "label", _(entries[i].label),
+ "tooltip", _(entries[i].tooltip),
+ "stock_id", entries[i].stock_id,
+ NULL);
+
+ if (entries[i].entry_type == RADIO_ACTION &&
+ entries[i].extra_data != NULL)
+ {
+ GtkAction *radio_action;
+ GSList *group;
+
+ radio_action =
+ gtk_action_group_get_action (GTK_ACTION_GROUP (action_group),
+ entries[i].extra_data);
+ if (radio_action)
+ {
+ group = gtk_radio_action_get_group (GTK_RADIO_ACTION (radio_action));
+ gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group);
+ }
+ else
+ g_warning (G_STRLOC " could not look up `%s'", entries[i].extra_data);
+ }
+
+ if (entries[i].callback)
+ g_signal_connect (action, "activate",
+ entries[i].callback, entries[i].user_data);
+
+ /* set the accel path for the menu item */
+ accel_path = g_strconcat ("<Actions>/", action_group->private_data->name, "/",
+ entries[i].name, NULL);
+ if (entries[i].accelerator)
+ {
+ guint accel_key = 0;
+ GdkModifierType accel_mods;
+
+ gtk_accelerator_parse (entries[i].accelerator, &accel_key,
+ &accel_mods);
+ if (accel_key)
+ gtk_accel_map_add_entry (accel_path, accel_key, accel_mods);
+ }
+
+ gtk_action_set_accel_path (action, accel_path);
+ g_free (accel_path);
+
+ gtk_action_group_add_action (action_group, action);
+ g_object_unref (action);
+ }
+}
diff --git a/gtk/gtkactiongroup.h b/gtk/gtkactiongroup.h
new file mode 100644
index 0000000000..523b05387f
--- /dev/null
+++ b/gtk/gtkactiongroup.h
@@ -0,0 +1,109 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+#ifndef __GTK_ACTION_GROUP_H__
+#define __GTK_ACTION_GROUP_H__
+
+#include <gtk/gtkaction.h>
+
+#define GTK_TYPE_ACTION_GROUP (gtk_action_group_get_type ())
+#define GTK_ACTION_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ACTION_GROUP, GtkActionGroup))
+#define GTK_ACTION_GROUP_CLASS(vtable) (G_TYPE_CHECK_CLASS_CAST ((vtable), GTK_TYPE_ACTION_GROUP, GtkActionGroupClass))
+#define GTK_IS_ACTION_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ACTION_GROUP))
+#define GTK_IS_ACTION_GROUP_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE ((vtable), GTK_TYPE_ACTION_GROUP))
+#define GTK_ACTION_GROUP_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((inst), GTK_TYPE_ACTION_GROUP, GtkActionGroupClass))
+
+typedef struct _GtkActionGroup GtkActionGroup;
+typedef struct _GtkActionGroupPrivate GtkActionGroupPrivate;
+typedef struct _GtkActionGroupClass GtkActionGroupClass;
+typedef struct _GtkActionGroupEntry GtkActionGroupEntry;
+
+struct _GtkActionGroup
+{
+ GObject parent;
+
+ /*< private >*/
+
+ GtkActionGroupPrivate *private_data;
+};
+
+struct _GtkActionGroupClass
+{
+ GObjectClass parent_class;
+
+ GtkAction *(* get_action) (GtkActionGroup *action_group,
+ const gchar *action_name);
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+typedef enum
+{
+ NORMAL_ACTION,
+ TOGGLE_ACTION,
+ RADIO_ACTION
+} GtkActionGroupEntryType;
+
+struct _GtkActionGroupEntry
+{
+ gchar *name;
+ gchar *label;
+ gchar *stock_id;
+ gchar *accelerator;
+ gchar *tooltip;
+
+ GCallback callback;
+ gpointer user_data;
+
+ GtkActionGroupEntryType entry_type;
+ gchar *extra_data;
+};
+
+GType gtk_action_group_get_type (void);
+
+GtkActionGroup *gtk_action_group_new (const gchar *name);
+
+const gchar *gtk_action_group_get_name (GtkActionGroup *action_group);
+GtkAction *gtk_action_group_get_action (GtkActionGroup *action_group,
+ const gchar *action_name);
+GList *gtk_action_group_list_actions (GtkActionGroup *action_group);
+void gtk_action_group_add_action (GtkActionGroup *action_group,
+ GtkAction *action);
+void gtk_action_group_remove_action (GtkActionGroup *action_group,
+ GtkAction *action);
+
+void gtk_action_group_add_actions (GtkActionGroup *action_group,
+ GtkActionGroupEntry *entries,
+ guint n_entries);
+
+#endif /* __GTK_ACTION_GROUP_H__ */
diff --git a/gtk/gtkmenumerge.c b/gtk/gtkmenumerge.c
new file mode 100644
index 0000000000..764734e4f1
--- /dev/null
+++ b/gtk/gtkmenumerge.c
@@ -0,0 +1,1652 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include "gtkmenumerge.h"
+#include "gtktoolbar.h"
+#include "gtkseparatortoolitem.h"
+#include "gtkmenushell.h"
+#include "gtkmenu.h"
+#include "gtkmenubar.h"
+#include "gtkseparatormenuitem.h"
+#include "gtkintl.h"
+
+#undef DEBUG_MENU_MERGE
+
+typedef enum
+{
+ GTK_MENU_MERGE_UNDECIDED,
+ GTK_MENU_MERGE_ROOT,
+ GTK_MENU_MERGE_MENUBAR,
+ GTK_MENU_MERGE_MENU,
+ GTK_MENU_MERGE_TOOLBAR,
+ GTK_MENU_MERGE_MENU_PLACEHOLDER,
+ GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER,
+ GTK_MENU_MERGE_POPUPS,
+ GTK_MENU_MERGE_MENUITEM,
+ GTK_MENU_MERGE_TOOLITEM,
+ GTK_MENU_MERGE_SEPARATOR,
+} GtkMenuMergeNodeType;
+
+
+typedef struct _GtkMenuMergeNode GtkMenuMergeNode;
+
+struct _GtkMenuMergeNode {
+ GtkMenuMergeNodeType type;
+
+ const gchar *name;
+
+ GQuark action_name;
+ GtkAction *action;
+ GtkWidget *proxy;
+ GtkWidget *extra; /*GtkMenu for submenus, second separator for placeholders*/
+
+ GList *uifiles;
+
+ guint dirty : 1;
+};
+
+#define GTK_MENU_MERGE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_MENU_MERGE, GtkMenuMergePrivate))
+
+struct _GtkMenuMergePrivate
+{
+ GtkAccelGroup *accel_group;
+
+ GNode *root_node;
+ GList *action_groups;
+
+ guint last_merge_id;
+
+ guint update_tag;
+};
+
+#define NODE_INFO(node) ((GtkMenuMergeNode *)node->data)
+
+typedef struct _NodeUIReference NodeUIReference;
+
+struct _NodeUIReference
+{
+ guint merge_id;
+ GQuark action_quark;
+};
+
+static void gtk_menu_merge_class_init (GtkMenuMergeClass *class);
+static void gtk_menu_merge_init (GtkMenuMerge *merge);
+
+static void gtk_menu_merge_queue_update (GtkMenuMerge *self);
+static void gtk_menu_merge_dirty_all (GtkMenuMerge *self);
+
+static GNode *get_child_node (GtkMenuMerge *self, GNode *parent,
+ const gchar *childname,
+ gint childname_length,
+ GtkMenuMergeNodeType node_type,
+ gboolean create, gboolean top);
+static GNode *gtk_menu_merge_get_node (GtkMenuMerge *self,
+ const gchar *path,
+ GtkMenuMergeNodeType node_type,
+ gboolean create);
+static guint gtk_menu_merge_next_merge_id (GtkMenuMerge *self);
+
+static void gtk_menu_merge_node_prepend_ui_reference (GtkMenuMergeNode *node,
+ guint merge_id,
+ GQuark action_quark);
+static void gtk_menu_merge_node_remove_ui_reference (GtkMenuMergeNode *node,
+ guint merge_id);
+static void gtk_menu_merge_ensure_update (GtkMenuMerge *self);
+
+
+enum
+{
+ ADD_WIDGET,
+ REMOVE_WIDGET,
+ LAST_SIGNAL
+};
+
+static guint merge_signals[LAST_SIGNAL] = { 0 };
+
+static GMemChunk *merge_node_chunk = NULL;
+
+GType
+gtk_menu_merge_get_type (void)
+{
+ static GtkType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo type_info =
+ {
+ sizeof (GtkMenuMergeClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gtk_menu_merge_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+
+ sizeof (GtkMenuMerge),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gtk_menu_merge_init,
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GtkMenuMerge",
+ &type_info, 0);
+ }
+ return type;
+}
+
+static void
+gtk_menu_merge_class_init (GtkMenuMergeClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ if (!merge_node_chunk)
+ merge_node_chunk = g_mem_chunk_create (GtkMenuMergeNode, 64,
+ G_ALLOC_AND_FREE);
+
+ merge_signals[ADD_WIDGET] =
+ g_signal_new ("add_widget",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
+ G_STRUCT_OFFSET (GtkMenuMergeClass, add_widget), NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GTK_TYPE_WIDGET);
+ merge_signals[REMOVE_WIDGET] =
+ g_signal_new ("remove_widget",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
+ G_STRUCT_OFFSET (GtkMenuMergeClass, remove_widget), NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GTK_TYPE_WIDGET);
+
+ g_type_class_add_private (gobject_class, sizeof (GtkMenuMergePrivate));
+}
+
+
+static void
+gtk_menu_merge_init (GtkMenuMerge *self)
+{
+ guint merge_id;
+ GNode *node;
+
+ self->private_data = GTK_MENU_MERGE_GET_PRIVATE (self);
+
+ self->private_data->accel_group = gtk_accel_group_new ();
+
+ self->private_data->root_node = NULL;
+ self->private_data->action_groups = NULL;
+
+ self->private_data->last_merge_id = 0;
+
+
+ merge_id = gtk_menu_merge_next_merge_id (self);
+ node = get_child_node (self, NULL, "Root", 4,
+ GTK_MENU_MERGE_ROOT, TRUE, FALSE);
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node), merge_id, 0);
+ node = get_child_node (self, self->private_data->root_node, "popups", 6,
+ GTK_MENU_MERGE_POPUPS, TRUE, FALSE);
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node), merge_id, 0);
+}
+
+
+/**
+ * gtk_menu_merge_new:
+ *
+ * Creates a new menu merge object.
+ *
+ * Return value: a new menu merge object.
+ *
+ * Since: 2.4
+ **/
+GtkMenuMerge*
+gtk_menu_merge_new (void)
+{
+ return g_object_new (GTK_TYPE_MENU_MERGE, NULL);
+}
+
+
+/**
+ * gtk_menu_merge_insert_action_group:
+ * @self: a #GtkMenuMerge object
+ * @action_group: the action group to be inserted
+ * @pos: the position at which the group will be inserted
+ *
+ * Inserts an action group into the list of action groups associated with @self.
+ *
+ * Since: 2.4
+ **/
+void
+gtk_menu_merge_insert_action_group (GtkMenuMerge *self,
+ GtkActionGroup *action_group,
+ gint pos)
+{
+ g_return_if_fail (GTK_IS_MENU_MERGE (self));
+ g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+ g_return_if_fail (g_list_find (self->private_data->action_groups, action_group) == NULL);
+
+ g_object_ref (action_group);
+ self->private_data->action_groups = g_list_insert (self->private_data->action_groups, action_group, pos);
+
+ /* dirty all nodes, as action bindings may change */
+ gtk_menu_merge_dirty_all (self);
+}
+
+/**
+ * gtk_menu_merge_remove_action_group:
+ * @self: a #GtkMenuMerge object
+ * @action_group: the action group to be removed
+ *
+ * Removes an action group from the list of action groups associated with @self.
+ *
+ * Since: 2.4
+ **/
+void
+gtk_menu_merge_remove_action_group (GtkMenuMerge *self,
+ GtkActionGroup *action_group)
+{
+ g_return_if_fail (GTK_IS_MENU_MERGE (self));
+ g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+ g_return_if_fail (g_list_find (self->private_data->action_groups,
+ action_group) != NULL);
+
+ self->private_data->action_groups =
+ g_list_remove (self->private_data->action_groups, action_group);
+ g_object_unref (action_group);
+
+ /* dirty all nodes, as action bindings may change */
+ gtk_menu_merge_dirty_all (self);
+}
+
+/**
+ * gtk_menu_merge_get_action_groups:
+ * @self: a #GtkMenuMerge object
+ *
+ * Returns the list of action groups associated with @self.
+ *
+ * Return value: a #GList of action groups. The list is owned by GTK+
+ * and should not be modified.
+ *
+ * Since: 2.4
+ **/
+GList *
+gtk_menu_merge_get_action_groups (GtkMenuMerge *self)
+{
+ g_return_val_if_fail (GTK_IS_MENU_MERGE (self), NULL);
+
+ return self->private_data->action_groups;
+}
+
+/**
+ * gtk_menu_merge_get_accel_group:
+ * @self: a #GtkMenuMerge object
+ *
+ * Returns the #GtkAccelGroup associated with @self.
+ *
+ * Return value: the #GtkAccelGroup.
+ *
+ * Since: 2.4
+ **/
+GtkAccelGroup *
+gtk_menu_merge_get_accel_group (GtkMenuMerge *self)
+{
+ g_return_val_if_fail (GTK_IS_MENU_MERGE (self), NULL);
+
+ return self->private_data->accel_group;
+}
+
+/**
+ * gtk_menu_merge_get_widget:
+ * @self: a #GtkMenuMerge
+ * @path: a path
+ *
+ * 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 attribute in the XML (e.g. &lt;popups&gt;) can be addressed by their
+ * XML element name (e.g. "popups").
+ *
+ * Return value: the widget found by following the path, or %NULL if no widget
+ * was found.
+ *
+ * Since: 2.4
+ **/
+GtkWidget *
+gtk_menu_merge_get_widget (GtkMenuMerge *self,
+ const gchar *path)
+{
+ GNode *node;
+
+ /* ensure that there are no pending updates before we get the
+ * widget */
+ gtk_menu_merge_ensure_update (self);
+
+ node = gtk_menu_merge_get_node (self, path, GTK_MENU_MERGE_UNDECIDED, FALSE);
+
+ if (node == NULL)
+ return NULL;
+
+ return NODE_INFO (node)->proxy;
+}
+
+static GNode *
+get_child_node (GtkMenuMerge *self,
+ GNode *parent,
+ const gchar *childname,
+ gint childname_length,
+ GtkMenuMergeNodeType node_type,
+ gboolean create,
+ gboolean top)
+{
+ GNode *child = NULL;
+
+ g_return_val_if_fail (parent == NULL ||
+ (NODE_INFO (parent)->type != GTK_MENU_MERGE_MENUITEM &&
+ NODE_INFO (parent)->type != GTK_MENU_MERGE_TOOLITEM),
+ NULL);
+
+ if (parent)
+ {
+ if (childname)
+ {
+ for (child = parent->children; child != NULL; child = child->next)
+ {
+ if (strlen (NODE_INFO (child)->name) == childname_length &&
+ !strncmp (NODE_INFO (child)->name, childname, childname_length))
+ {
+ /* if undecided about node type, set it */
+ if (NODE_INFO (child)->type == GTK_MENU_MERGE_UNDECIDED)
+ NODE_INFO (child)->type = node_type;
+
+ /* warn about type mismatch */
+ if (NODE_INFO (child)->type != GTK_MENU_MERGE_UNDECIDED &&
+ node_type != GTK_MENU_MERGE_UNDECIDED &&
+ 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);
+
+ return child;
+ }
+ }
+ }
+ if (!child && create)
+ {
+ GtkMenuMergeNode *mnode;
+
+ mnode = g_chunk_new0 (GtkMenuMergeNode, merge_node_chunk);
+ mnode->type = node_type;
+ mnode->name = g_strndup (childname, childname_length);
+ mnode->dirty = TRUE;
+
+ if (top)
+ child = g_node_prepend_data (parent, mnode);
+ else
+ child = g_node_append_data (parent, mnode);
+ }
+ }
+ else
+ {
+ /* handle root node */
+ if (self->private_data->root_node)
+ {
+ child = self->private_data->root_node;
+ if (strncmp (NODE_INFO (child)->name, childname, childname_length) != 0)
+ g_warning ("root node name '%s' doesn't match '%s'",
+ childname, NODE_INFO (child)->name);
+ if (NODE_INFO (child)->type != GTK_MENU_MERGE_ROOT)
+ g_warning ("base element must be of type ROOT");
+ }
+ else if (create)
+ {
+ GtkMenuMergeNode *mnode;
+
+ mnode = g_chunk_new0 (GtkMenuMergeNode, merge_node_chunk);
+ mnode->type = node_type;
+ mnode->name = g_strndup (childname, childname_length);
+ mnode->dirty = TRUE;
+
+ child = self->private_data->root_node = g_node_new (mnode);
+ }
+ }
+
+ return child;
+}
+
+static GNode *
+gtk_menu_merge_get_node (GtkMenuMerge *self,
+ const gchar *path,
+ GtkMenuMergeNodeType node_type,
+ gboolean create)
+{
+ const gchar *pos, *end;
+ GNode *parent, *node;
+
+ end = path + strlen (path);
+ pos = path;
+ parent = node = NULL;
+ while (pos < end)
+ {
+ const gchar *slash;
+ gsize length;
+
+ slash = strchr (pos, '/');
+ if (slash)
+ length = slash - pos;
+ else
+ length = strlen (pos);
+
+ node = get_child_node (self, parent, pos, length, GTK_MENU_MERGE_UNDECIDED,
+ create, FALSE);
+ if (!node)
+ return NULL;
+
+ pos += length + 1; /* move past the node name and the slash too */
+ parent = node;
+ }
+
+ if (NODE_INFO (node)->type == GTK_MENU_MERGE_UNDECIDED)
+ NODE_INFO (node)->type = node_type;
+ return node;
+}
+
+static guint
+gtk_menu_merge_next_merge_id (GtkMenuMerge *self)
+{
+ self->private_data->last_merge_id++;
+
+ return self->private_data->last_merge_id;
+}
+
+static void
+gtk_menu_merge_node_prepend_ui_reference (GtkMenuMergeNode *node,
+ guint merge_id,
+ GQuark action_quark)
+{
+ NodeUIReference *reference;
+
+ reference = g_new (NodeUIReference, 1);
+ reference->action_quark = action_quark;
+ reference->merge_id = merge_id;
+
+ /* Prepend the reference */
+ node->uifiles = g_list_prepend (node->uifiles, reference);
+
+ node->dirty = TRUE;
+}
+
+static void
+gtk_menu_merge_node_remove_ui_reference (GtkMenuMergeNode *node,
+ guint merge_id)
+{
+ GList *p;
+
+ for (p = node->uifiles; p != NULL; p = p->next)
+ {
+ NodeUIReference *reference = p->data;
+
+ if (reference->merge_id == merge_id)
+ {
+ node->uifiles = g_list_remove_link (node->uifiles, p);
+ node->dirty = TRUE;
+ g_free (reference);
+
+ break;
+ }
+ }
+}
+
+/* -------------------- The UI file parser -------------------- */
+
+typedef enum
+{
+ STATE_START,
+ STATE_ROOT,
+ STATE_MENU,
+ STATE_TOOLBAR,
+ STATE_POPUPS,
+ STATE_MENUITEM,
+ STATE_TOOLITEM,
+ STATE_END
+} ParseState;
+
+typedef struct _ParseContext ParseContext;
+struct _ParseContext
+{
+ ParseState state;
+ ParseState prev_state;
+
+ GtkMenuMerge *self;
+
+ GNode *current;
+
+ guint merge_id;
+};
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseContext *ctx = user_data;
+ GtkMenuMerge *self = ctx->self;
+
+ gint i;
+ const gchar *node_name;
+ GQuark verb_quark;
+ gboolean top;
+
+ gboolean raise_error = TRUE;
+ gchar *error_attr = NULL;
+
+ /* work out a name for this node. Either the name attribute, or
+ * element name */
+ node_name = element_name;
+ verb_quark = 0;
+ top = FALSE;
+ for (i = 0; attribute_names[i] != NULL; i++)
+ {
+ if (!strcmp (attribute_names[i], "name"))
+ {
+ node_name = attribute_values[i];
+ }
+ else if (!strcmp (attribute_names[i], "verb"))
+ {
+ verb_quark = g_quark_from_string (attribute_values[i]);
+ }
+ else if (!strcmp (attribute_names[i], "pos"))
+ {
+ top = !strcmp (attribute_values[i], "top");
+ }
+ }
+ /* if no verb, then set it to the node's name */
+ if (verb_quark == 0)
+ verb_quark = g_quark_from_string (node_name);
+
+ switch (element_name[0])
+ {
+ case 'R':
+ if (ctx->state == STATE_START && !strcmp (element_name, "Root"))
+ {
+ ctx->state = STATE_ROOT;
+ ctx->current = self->private_data->root_node;
+ raise_error = FALSE;
+
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+ ctx->merge_id, verb_quark);
+ }
+ break;
+ case 'm':
+ if (ctx->state == STATE_ROOT && !strcmp (element_name, "menu"))
+ {
+ ctx->state = STATE_MENU;
+ ctx->current = get_child_node (self, ctx->current,
+ node_name, strlen (node_name),
+ GTK_MENU_MERGE_MENUBAR,
+ TRUE, FALSE);
+ if (NODE_INFO (ctx->current)->action_name == 0)
+ NODE_INFO (ctx->current)->action_name = verb_quark;
+
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+ ctx->merge_id, verb_quark);
+ NODE_INFO (ctx->current)->dirty = TRUE;
+
+ raise_error = FALSE;
+ }
+ else if (ctx->state == STATE_MENU && !strcmp (element_name, "menuitem"))
+ {
+ GNode *node;
+
+ ctx->state = STATE_MENUITEM;
+ node = get_child_node (self, ctx->current,
+ node_name, strlen (node_name),
+ GTK_MENU_MERGE_MENUITEM,
+ TRUE, top);
+ if (NODE_INFO (node)->action_name == 0)
+ NODE_INFO (node)->action_name = verb_quark;
+
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node),
+ ctx->merge_id, verb_quark);
+ NODE_INFO (node)->dirty = TRUE;
+
+ raise_error = FALSE;
+ }
+ break;
+ case 'd':
+ if (ctx->state == STATE_ROOT && !strcmp (element_name, "dockitem"))
+ {
+ ctx->state = STATE_TOOLBAR;
+ ctx->current = get_child_node (self, ctx->current,
+ node_name, strlen (node_name),
+ GTK_MENU_MERGE_TOOLBAR,
+ TRUE, FALSE);
+ if (NODE_INFO (ctx->current)->action_name == 0)
+ NODE_INFO (ctx->current)->action_name = verb_quark;
+
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+ ctx->merge_id, verb_quark);
+ NODE_INFO (ctx->current)->dirty = TRUE;
+
+ raise_error = FALSE;
+ }
+ break;
+ case 'p':
+ if (ctx->state == STATE_ROOT && !strcmp (element_name, "popups"))
+ {
+ ctx->state = STATE_POPUPS;
+ ctx->current = get_child_node (self, ctx->current,
+ node_name, strlen (node_name),
+ GTK_MENU_MERGE_POPUPS,
+ TRUE, FALSE);
+
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+ ctx->merge_id, verb_quark);
+ NODE_INFO (ctx->current)->dirty = TRUE;
+
+ raise_error = FALSE;
+ }
+ else if (ctx->state == STATE_POPUPS && !strcmp (element_name, "popup"))
+ {
+ ctx->state = STATE_MENU;
+ ctx->current = get_child_node (self, ctx->current,
+ node_name, strlen (node_name),
+ GTK_MENU_MERGE_MENU,
+ TRUE, FALSE);
+ if (NODE_INFO (ctx->current)->action_name == 0)
+ NODE_INFO (ctx->current)->action_name = verb_quark;
+
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+ ctx->merge_id, verb_quark);
+ NODE_INFO (ctx->current)->dirty = TRUE;
+
+ raise_error = FALSE;
+ }
+ else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
+ !strcmp (element_name, "placeholder"))
+ {
+ if (ctx->state == STATE_MENU)
+ ctx->current = get_child_node (self, ctx->current,
+ node_name, strlen (node_name),
+ GTK_MENU_MERGE_MENU_PLACEHOLDER,
+ TRUE, top);
+ else
+ ctx->current = get_child_node (self, ctx->current,
+ node_name, strlen (node_name),
+ GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER,
+ TRUE, top);
+
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+ ctx->merge_id, verb_quark);
+ NODE_INFO (ctx->current)->dirty = TRUE;
+
+ raise_error = FALSE;
+ }
+ break;
+ case 's':
+ if (ctx->state == STATE_MENU && !strcmp (element_name, "submenu"))
+ {
+ ctx->state = STATE_MENU;
+ ctx->current = get_child_node (self, ctx->current,
+ node_name, strlen (node_name),
+ GTK_MENU_MERGE_MENU,
+ TRUE, top);
+ if (NODE_INFO (ctx->current)->action_name == 0)
+ NODE_INFO (ctx->current)->action_name = verb_quark;
+
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+ ctx->merge_id, verb_quark);
+ NODE_INFO (ctx->current)->dirty = TRUE;
+
+ raise_error = FALSE;
+ }
+ else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
+ !strcmp (element_name, "separator"))
+ {
+ GNode *node;
+
+ if (ctx->state == STATE_MENU)
+ ctx->state = STATE_MENUITEM;
+ else
+ ctx->state = STATE_TOOLITEM;
+ node = get_child_node (self, ctx->current,
+ node_name, strlen (node_name),
+ GTK_MENU_MERGE_SEPARATOR,
+ TRUE, top);
+ if (NODE_INFO (node)->action_name == 0)
+ NODE_INFO (node)->action_name = verb_quark;
+
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node),
+ ctx->merge_id, verb_quark);
+ NODE_INFO (node)->dirty = TRUE;
+
+ raise_error = FALSE;
+ }
+ break;
+ case 't':
+ if (ctx->state == STATE_TOOLBAR && !strcmp (element_name, "toolitem"))
+ {
+ GNode *node;
+
+ ctx->state = STATE_TOOLITEM;
+ node = get_child_node (self, ctx->current,
+ node_name, strlen (node_name),
+ GTK_MENU_MERGE_TOOLITEM,
+ TRUE, top);
+ if (NODE_INFO (node)->action_name == 0)
+ NODE_INFO (node)->action_name = verb_quark;
+
+ gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node),
+ ctx->merge_id, verb_quark);
+ NODE_INFO (node)->dirty = TRUE;
+
+ raise_error = FALSE;
+ }
+ break;
+ default:
+ break;
+ }
+ if (raise_error)
+ {
+ gint line_number, char_number;
+
+ g_markup_parse_context_get_position (context,
+ &line_number, &char_number);
+ if (error_attr)
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute '%s' on line %d char %d"),
+ error_attr,
+ line_number, char_number);
+ else
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ _("Unknown tag '%s' on line %d char %d"),
+ element_name,
+ line_number, char_number);
+ }
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseContext *ctx = user_data;
+ GtkMenuMerge *self = ctx->self;
+
+ switch (ctx->state)
+ {
+ case STATE_START:
+ g_warning ("shouldn't get any end tags in start state");
+ /* should we GError here? */
+ break;
+ case STATE_ROOT:
+ if (ctx->current != self->private_data->root_node)
+ g_warning ("we are in STATE_ROOT, but the current node isn't the root");
+ ctx->current = NULL;
+ ctx->state = STATE_END;
+ break;
+ case STATE_MENU:
+ ctx->current = ctx->current->parent;
+ if (NODE_INFO (ctx->current)->type == GTK_MENU_MERGE_ROOT) /* menubar */
+ ctx->state = STATE_ROOT;
+ else if (NODE_INFO (ctx->current)->type == GTK_MENU_MERGE_POPUPS) /* popup */
+ ctx->state = STATE_POPUPS;
+ /* else, stay in STATE_MENU state */
+ break;
+ case STATE_TOOLBAR:
+ ctx->current = ctx->current->parent;
+ /* we conditionalise this test, in case we are closing off a
+ * placeholder */
+ if (NODE_INFO (ctx->current)->type == GTK_MENU_MERGE_ROOT)
+ ctx->state = STATE_ROOT;
+ /* else, stay in STATE_TOOLBAR state */
+ break;
+ case STATE_POPUPS:
+ ctx->current = ctx->current->parent;
+ ctx->state = STATE_ROOT;
+ break;
+ case STATE_MENUITEM:
+ ctx->state = STATE_MENU;
+ break;
+ case STATE_TOOLITEM:
+ ctx->state = STATE_TOOLBAR;
+ break;
+ case STATE_END:
+ g_warning ("shouldn't get any end tags at this point");
+ /* should do an error here */
+ break;
+ }
+}
+
+static void
+cleanup (GMarkupParseContext *context,
+ GError *error,
+ gpointer user_data)
+{
+ ParseContext *ctx = user_data;
+ GtkMenuMerge *self = ctx->self;
+
+ ctx->current = NULL;
+ /* should also walk through the tree and get rid of nodes related to
+ * this UI file's tag */
+
+ gtk_menu_merge_remove_ui (self, ctx->merge_id);
+}
+
+static GMarkupParser ui_parser = {
+ start_element_handler,
+ end_element_handler,
+ NULL,
+ NULL,
+ cleanup
+};
+
+
+/**
+ * gtk_menu_merge_add_ui_from_string:
+ * @self: a #GtkMenuMerge object
+ * @buffer: the string to parse
+ * @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
+ * @error: return location for an error
+ *
+ * Parses a string containing a UI description and merge it with the current
+ * contents of @self. FIXME: describe the XML format.
+ *
+ * Return value: The merge id for the merged UI. The merge id can be used
+ * to unmerge the UI with gtk_menu_merge_remove_ui(). If an error occurred,
+ * the return value is 0.
+ *
+ * Since: 2.4
+ **/
+guint
+gtk_menu_merge_add_ui_from_string (GtkMenuMerge *self,
+ const gchar *buffer,
+ gsize length,
+ GError **error)
+{
+ ParseContext ctx = { 0 };
+ GMarkupParseContext *context;
+ gboolean res = TRUE;
+
+ g_return_val_if_fail (GTK_IS_MENU_MERGE (self), FALSE);
+ g_return_val_if_fail (buffer != NULL, FALSE);
+
+ ctx.state = STATE_START;
+ ctx.self = self;
+ ctx.current = NULL;
+ ctx.merge_id = gtk_menu_merge_next_merge_id (self);
+
+ context = g_markup_parse_context_new (&ui_parser, 0, &ctx, NULL);
+ if (length < 0)
+ length = strlen (buffer);
+
+ if (g_markup_parse_context_parse (context, buffer, length, error))
+ {
+ if (!g_markup_parse_context_end_parse (context, error))
+ res = FALSE;
+ }
+ else
+ res = FALSE;
+
+ g_markup_parse_context_free (context);
+
+ gtk_menu_merge_queue_update (self);
+
+ if (res)
+ return ctx.merge_id;
+
+ return 0;
+}
+
+/**
+ * gtk_menu_merge_add_ui_from_file:
+ * @self: a #GtkMenuMerge object
+ * @filename: the name of the file to parse
+ * @error: return location for an error
+ *
+ * Parses a file containing a UI description and merge it with the current
+ * contents of @self. See gtk_menu_merge_add_ui_from_file().
+ *
+ * Return value: The merge id for the merged UI. The merge id can be used
+ * to unmerge the UI with gtk_menu_merge_remove_ui(). If an error occurred,
+ * the return value is 0.
+ *
+ * Since: 2.4
+ **/
+guint
+gtk_menu_merge_add_ui_from_file (GtkMenuMerge *self,
+ const gchar *filename,
+ GError **error)
+{
+ gchar *buffer;
+ gint length;
+ guint res;
+
+ if (!g_file_get_contents (filename, &buffer, &length, error))
+ return 0;
+
+ res = gtk_menu_merge_add_ui_from_string (self, buffer, length, error);
+ g_free (buffer);
+
+ return res;
+}
+
+static gboolean
+remove_ui (GNode *node,
+ gpointer user_data)
+{
+ guint merge_id = GPOINTER_TO_UINT (user_data);
+
+ gtk_menu_merge_node_remove_ui_reference (NODE_INFO (node), merge_id);
+
+ return FALSE; /* continue */
+}
+
+/**
+ * gtk_menu_merge_remove_ui:
+ * @self: a #GtkMenuMerge object
+ * @merge_id: a merge id as returned by gtk_menu_merge_add_ui_from_string()
+ *
+ * Unmerges the part of @self<!-- -->s content identified by @merge_id.
+ **/
+void
+gtk_menu_merge_remove_ui (GtkMenuMerge *self,
+ guint merge_id)
+{
+ g_node_traverse (self->private_data->root_node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
+ remove_ui, GUINT_TO_POINTER (merge_id));
+
+ gtk_menu_merge_queue_update (self);
+}
+
+/* -------------------- Updates -------------------- */
+
+
+static GtkAction *
+get_action_by_name (GtkMenuMerge *merge,
+ const char *action_name)
+{
+ GList *tmp;
+
+ if (!action_name)
+ return NULL;
+
+ /* lookup name */
+ for (tmp = merge->private_data->action_groups; tmp != NULL; tmp = tmp->next)
+ {
+ GtkActionGroup *action_group = tmp->data;
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (action_group, action_name);
+
+ if (action)
+ return action;
+ }
+
+ return NULL;
+}
+
+static gboolean
+find_menu_position (GNode *node,
+ GtkWidget **menushell_p,
+ gint *pos_p)
+{
+ GtkWidget *menushell;
+ gint pos;
+
+ g_return_val_if_fail (node != NULL, FALSE);
+ g_return_val_if_fail (NODE_INFO (node)->type == GTK_MENU_MERGE_MENU ||
+ NODE_INFO (node)->type == GTK_MENU_MERGE_MENU_PLACEHOLDER ||
+ NODE_INFO (node)->type == GTK_MENU_MERGE_MENUITEM ||
+ NODE_INFO (node)->type == GTK_MENU_MERGE_SEPARATOR,
+ FALSE);
+
+ /* first sibling -- look at parent */
+ if (node->prev == NULL)
+ {
+ GNode *parent;
+
+ parent = node->parent;
+ switch (NODE_INFO (parent)->type)
+ {
+ case GTK_MENU_MERGE_MENUBAR:
+ menushell = NODE_INFO (parent)->proxy;
+ pos = 0;
+ break;
+ case GTK_MENU_MERGE_MENU:
+ menushell = NODE_INFO (parent)->proxy;
+ if (GTK_IS_MENU_ITEM (menushell))
+ menushell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menushell));
+ pos = 0;
+ break;
+ case GTK_MENU_MERGE_MENU_PLACEHOLDER:
+ menushell = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
+ g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
+ pos = g_list_index (GTK_MENU_SHELL (menushell)->children,
+ NODE_INFO (parent)->proxy) + 1;
+ break;
+ default:
+ g_warning("%s: bad parent node type %d", G_STRLOC,
+ NODE_INFO (parent)->type);
+ return FALSE;
+ }
+ }
+ else
+ {
+ GtkWidget *prev_child;
+ GNode *sibling;
+
+ sibling = node->prev;
+ if (NODE_INFO (sibling)->type == GTK_MENU_MERGE_MENU_PLACEHOLDER)
+ prev_child = NODE_INFO (sibling)->extra; /* second Separator */
+ else
+ prev_child = NODE_INFO (sibling)->proxy;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
+ menushell = gtk_widget_get_parent (prev_child);
+ g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
+
+ pos = g_list_index (GTK_MENU_SHELL (menushell)->children, prev_child) + 1;
+ }
+
+ if (menushell_p)
+ *menushell_p = menushell;
+ if (pos_p)
+ *pos_p = pos;
+
+ return TRUE;
+}
+
+static gboolean
+find_toolbar_position (GNode *node,
+ GtkWidget **toolbar_p,
+ gint *pos_p)
+{
+ GtkWidget *toolbar;
+ gint pos;
+
+ g_return_val_if_fail (node != NULL, FALSE);
+ g_return_val_if_fail (NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLBAR ||
+ NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER ||
+ NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLITEM ||
+ NODE_INFO (node)->type == GTK_MENU_MERGE_SEPARATOR,
+ FALSE);
+
+ /* first sibling -- look at parent */
+ if (node->prev == NULL)
+ {
+ GNode *parent;
+
+ parent = node->parent;
+ switch (NODE_INFO (parent)->type)
+ {
+ case GTK_MENU_MERGE_TOOLBAR:
+ toolbar = NODE_INFO (parent)->proxy;
+ pos = 0;
+ break;
+ case GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER:
+ toolbar = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
+ g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
+ pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
+ GTK_TOOL_ITEM (NODE_INFO (parent)->proxy)) + 1;
+ break;
+ default:
+ g_warning ("%s: bad parent node type %d", G_STRLOC,
+ NODE_INFO (parent)->type);
+ return FALSE;
+ }
+ }
+ else
+ {
+ GtkWidget *prev_child;
+ GNode *sibling;
+
+ sibling = node->prev;
+ if (NODE_INFO (sibling)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER)
+ prev_child = NODE_INFO (sibling)->extra; /* second Separator */
+ else
+ prev_child = NODE_INFO (sibling)->proxy;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
+ toolbar = gtk_widget_get_parent (prev_child);
+ g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
+
+ pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
+ GTK_TOOL_ITEM (prev_child)) + 1;
+ }
+
+ if (toolbar_p)
+ *toolbar_p = toolbar;
+ if (pos_p)
+ *pos_p = pos;
+
+ return TRUE;
+}
+
+static void
+update_node (GtkMenuMerge *self,
+ GNode *node)
+{
+ GtkMenuMergeNode *info;
+ GNode *child;
+ GtkAction *action;
+#ifdef DEBUG_MENU_MERGE
+ GList *tmp;
+#endif
+
+ g_return_if_fail (node != NULL);
+ g_return_if_fail (NODE_INFO (node) != NULL);
+
+ info = NODE_INFO (node);
+
+#ifdef DEBUG_MENU_MERGE
+ g_print ("update_node name=%s dirty=%d (", info->name, info->dirty);
+ for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next)
+ {
+ NodeUIReference *ref = tmp->data;
+ g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id);
+ if (tmp->next)
+ g_print (", ");
+ }
+ g_print (")\n");
+#endif
+
+ if (NODE_INFO (node)->dirty)
+ {
+ const gchar *action_name;
+ NodeUIReference *ref;
+
+ if (info->uifiles == NULL) {
+ /* We may need to remove this node.
+ * This must be done in post order
+ */
+ goto recurse_children;
+ }
+
+ ref = info->uifiles->data;
+ action_name = g_quark_to_string (ref->action_quark);
+ action = get_action_by_name (self, action_name);
+
+ NODE_INFO (node)->dirty = FALSE;
+
+ /* Check if the node doesn't have an action and must have an action */
+ if (action == NULL &&
+ info->type != GTK_MENU_MERGE_MENUBAR &&
+ info->type != GTK_MENU_MERGE_TOOLBAR &&
+ info->type != GTK_MENU_MERGE_SEPARATOR &&
+ info->type != GTK_MENU_MERGE_MENU_PLACEHOLDER &&
+ info->type != GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER)
+ {
+ /* FIXME: Should we warn here? */
+ goto recurse_children;
+ }
+
+ /* If the widget already has a proxy and the action hasn't changed, then
+ * we don't have to do anything.
+ */
+ if (info->proxy != NULL &&
+ action == info->action)
+ {
+ goto recurse_children;
+ }
+
+ if (info->action)
+ g_object_unref (info->action);
+ info->action = action;
+ if (info->action)
+ g_object_ref (info->action);
+
+ switch (info->type)
+ {
+ case GTK_MENU_MERGE_MENUBAR:
+ if (info->proxy == NULL)
+ {
+ info->proxy = gtk_menu_bar_new ();
+ gtk_widget_show (info->proxy);
+ g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
+ }
+ break;
+ case GTK_MENU_MERGE_MENU:
+ if (NODE_INFO (node->parent)->type == GTK_MENU_MERGE_POPUPS)
+ {
+ if (info->proxy == NULL)
+ {
+ GtkWidget *menu;
+ menu = gtk_menu_new ();
+ gtk_menu_set_accel_group (GTK_MENU (menu), self->private_data->accel_group);
+ info->proxy = menu;
+ }
+ }
+ else
+ {
+ GtkWidget *prev_submenu = NULL;
+ /* remove the proxy if it is of the wrong type ... */
+ if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
+ GTK_ACTION_GET_CLASS (info->action)->menu_item_type)
+ {
+ prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
+ if (prev_submenu)
+ {
+ g_object_ref (prev_submenu);
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),NULL);
+ }
+ gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+ info->proxy);
+ info->proxy = NULL;
+ }
+ /* create proxy if needed ... */
+ if (info->proxy == NULL)
+ {
+ GtkWidget *menushell;
+ gint pos;
+
+ if (find_menu_position (node, &menushell, &pos))
+ {
+ GtkWidget *menu;
+ info->proxy = gtk_action_create_menu_item (info->action);
+ menu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu);
+ gtk_menu_set_accel_group (GTK_MENU (menu), self->private_data->accel_group);
+ gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos);
+ }
+ }
+ else
+ {
+ gtk_action_connect_proxy (info->action, info->proxy);
+ }
+ if (prev_submenu)
+ {
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),
+ prev_submenu);
+ g_object_unref (prev_submenu);
+ }
+ }
+ break;
+ case GTK_MENU_MERGE_UNDECIDED:
+ g_warning ("found 'undecided node!");
+ break;
+ case GTK_MENU_MERGE_ROOT:
+ break;
+ case GTK_MENU_MERGE_TOOLBAR:
+ if (info->proxy == NULL)
+ {
+ info->proxy = gtk_toolbar_new ();
+ gtk_widget_show (info->proxy);
+ g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
+ }
+ break;
+ case GTK_MENU_MERGE_MENU_PLACEHOLDER:
+ /* create menu items for placeholders if necessary ... */
+ if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) ||
+ !GTK_IS_SEPARATOR_MENU_ITEM (info->extra))
+ {
+ if (info->proxy)
+ gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+ info->proxy);
+ if (info->extra)
+ gtk_container_remove (GTK_CONTAINER (info->extra->parent),
+ info->extra);
+ info->proxy = NULL;
+ info->extra = NULL;
+ }
+ if (info->proxy == NULL)
+ {
+ GtkWidget *menushell;
+ gint pos;
+
+ if (find_menu_position (node, &menushell, &pos))
+ {
+ NODE_INFO (node)->proxy = gtk_separator_menu_item_new ();
+ gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
+ NODE_INFO (node)->proxy, pos);
+
+ NODE_INFO (node)->extra = gtk_separator_menu_item_new ();
+ gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
+ NODE_INFO (node)->extra, pos+1);
+ }
+ }
+ break;
+ case GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER:
+ /* create toolbar items for placeholders if necessary ... */
+ if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) ||
+ !GTK_IS_SEPARATOR_TOOL_ITEM (info->extra))
+ {
+ if (info->proxy)
+ gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+ info->proxy);
+ if (info->extra)
+ gtk_container_remove (GTK_CONTAINER (info->extra->parent),
+ info->extra);
+ info->proxy = NULL;
+ info->extra = NULL;
+ }
+ if (info->proxy == NULL)
+ {
+ GtkWidget *toolbar;
+ gint pos;
+
+ if (find_toolbar_position (node, &toolbar, &pos))
+ {
+ GtkToolItem *item;
+
+ item = gtk_separator_tool_item_new ();
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
+ NODE_INFO(node)->proxy = GTK_WIDGET (item);
+
+ item = gtk_separator_tool_item_new ();
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1);
+ NODE_INFO (node)->extra = GTK_WIDGET (item);
+ }
+ }
+ break;
+ case GTK_MENU_MERGE_POPUPS:
+ break;
+ case GTK_MENU_MERGE_MENUITEM:
+ /* remove the proxy if it is of the wrong type ... */
+ if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
+ GTK_ACTION_GET_CLASS (info->action)->menu_item_type)
+ {
+ gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+ info->proxy);
+ info->proxy = NULL;
+ }
+ /* create proxy if needed ... */
+ if (info->proxy == NULL)
+ {
+ GtkWidget *menushell;
+ gint pos;
+
+ if (find_menu_position (node, &menushell, &pos))
+ {
+ info->proxy = gtk_action_create_menu_item (info->action);
+
+ gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
+ info->proxy, pos);
+ }
+ }
+ else
+ {
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
+ gtk_action_connect_proxy (info->action, info->proxy);
+ }
+ break;
+ case GTK_MENU_MERGE_TOOLITEM:
+ /* remove the proxy if it is of the wrong type ... */
+ if (info->proxy && G_OBJECT_TYPE (info->proxy) !=
+ GTK_ACTION_GET_CLASS (info->action)->toolbar_item_type)
+ {
+ gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+ info->proxy);
+ info->proxy = NULL;
+ }
+ /* create proxy if needed ... */
+ if (info->proxy == NULL)
+ {
+ GtkWidget *toolbar;
+ gint pos;
+
+ if (find_toolbar_position (node, &toolbar, &pos))
+ {
+ info->proxy = gtk_action_create_tool_item (info->action);
+
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
+ GTK_TOOL_ITEM (info->proxy), pos);
+ }
+ }
+ else
+ {
+ gtk_action_connect_proxy (info->action, info->proxy);
+ }
+ break;
+ case GTK_MENU_MERGE_SEPARATOR:
+ if (NODE_INFO (node->parent)->type == GTK_MENU_MERGE_TOOLBAR ||
+ NODE_INFO (node->parent)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER)
+ {
+ GtkWidget *toolbar;
+ gint pos;
+
+ if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy))
+ {
+ gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+ info->proxy);
+ info->proxy = NULL;
+ }
+
+ if (find_toolbar_position (node, &toolbar, &pos))
+ {
+ GtkToolItem *item = gtk_separator_tool_item_new ();
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
+ info->proxy = GTK_WIDGET (item);
+ gtk_widget_show (info->proxy);
+ }
+ }
+ else
+ {
+ GtkWidget *menushell;
+ gint pos;
+
+ if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy))
+ {
+ gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+ info->proxy);
+ info->proxy = NULL;
+ }
+
+ if (find_menu_position (node, &menushell, &pos))
+ {
+ info->proxy = gtk_separator_menu_item_new ();
+ gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
+ info->proxy, pos);
+ gtk_widget_show (info->proxy);
+ }
+ }
+ break;
+ }
+
+ /* if this node has a widget, but it is the wrong type, remove it */
+ }
+
+ recurse_children:
+ /* process children */
+ child = node->children;
+ while (child)
+ {
+ GNode *current;
+
+ current = child;
+ child = current->next;
+ update_node (self, current);
+ }
+
+ /* handle cleanup of dead nodes */
+ if (node->children == NULL && NODE_INFO (node)->uifiles == NULL)
+ {
+ if (NODE_INFO (node)->proxy)
+ gtk_widget_destroy (NODE_INFO (node)->proxy);
+ if ((NODE_INFO (node)->type == GTK_MENU_MERGE_MENU_PLACEHOLDER ||
+ NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER) &&
+ NODE_INFO (node)->extra)
+ gtk_widget_destroy (NODE_INFO (node)->extra);
+ g_chunk_free (NODE_INFO (node), merge_node_chunk);
+ g_node_destroy (node);
+ }
+}
+
+static gboolean
+do_updates (GtkMenuMerge *self)
+{
+ /* this function needs to check through the tree for dirty nodes.
+ * For such nodes, it needs to do the following:
+ *
+ * 1) check if they are referenced by any loaded UI files anymore.
+ * In which case, the proxy widget should be destroyed, unless
+ * there are any subnodes.
+ *
+ * 2) lookup the action for this node again. If it is different to
+ * the current one (or if no previous action has been looked up),
+ * the proxy is reconnected to the new action (or a new proxy widget
+ * is created and added to the parent container).
+ */
+
+ update_node (self, self->private_data->root_node);
+
+ self->private_data->update_tag = 0;
+
+ return FALSE;
+}
+
+static void
+gtk_menu_merge_queue_update (GtkMenuMerge *self)
+{
+ if (self->private_data->update_tag != 0)
+ return;
+
+ self->private_data->update_tag = g_idle_add ((GSourceFunc)do_updates, self);
+}
+
+static void
+gtk_menu_merge_ensure_update (GtkMenuMerge *self)
+{
+ if (self->private_data->update_tag != 0)
+ {
+ g_source_remove (self->private_data->update_tag);
+ do_updates (self);
+ }
+}
+
+static gboolean
+dirty_traverse_func (GNode *node,
+ gpointer data)
+{
+ NODE_INFO (node)->dirty = TRUE;
+ return FALSE;
+}
+
+static void
+gtk_menu_merge_dirty_all (GtkMenuMerge *self)
+{
+ g_node_traverse (self->private_data->root_node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+ dirty_traverse_func, NULL);
+ gtk_menu_merge_queue_update (self);
+}
+
+static const gchar *open_tag_format[] = {
+ "%*s<UNDECIDED>\n",
+ "%*s<Root>\n",
+ "%*s<menu name=\"%s\">\n",
+ "%*s<submenu name=\"%s\" verb=\"%s\">\n",
+ "%*s<dockitem name=\"%s\">\n",
+ "%*s<placeholder name=\"%s\">\n",
+ "%*s<placeholder name=\"%s\">\n",
+ "%*s<popups>\n",
+ "%*s<menuitem name=\"%s\" verb=\"%s\"/>\n",
+ "%*s<toolitem name=\"%s\" verb=\"%s\"/>\n",
+ "%*s<separator/>\n",
+ "%*s<popup name=\"%s\">\n"
+};
+
+static const gchar *close_tag_format[] = {
+ "%*s</UNDECIDED>\n",
+ "%*s</Root>\n",
+ "%*s</menu>\n",
+ "%*s</submenu>\n",
+ "%*s</dockitem>\n",
+ "%*s</placeholder>\n",
+ "%*s</placeholder>\n",
+ "%*s</popups>\n",
+ "",
+ "",
+ "",
+ "%*s</popup>\n"
+};
+
+static void
+print_node (GtkMenuMerge *self,
+ GNode *node,
+ gint indent_level,
+ GString *buffer)
+{
+ GtkMenuMergeNode *mnode;
+ GNode *child;
+ guint type;
+
+ mnode = node->data;
+ if (mnode->type == GTK_MENU_MERGE_MENU &&
+ NODE_INFO (node->parent)->type == GTK_MENU_MERGE_POPUPS)
+ type = GTK_MENU_MERGE_SEPARATOR + 1;
+ else
+ type = mnode->type;
+
+ g_string_append_printf (buffer, open_tag_format[type],
+ indent_level, "",
+ mnode->name,
+ g_quark_to_string (mnode->action_name));
+
+ for (child = node->children; child != NULL; child = child->next)
+ print_node (self, child, indent_level + 2, buffer);
+
+ g_string_append_printf (buffer, close_tag_format[type],
+ indent_level, "");
+
+}
+
+/**
+ * gtk_menu_merge_get_ui:
+ * @self: a #GtkMenuMerge
+ *
+ * Creates an XML representation of the merged ui.
+ *
+ * Return value: A newly allocated string containing an XML representation of
+ * the merged ui.
+ **/
+gchar*
+gtk_menu_merge_get_ui (GtkMenuMerge *self)
+{
+ GString *buffer;
+
+ buffer = g_string_new (NULL);
+
+ gtk_menu_merge_ensure_update (self);
+
+ print_node (self, self->private_data->root_node, 0, buffer);
+
+ return g_string_free (buffer, FALSE);
+}
diff --git a/gtk/gtkmenumerge.h b/gtk/gtkmenumerge.h
new file mode 100644
index 0000000000..e4db4dc7d9
--- /dev/null
+++ b/gtk/gtkmenumerge.h
@@ -0,0 +1,109 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+#ifndef __GTK_MENU_MERGE_H__
+#define __GTK_MENU_MERGE_H__
+
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtkaccelgroup.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkaction.h>
+#include <gtk/gtkactiongroup.h>
+
+#define GTK_TYPE_MENU_MERGE (gtk_menu_merge_get_type ())
+#define GTK_MENU_MERGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU_MERGE, GtkMenuMerge))
+#define GTK_MENU_MERGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MENU_MERGE, GtkMenuMergeClass))
+#define GTK_IS_MENU_MERGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_MENU_MERGE))
+#define GTK_IS_MENU_MERGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GTK_TYPE_MENU_MERGE))
+#define GTK_MENU_MERGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_MENU_MERGE, GtkMenuMergeClass))
+
+typedef struct _GtkMenuMerge GtkMenuMerge;
+typedef struct _GtkMenuMergeClass GtkMenuMergeClass;
+typedef struct _GtkMenuMergePrivate GtkMenuMergePrivate;
+
+
+struct _GtkMenuMerge {
+ GObject parent;
+
+ /*< private >*/
+
+ GtkMenuMergePrivate *private_data;
+};
+
+struct _GtkMenuMergeClass {
+ GObjectClass parent_class;
+
+ void (* add_widget) (GtkMenuMerge *merge,
+ GtkWidget *widget);
+ void (* remove_widget) (GtkMenuMerge *merge,
+ GtkWidget *widget);
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType gtk_menu_merge_get_type (void);
+GtkMenuMerge *gtk_menu_merge_new (void);
+
+/* these two functions will dirty all merge nodes, as they may need to
+ * be connected up to different actions */
+void gtk_menu_merge_insert_action_group (GtkMenuMerge *self,
+ GtkActionGroup *action_group,
+ gint pos);
+void gtk_menu_merge_remove_action_group (GtkMenuMerge *self,
+ GtkActionGroup *action_group);
+GList *gtk_menu_merge_get_action_groups (GtkMenuMerge *self);
+GtkAccelGroup *gtk_menu_merge_get_accel_group (GtkMenuMerge *self);
+
+
+
+GtkWidget *gtk_menu_merge_get_widget (GtkMenuMerge *self,
+ const gchar *path);
+
+/* these two functions are for adding UI elements to the merged user
+ * interface */
+guint gtk_menu_merge_add_ui_from_string (GtkMenuMerge *self,
+ const gchar *buffer,
+ gsize length,
+ GError **error);
+guint gtk_menu_merge_add_ui_from_file (GtkMenuMerge *self,
+ const gchar *filename,
+ GError **error);
+void gtk_menu_merge_remove_ui (GtkMenuMerge *self,
+ guint merge_id);
+
+gchar *gtk_menu_merge_get_ui (GtkMenuMerge *self);
+
+
+#endif /* __GTK_MENU_MERGE_H__ */
diff --git a/gtk/gtkradioaction.c b/gtk/gtkradioaction.c
new file mode 100644
index 0000000000..e0ea5f1f9b
--- /dev/null
+++ b/gtk/gtkradioaction.c
@@ -0,0 +1,240 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+
+#include "gtkradioaction.h"
+#include "gtktoggleactionprivate.h"
+
+#define GTK_RADIO_ACTION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_RADIO_ACTION, GtkRadioActionPrivate))
+
+struct _GtkRadioActionPrivate
+{
+ GSList *group;
+};
+
+static void gtk_radio_action_init (GtkRadioAction *action);
+static void gtk_radio_action_class_init (GtkRadioActionClass *class);
+
+GType
+gtk_radio_action_get_type (void)
+{
+ static GtkType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo type_info =
+ {
+ sizeof (GtkRadioActionClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gtk_radio_action_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+
+ sizeof (GtkRadioAction),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gtk_radio_action_init,
+ };
+
+ type = g_type_register_static (GTK_TYPE_TOGGLE_ACTION,
+ "GtkRadioAction",
+ &type_info, 0);
+ }
+ return type;
+}
+
+static void gtk_radio_action_finalize (GObject *object);
+static void gtk_radio_action_activate (GtkAction *action);
+
+static GObjectClass *parent_class = NULL;
+
+static void
+gtk_radio_action_class_init (GtkRadioActionClass *klass)
+{
+ GObjectClass *gobject_class;
+ GtkActionClass *action_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+ gobject_class = G_OBJECT_CLASS (klass);
+ action_class = GTK_ACTION_CLASS (klass);
+
+ gobject_class->finalize = gtk_radio_action_finalize;
+
+ action_class->activate = gtk_radio_action_activate;
+
+ g_type_class_add_private (gobject_class, sizeof (GtkRadioActionPrivate));
+}
+
+static void
+gtk_radio_action_init (GtkRadioAction *action)
+{
+ action->private_data = GTK_RADIO_ACTION_GET_PRIVATE (action);
+ action->private_data->group = g_slist_prepend (NULL, action);
+}
+
+static void
+gtk_radio_action_finalize (GObject *object)
+{
+ GtkRadioAction *action;
+ GSList *tmp_list;
+
+ action = GTK_RADIO_ACTION (object);
+
+ action->private_data->group = g_slist_remove (action->private_data->group, action);
+
+ tmp_list = action->private_data->group;
+
+ while (tmp_list)
+ {
+ GtkRadioAction *tmp_action = tmp_list->data;
+
+ tmp_list = tmp_list->next;
+ tmp_action->private_data->group = action->private_data->group;
+ }
+
+ if (parent_class->finalize)
+ (* parent_class->finalize) (object);
+}
+
+static void
+gtk_radio_action_activate (GtkAction *action)
+{
+ GtkRadioAction *radio_action;
+ GtkToggleAction *toggle_action;
+ GtkToggleAction *tmp_action;
+ GSList *tmp_list;
+
+ radio_action = GTK_RADIO_ACTION (action);
+ toggle_action = GTK_TOGGLE_ACTION (action);
+
+ if (toggle_action->private_data->active)
+ {
+ tmp_list = radio_action->private_data->group;
+
+ while (tmp_list)
+ {
+ tmp_action = tmp_list->data;
+ tmp_list = tmp_list->next;
+
+ if (tmp_action->private_data->active && (tmp_action != toggle_action))
+ {
+ toggle_action->private_data->active = !toggle_action->private_data->active;
+ break;
+ }
+ }
+ }
+ else
+ {
+ toggle_action->private_data->active = !toggle_action->private_data->active;
+
+ tmp_list = radio_action->private_data->group;
+ while (tmp_list)
+ {
+ tmp_action = tmp_list->data;
+ tmp_list = tmp_list->next;
+
+ if (tmp_action->private_data->active && (tmp_action != toggle_action))
+ {
+ gtk_action_activate (GTK_ACTION (tmp_action));
+ break;
+ }
+ }
+ }
+
+ gtk_toggle_action_toggled (toggle_action);
+}
+
+/**
+ * gtk_radio_action_get_group:
+ * @action: the action object
+ *
+ * Returns the list representing the radio group for this object
+ *
+ * Returns: the list representing the radio group for this object
+ *
+ * Since: 2.4
+ */
+GSList *
+gtk_radio_action_get_group (GtkRadioAction *action)
+{
+ g_return_val_if_fail (GTK_IS_RADIO_ACTION (action), NULL);
+
+ return action->private_data->group;
+}
+
+/**
+ * gtk_radio_action_set_group:
+ * @action: the action object
+ * @group: a list representing a radio group
+ *
+ * Sets the radio group for the radio action object.
+ *
+ * Since: 2.4
+ */
+void
+gtk_radio_action_set_group (GtkRadioAction *action,
+ GSList *group)
+{
+ g_return_if_fail (GTK_IS_RADIO_ACTION (action));
+ g_return_if_fail (!g_slist_find (group, action));
+
+ if (action->private_data->group)
+ {
+ GSList *slist;
+
+ action->private_data->group = g_slist_remove (action->private_data->group, action);
+
+ for (slist = action->private_data->group; slist; slist = slist->next)
+ {
+ GtkRadioAction *tmp_action = slist->data;
+
+ tmp_action->private_data->group = action->private_data->group;
+ }
+ }
+
+ action->private_data->group = g_slist_prepend (group, action);
+
+ if (group)
+ {
+ GSList *slist;
+
+ for (slist = action->private_data->group; slist; slist = slist->next)
+ {
+ GtkRadioAction *tmp_action = slist->data;
+
+ tmp_action->private_data->group = action->private_data->group;
+ }
+ }
+ else
+ {
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+ }
+}
diff --git a/gtk/gtkradioaction.h b/gtk/gtkradioaction.h
new file mode 100644
index 0000000000..51db941106
--- /dev/null
+++ b/gtk/gtkradioaction.h
@@ -0,0 +1,73 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+#ifndef __GTK_RADIO_ACTION_H__
+#define __GTK_RADIO_ACTION_H__
+
+#include <gtk/gtktoggleaction.h>
+
+#define GTK_TYPE_RADIO_ACTION (gtk_radio_action_get_type ())
+#define GTK_RADIO_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_RADIO_ACTION, GtkRadioAction))
+#define GTK_RADIO_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_RADIO_ACTION, GtkRadioActionClass))
+#define GTK_IS_RADIO_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_RADIO_ACTION))
+#define GTK_IS_RADIO_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GTK_TYPE_RADIO_ACTION))
+#define GTK_RADIO_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_RADIO_ACTION, GtkRadioActionClass))
+
+typedef struct _GtkRadioAction GtkRadioAction;
+typedef struct _GtkRadioActionPrivate GtkRadioActionPrivate;
+typedef struct _GtkRadioActionClass GtkRadioActionClass;
+
+struct _GtkRadioAction
+{
+ GtkToggleAction parent;
+
+ /*< private >*/
+
+ GtkRadioActionPrivate *private_data;
+};
+
+struct _GtkRadioActionClass
+{
+ GtkToggleActionClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType gtk_radio_action_get_type (void);
+
+GSList *gtk_radio_action_get_group (GtkRadioAction *action);
+void gtk_radio_action_set_group (GtkRadioAction *action,
+ GSList *group);
+
+
+#endif /* __GTK_RADIO_ACTION_H__ */
diff --git a/gtk/gtktoggleaction.c b/gtk/gtktoggleaction.c
new file mode 100644
index 0000000000..dd7ed2330e
--- /dev/null
+++ b/gtk/gtktoggleaction.c
@@ -0,0 +1,246 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+
+#include "gtktoggleaction.h"
+#include "gtktoggleactionprivate.h"
+#include "gtktoggletoolbutton.h"
+#include "gtkcheckmenuitem.h"
+
+enum
+{
+ TOGGLED,
+ LAST_SIGNAL
+};
+
+static void gtk_toggle_action_init (GtkToggleAction *action);
+static void gtk_toggle_action_class_init (GtkToggleActionClass *class);
+
+GType
+gtk_toggle_action_get_type (void)
+{
+ static GtkType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo type_info =
+ {
+ sizeof (GtkToggleActionClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gtk_toggle_action_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+
+ sizeof (GtkToggleAction),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gtk_toggle_action_init,
+ };
+
+ type = g_type_register_static (GTK_TYPE_ACTION,
+ "GtkToggleAction",
+ &type_info, 0);
+ }
+ return type;
+}
+
+static void gtk_toggle_action_activate (GtkAction *action);
+static void gtk_toggle_action_real_toggled (GtkToggleAction *action);
+static void connect_proxy (GtkAction *action,
+ GtkWidget *proxy);
+static void disconnect_proxy (GtkAction *action,
+ GtkWidget *proxy);
+
+static GObjectClass *parent_class = NULL;
+static guint action_signals[LAST_SIGNAL] = { 0 };
+
+static void
+gtk_toggle_action_class_init (GtkToggleActionClass *klass)
+{
+ GObjectClass *gobject_class;
+ GtkActionClass *action_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+ gobject_class = G_OBJECT_CLASS (klass);
+ action_class = GTK_ACTION_CLASS (klass);
+
+ action_class->activate = gtk_toggle_action_activate;
+ action_class->connect_proxy = connect_proxy;
+ action_class->disconnect_proxy = disconnect_proxy;
+
+ action_class->menu_item_type = GTK_TYPE_CHECK_MENU_ITEM;
+ action_class->toolbar_item_type = GTK_TYPE_TOGGLE_TOOL_BUTTON;
+
+ klass->toggled = gtk_toggle_action_real_toggled;
+
+ action_signals[TOGGLED] =
+ g_signal_new ("toggled",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GtkToggleActionClass, toggled),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (gobject_class, sizeof (GtkToggleActionPrivate));
+}
+
+static void
+gtk_toggle_action_init (GtkToggleAction *action)
+{
+ action->private_data = GTK_TOGGLE_ACTION_GET_PRIVATE (action);
+ action->private_data->active = FALSE;
+}
+
+static void
+gtk_toggle_action_activate (GtkAction *action)
+{
+ GtkToggleAction *toggle_action;
+
+ g_return_if_fail (GTK_IS_TOGGLE_ACTION (action));
+
+ toggle_action = GTK_TOGGLE_ACTION (action);
+
+ toggle_action->private_data->active = !toggle_action->private_data->active;
+
+ gtk_toggle_action_toggled (toggle_action);
+}
+
+static void
+gtk_toggle_action_real_toggled (GtkToggleAction *action)
+{
+ GSList *slist;
+
+ g_return_if_fail (GTK_IS_TOGGLE_ACTION (action));
+
+ for (slist = gtk_action_get_proxies (GTK_ACTION (action)); slist; slist = slist->next)
+ {
+ GtkWidget *proxy = slist->data;
+
+ gtk_action_block_activate_from (GTK_ACTION (action), proxy);
+ if (GTK_IS_CHECK_MENU_ITEM (proxy))
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (proxy),
+ action->private_data->active);
+ else if (GTK_IS_TOGGLE_TOOL_BUTTON (proxy))
+ gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (proxy),
+ action->private_data->active);
+ else {
+ g_warning ("Don't know how to toggle `%s' widgets",
+ G_OBJECT_TYPE_NAME (proxy));
+ }
+ gtk_action_unblock_activate_from (GTK_ACTION (action), proxy);
+ }
+}
+
+static void
+connect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ GtkToggleAction *toggle_action;
+
+ toggle_action = GTK_TOGGLE_ACTION (action);
+
+ /* do this before hand, so that we don't call the "activate" handler */
+ if (GTK_IS_MENU_ITEM (proxy))
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (proxy),
+ toggle_action->private_data->active);
+ else if (GTK_IS_TOGGLE_TOOL_BUTTON (proxy))
+ gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (proxy),
+ toggle_action->private_data->active);
+
+ (* GTK_ACTION_CLASS (parent_class)->connect_proxy) (action, proxy);
+}
+
+static void
+disconnect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ GtkToggleAction *toggle_action;
+
+ toggle_action = GTK_TOGGLE_ACTION (action);
+
+ (* GTK_ACTION_CLASS (parent_class)->disconnect_proxy) (action, proxy);
+}
+
+/**
+ * gtk_toggle_action_toggled:
+ * @action: the action object
+ *
+ * Emits the "toggled" signal on the toggle action.
+ *
+ * Since: 2.4
+ */
+void
+gtk_toggle_action_toggled (GtkToggleAction *action)
+{
+ g_return_if_fail (GTK_IS_TOGGLE_ACTION (action));
+
+ g_signal_emit (action, action_signals[TOGGLED], 0);
+}
+
+/**
+ * gtk_toggle_action_set_active:
+ * @action: the action object
+ * @is_active: whether the action should be checked or not
+ *
+ * Sets the checked state on the toggle action.
+ *
+ * Since: 2.4
+ */
+void
+gtk_toggle_action_set_active (GtkToggleAction *action,
+ gboolean is_active)
+{
+ g_return_if_fail (GTK_IS_TOGGLE_ACTION (action));
+
+ is_active = is_active != FALSE;
+
+ if (action->private_data->active != is_active)
+ {
+ gtk_action_activate (GTK_ACTION (action));
+ }
+}
+
+/**
+ * gtk_toggle_action_get_active:
+ * @action: the action object
+ *
+ * Returns: the checked state of the toggle action
+ *
+ * Since: 2.4
+ */
+gboolean
+gtk_toggle_action_get_active (GtkToggleAction *action)
+{
+ g_return_val_if_fail (GTK_IS_TOGGLE_ACTION (action), FALSE);
+
+ return action->private_data->active;
+}
diff --git a/gtk/gtktoggleaction.h b/gtk/gtktoggleaction.h
new file mode 100644
index 0000000000..6c63df832a
--- /dev/null
+++ b/gtk/gtktoggleaction.h
@@ -0,0 +1,76 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+#ifndef __GTK_TOGGLE_ACTION_H__
+#define __GTK_TOGGLE_ACTION_H__
+
+#include <gtk/gtkaction.h>
+
+#define GTK_TYPE_TOGGLE_ACTION (gtk_toggle_action_get_type ())
+#define GTK_TOGGLE_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TOGGLE_ACTION, GtkToggleAction))
+#define GTK_TOGGLE_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TOGGLE_ACTION, GtkToggleActionClass))
+#define GTK_IS_TOGGLE_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TOGGLE_ACTION))
+#define GTK_IS_TOGGLE_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GTK_TYPE_TOGGLE_ACTION))
+#define GTK_TOGGLE_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_TOGGLE_ACTION, GtkToggleActionClass))
+
+typedef struct _GtkToggleAction GtkToggleAction;
+typedef struct _GtkToggleActionPrivate GtkToggleActionPrivate;
+typedef struct _GtkToggleActionClass GtkToggleActionClass;
+
+struct _GtkToggleAction
+{
+ GtkAction parent;
+
+ /*< private >*/
+
+ GtkToggleActionPrivate *private_data;
+};
+
+struct _GtkToggleActionClass
+{
+ GtkActionClass parent_class;
+
+ void (* toggled) (GtkToggleAction *action);
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType gtk_toggle_action_get_type (void);
+
+void gtk_toggle_action_toggled (GtkToggleAction *action);
+void gtk_toggle_action_set_active (GtkToggleAction *action,
+ gboolean is_active);
+gboolean gtk_toggle_action_get_active (GtkToggleAction *action);
+
+
+#endif /* __GTK_TOGGLE_ACTION_H__ */
diff --git a/gtk/gtktoggleactionprivate.h b/gtk/gtktoggleactionprivate.h
new file mode 100644
index 0000000000..46be800a64
--- /dev/null
+++ b/gtk/gtktoggleactionprivate.h
@@ -0,0 +1,42 @@
+/*
+ * GTK - The GIMP Toolkit
+ * 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
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_TOGGLE_ACTION_PRIVATE_H__
+#define __GTK_TOGGLE_ACTION_PRIVATE_H__
+
+
+#define GTK_TOGGLE_ACTION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_TOGGLE_ACTION, GtkToggleActionPrivate))
+
+struct _GtkToggleActionPrivate
+{
+ guint active : 1;
+};
+
+#endif /* __GTK_TOGGLE_ACTION_PRIVATE_H__ */