summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog21
-rw-r--r--ChangeLog.pre-2-1021
-rw-r--r--ChangeLog.pre-2-421
-rw-r--r--ChangeLog.pre-2-621
-rw-r--r--ChangeLog.pre-2-821
-rw-r--r--demos/gtk-demo/appwindow.c234
-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
-rw-r--r--tests/Makefile.am19
-rw-r--r--tests/merge-1.ui20
-rw-r--r--tests/merge-2.ui27
-rw-r--r--tests/merge-3.ui23
-rw-r--r--tests/testactions.c268
-rw-r--r--tests/testmerge.c419
25 files changed, 4930 insertions, 118 deletions
diff --git a/ChangeLog b/ChangeLog
index c4a4ca7f9f..3a793f39e0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+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.
+
Sat Aug 23 21:40:18 2003 Owen Taylor <otaylor@redhat.com>
* gtk/gtkrc.c (gtk_rc_context_parse_one_file): Fix
diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10
index c4a4ca7f9f..3a793f39e0 100644
--- a/ChangeLog.pre-2-10
+++ b/ChangeLog.pre-2-10
@@ -1,3 +1,24 @@
+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.
+
Sat Aug 23 21:40:18 2003 Owen Taylor <otaylor@redhat.com>
* gtk/gtkrc.c (gtk_rc_context_parse_one_file): Fix
diff --git a/ChangeLog.pre-2-4 b/ChangeLog.pre-2-4
index c4a4ca7f9f..3a793f39e0 100644
--- a/ChangeLog.pre-2-4
+++ b/ChangeLog.pre-2-4
@@ -1,3 +1,24 @@
+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.
+
Sat Aug 23 21:40:18 2003 Owen Taylor <otaylor@redhat.com>
* gtk/gtkrc.c (gtk_rc_context_parse_one_file): Fix
diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6
index c4a4ca7f9f..3a793f39e0 100644
--- a/ChangeLog.pre-2-6
+++ b/ChangeLog.pre-2-6
@@ -1,3 +1,24 @@
+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.
+
Sat Aug 23 21:40:18 2003 Owen Taylor <otaylor@redhat.com>
* gtk/gtkrc.c (gtk_rc_context_parse_one_file): Fix
diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8
index c4a4ca7f9f..3a793f39e0 100644
--- a/ChangeLog.pre-2-8
+++ b/ChangeLog.pre-2-8
@@ -1,3 +1,24 @@
+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.
+
Sat Aug 23 21:40:18 2003 Owen Taylor <otaylor@redhat.com>
* gtk/gtkrc.c (gtk_rc_context_parse_one_file): Fix
diff --git a/demos/gtk-demo/appwindow.c b/demos/gtk-demo/appwindow.c
index 61254989f1..ac03687748 100644
--- a/demos/gtk-demo/appwindow.c
+++ b/demos/gtk-demo/appwindow.c
@@ -8,20 +8,20 @@
static GtkWidget *window = NULL;
-
static void
-menuitem_cb (gpointer callback_data,
- guint callback_action,
- GtkWidget *widget)
+activate_action (GtkAction *action)
{
+ const gchar *name = gtk_action_get_name (action);
+ const gchar *typename = G_OBJECT_TYPE_NAME (action);
+
GtkWidget *dialog;
- dialog = gtk_message_dialog_new (GTK_WINDOW (callback_data),
+ dialog = gtk_message_dialog_new (GTK_WINDOW (window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_INFO,
GTK_BUTTONS_CLOSE,
- "You selected or toggled the menu item: \"%s\"",
- gtk_item_factory_path_from_widget (widget));
+ "You activated action: \"%s\" of type \"%s\"",
+ name, typename);
/* Close dialog on user response */
g_signal_connect (dialog,
@@ -33,53 +33,71 @@ menuitem_cb (gpointer callback_data,
}
-static GtkItemFactoryEntry menu_items[] =
-{
- { "/_File", NULL, 0, 0, "<Branch>" },
- { "/File/_New", "<control>N", menuitem_cb, 0, "<StockItem>", GTK_STOCK_NEW },
- { "/File/_Open", "<control>O", menuitem_cb, 0, "<StockItem>", GTK_STOCK_OPEN },
- { "/File/_Save", "<control>S", menuitem_cb, 0, "<StockItem>", GTK_STOCK_SAVE },
- { "/File/Save _As...", NULL, menuitem_cb, 0, "<StockItem>", GTK_STOCK_SAVE },
- { "/File/sep1", NULL, menuitem_cb, 0, "<Separator>" },
- { "/File/_Quit", "<control>Q", menuitem_cb, 0, "<StockItem>", GTK_STOCK_QUIT },
-
- { "/_Preferences", NULL, 0, 0, "<Branch>" },
- { "/_Preferences/_Color", NULL, 0, 0, "<Branch>" },
- { "/_Preferences/Color/_Red", NULL, menuitem_cb, 0, "<RadioItem>" },
- { "/_Preferences/Color/_Green", NULL, menuitem_cb, 0, "/Preferences/Color/Red" },
- { "/_Preferences/Color/_Blue", NULL, menuitem_cb, 0, "/Preferences/Color/Red" },
- { "/_Preferences/_Shape", NULL, 0, 0, "<Branch>" },
- { "/_Preferences/Shape/_Square", NULL, menuitem_cb, 0, "<RadioItem>" },
- { "/_Preferences/Shape/_Rectangle", NULL, menuitem_cb, 0, "/Preferences/Shape/Square" },
- { "/_Preferences/Shape/_Oval", NULL, menuitem_cb, 0, "/Preferences/Shape/Rectangle" },
-
- /* If you wanted this to be right justified you would use "<LastBranch>", not "<Branch>".
- * Right justified help menu items are generally considered a bad idea now days.
- */
- { "/_Help", NULL, 0, 0, "<Branch>" },
- { "/Help/_About", NULL, menuitem_cb, 0 },
+#ifndef N_
+#define N_(String) String
+#endif
+
+static GtkActionGroupEntry entries[] = {
+ { "FileMenu", N_("_File"), NULL, NULL, NULL, NULL, NULL },
+ { "PreferencesMenu", N_("_Preferences"), NULL, NULL, NULL, NULL, NULL },
+ { "ColorMenu", N_("_Color"), NULL, NULL, NULL, NULL, NULL },
+ { "ShapeMenu", N_("_Shape"), NULL, NULL, NULL, NULL, NULL },
+ { "HelpMenu", N_("_Help"), NULL, NULL, NULL, NULL, NULL },
+
+ { "New", N_("_New"), GTK_STOCK_NEW, "<control>N", N_("Create a new file"), G_CALLBACK (activate_action), NULL },
+ { "Open", N_("_Open"), GTK_STOCK_OPEN, "<control>O", N_("Open a file"), G_CALLBACK (activate_action), NULL },
+ { "Save", N_("_Save"), GTK_STOCK_SAVE, "<control>S", N_("Save current file"), G_CALLBACK (activate_action), NULL },
+ { "SaveAs", N_("Save _As..."), GTK_STOCK_SAVE, NULL, N_("Save to a file"), G_CALLBACK (activate_action), NULL },
+ { "Quit", N_("_Quit"), GTK_STOCK_QUIT, "<control>Q", N_("Quit"), G_CALLBACK (activate_action), NULL },
+
+ { "Red", N_("_Red"), NULL, "<control>R", N_("Blood"), G_CALLBACK (activate_action), NULL, RADIO_ACTION },
+ { "Green", N_("_Green"), NULL, "<control>G", N_("Grass"), G_CALLBACK (activate_action), NULL, RADIO_ACTION, "Red" },
+ { "Blue", N_("_Blue"), NULL, "<control>B", N_("Sky"), G_CALLBACK (activate_action), NULL, RADIO_ACTION, "Red" },
+
+ { "Square", N_("_Square"), NULL, "<control>S", N_("Square"), G_CALLBACK (activate_action), NULL, RADIO_ACTION },
+ { "Rectangle", N_("_Rectangle"), NULL, "<control>R", N_("Rectangle"), G_CALLBACK (activate_action), NULL, RADIO_ACTION, "Square" },
+ { "Oval", N_("_Oval"), NULL, "<control>O", N_("Egg"), G_CALLBACK (activate_action), NULL, RADIO_ACTION, "Square" },
+ { "About", N_("_About"), NULL, "<control>A", N_("About"), G_CALLBACK (activate_action), NULL },
+ { "Logo", NULL, "demo-gtk-logo", NULL, N_("GTK+"), G_CALLBACK (activate_action), NULL },
};
+static guint n_entries = G_N_ELEMENTS (entries);
+
+static const gchar *ui_info =
+"<Root>\n"
+" <menu>\n"
+" <submenu name='FileMenu'>\n"
+" <menuitem name='New'/>\n"
+" <menuitem name='Open'/>\n"
+" <menuitem name='Save'/>\n"
+" <menuitem name='SaveAs'/>\n"
+" <separator name='Sep1'/>\n"
+" <menuitem name='Quit'/>\n"
+" </submenu>\n"
+" <submenu name='PreferencesMenu'>\n"
+" <submenu name='ColorMenu'>\n"
+" <menuitem name='Red'/>\n"
+" <menuitem name='Green'/>\n"
+" <menuitem name='Blue'/>\n"
+" </submenu>\n"
+" <submenu name='ShapeMenu'>\n"
+" <menuitem name='Square'/>\n"
+" <menuitem name='Rectangle'/>\n"
+" <menuitem name='Oval'/>\n"
+" </submenu>\n"
+" </submenu>\n"
+" <submenu name='HelpMenu'>\n"
+" <menuitem name='About'/>\n"
+" </submenu>\n"
+" </menu>\n"
+" <dockitem>\n"
+" <toolitem name='Open'/>\n"
+" <toolitem name='Quit'/>\n"
+" <separator name='Sep1'/>\n"
+" <toolitem name='Logo'/>\n"
+" </dockitem>\n"
+"</Root>\n";
-static void
-toolbar_cb (GtkWidget *button,
- gpointer data)
-{
- GtkWidget *dialog;
-
- dialog = gtk_message_dialog_new (GTK_WINDOW (data),
- GTK_DIALOG_DESTROY_WITH_PARENT,
- GTK_MESSAGE_INFO,
- GTK_BUTTONS_CLOSE,
- "You selected a toolbar button");
- /* Close dialog on user response */
- g_signal_connect (dialog,
- "response",
- G_CALLBACK (gtk_widget_destroy),
- NULL);
-
- gtk_widget_show (dialog);
-}
/* This function registers our custom toolbar icons, so they can be themed.
*
@@ -195,19 +213,47 @@ update_resize_grip (GtkWidget *widget,
}
+static void
+add_widget (GtkMenuMerge *merge,
+ GtkWidget *widget,
+ GtkTable *table)
+{
+ if (GTK_IS_MENU_BAR (widget))
+ {
+ gtk_table_attach (GTK_TABLE (table),
+ widget,
+ /* X direction */ /* Y direction */
+ 0, 1, 0, 1,
+ GTK_EXPAND | GTK_FILL, 0,
+ 0, 0);
+ }
+ else if (GTK_IS_TOOLBAR (widget))
+ {
+ gtk_table_attach (GTK_TABLE (table),
+ widget,
+ /* X direction */ /* Y direction */
+ 0, 1, 1, 2,
+ GTK_EXPAND | GTK_FILL, 0,
+ 0, 0);
+ }
+
+ gtk_widget_show (widget);
+}
+
GtkWidget *
do_appwindow (void)
{
if (!window)
{
GtkWidget *table;
- GtkWidget *toolbar;
GtkWidget *statusbar;
GtkWidget *contents;
GtkWidget *sw;
GtkTextBuffer *buffer;
- GtkAccelGroup *accel_group;
- GtkItemFactory *item_factory;
+ GtkActionGroup *action_group;
+ GtkAction *action;
+ GtkMenuMerge *merge;
+ GError *error = NULL;
register_stock_icons ();
@@ -226,70 +272,28 @@ do_appwindow (void)
gtk_container_add (GTK_CONTAINER (window), table);
- /* Create the menubar
+ /* Create the menubar and toolbar
*/
- accel_group = gtk_accel_group_new ();
- gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
- g_object_unref (accel_group);
+ action_group = gtk_action_group_new ("AppWindowActions");
+ gtk_action_group_add_actions (action_group, entries, n_entries);
+
+ action = gtk_action_group_get_action (action_group, "red");
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+ action = gtk_action_group_get_action (action_group, "square");
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+
+ merge = gtk_menu_merge_new ();
+ gtk_menu_merge_insert_action_group (merge, action_group, 0);
+ g_signal_connect (merge, "add_widget", G_CALLBACK (add_widget), table);
+ gtk_window_add_accel_group (GTK_WINDOW (window),
+ gtk_menu_merge_get_accel_group (merge));
- item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", accel_group);
-
- /* Set up item factory to go away with the window */
- g_object_ref (item_factory);
- gtk_object_sink (GTK_OBJECT (item_factory));
- g_object_set_data_full (G_OBJECT (window),
- "<main>",
- item_factory,
- (GDestroyNotify) g_object_unref);
-
- /* create menu items */
- gtk_item_factory_create_items (item_factory, G_N_ELEMENTS (menu_items),
- menu_items, window);
-
- gtk_table_attach (GTK_TABLE (table),
- gtk_item_factory_get_widget (item_factory, "<main>"),
- /* X direction */ /* Y direction */
- 0, 1, 0, 1,
- GTK_EXPAND | GTK_FILL, 0,
- 0, 0);
-
- /* Create the toolbar
- */
- toolbar = gtk_toolbar_new ();
-
- gtk_toolbar_insert_stock (GTK_TOOLBAR (toolbar),
- GTK_STOCK_OPEN,
- "This is a demo button with an 'open' icon",
- NULL,
- G_CALLBACK (toolbar_cb),
- window, /* user data for callback */
- -1); /* -1 means "append" */
-
- gtk_toolbar_insert_stock (GTK_TOOLBAR (toolbar),
- GTK_STOCK_QUIT,
- "This is a demo button with a 'quit' icon",
- NULL,
- G_CALLBACK (toolbar_cb),
- window, /* user data for callback */
- -1); /* -1 means "append" */
-
- gtk_toolbar_append_space (GTK_TOOLBAR (toolbar));
-
- gtk_toolbar_insert_stock (GTK_TOOLBAR (toolbar),
- "demo-gtk-logo",
- "This is a demo button with a 'gtk' icon",
- NULL,
- G_CALLBACK (toolbar_cb),
- window, /* user data for callback */
- -1); /* -1 means "append" */
-
- gtk_table_attach (GTK_TABLE (table),
- toolbar,
- /* X direction */ /* Y direction */
- 0, 1, 1, 2,
- GTK_EXPAND | GTK_FILL, 0,
- 0, 0);
+ if (!gtk_menu_merge_add_ui_from_string (merge, ui_info, -1, &error))
+ {
+ g_message ("building menus failed: %s", error->message);
+ g_error_free (error);
+ }
/* Create document
*/
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__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index adff1476f4..367777581a 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -54,7 +54,9 @@ noinst_PROGRAMS = \
pixbuf-read \
pixbuf-lowmem \
pixbuf-randomly-modified \
- pixbuf-random
+ pixbuf-random \
+ testmerge \
+ testactions
simple_DEPENDENCIES = $(TEST_DEPS)
testicontheme_DEPENDENCIES = $(TEST_DEPS)
@@ -79,6 +81,8 @@ testtreecolumns_DEPENDENCIES = $(DEPS)
testtreesort_DEPENDENCIES = $(DEPS)
treestoretest_DEPENDENCIES = $(TEST_DEPS)
testxinerama_DEPENDENCIES = $(TEST_DEPS)
+testmerge_DEPENDENCIES = $(TEST_DEPS)
+testactions_DEPENDENCIES = $(TEST_DEPS)
simple_LDADD = $(LDADDS)
testcalendar_LDADD = $(LDADDS)
@@ -109,6 +113,8 @@ pixbuf_read_LDADD = $(LDADDS)
pixbuf_lowmem_LDADD = $(LDADDS)
pixbuf_randomly_modified_LDADD = $(LDADDS)
pixbuf_random_LDADD = $(LDADDS)
+testmerge_LDADD = $(LDADDS)
+testactions_LDADD = $(LDADDS)
testgtk_SOURCES = \
prop-editor.c \
@@ -137,6 +143,12 @@ testsocket_child_SOURCES = \
testsocket_child.c \
testsocket_common.c
+testmerge_SOURCES = \
+ testmerge.c
+
+testactions_SOURCES = \
+ testactions.c
+
EXTRA_DIST = \
prop-editor.h \
testgtk.1 \
@@ -152,4 +164,7 @@ EXTRA_DIST = \
test.xpm \
check-y.xpm \
check-n.xpm \
- test.xpm
+ test.xpm \
+ merge-1.ui \
+ merge-2.ui \
+ merge-3.ui
diff --git a/tests/merge-1.ui b/tests/merge-1.ui
new file mode 100644
index 0000000000..8ff6a72b2b
--- /dev/null
+++ b/tests/merge-1.ui
@@ -0,0 +1,20 @@
+<!--*- xml -*-->
+<Root>
+ <menu>
+ <submenu name="FileMenu" verb="StockFileMenuAction">
+ <menuitem name="Open" verb="OpenAction" />
+ </submenu>
+ <submenu name="EditMenu" verb="StockEditMenuAction">
+ <menuitem name="Cut" verb="CutAction" />
+ </submenu>
+ <placeholder name="TestPlaceholder" />
+ </menu>
+ <dockitem name="toolbar1">
+ <placeholder name="ToolbarPlaceholder" />
+ <toolitem name="NewButton" verb="NewAction" />
+ <toolitem name="CutButton" verb="CutAction" />
+ <toolitem name="CopyButton" verb="CopyAction" />
+ <toolitem name="PasteButton" verb="PasteAction" />
+ <placeholder name="JustifyToolItems"/>
+ </dockitem>
+</Root>
diff --git a/tests/merge-2.ui b/tests/merge-2.ui
new file mode 100644
index 0000000000..cf32ecfcc4
--- /dev/null
+++ b/tests/merge-2.ui
@@ -0,0 +1,27 @@
+<!--*- xml -*-->
+<Root>
+ <menu>
+ <submenu name="FileMenu" verb="StockFileMenuAction">
+ <menuitem name="New" verb="NewAction" pos="top" />
+ <separator />
+ <menuitem name="Quit" verb="QuitAction" />
+ </submenu>
+ <submenu name="HelpMenu" verb="StockHelpMenuAction">
+ <menuitem name="About" verb="AboutAction" />
+ </submenu>
+ </menu>
+ <dockitem name="toolbar1">
+ <placeholder name="ToolbarPlaceholder">
+ <toolitem name="Quit" verb="QuitAction" />
+ <separator />
+ </placeholder>
+ </dockitem>
+ <popups>
+ <popup name="FileMenu" verb="StockFileMenuAction">
+ <menuitem name="New" verb="NewAction" pos="top" />
+ <submenu name="HelpMenu" verb="StockHelpMenuAction">
+ <menuitem name="About" verb="AboutAction" />
+ </submenu>
+ </popup>
+ </popups>
+</Root>
diff --git a/tests/merge-3.ui b/tests/merge-3.ui
new file mode 100644
index 0000000000..397930d27e
--- /dev/null
+++ b/tests/merge-3.ui
@@ -0,0 +1,23 @@
+<!--*- xml -*-->
+<Root>
+ <menu>
+ <submenu name="FileMenu" verb="StockFileMenuAction">
+ <menuitem name="New" verb="New2Action" />
+ </submenu>
+ <placeholder name="TestPlaceholder">
+ <submenu name="Test">
+ <menuitem name="Cut" verb="CutAction" />
+ </submenu>
+ </placeholder>
+ </menu>
+ <dockitem name="toolbar1">
+ <placeholder name="JustifyToolItems">
+ <separator name="first-sep"/>
+ <toolitem name="Left" verb="justify-left"/>
+ <toolitem name="Centre" verb="justify-center"/>
+ <toolitem name="Right" verb="justify-right"/>
+ <toolitem name="Fill" verb="justify-fill"/>
+ <separator name="second-sep" />
+ </placeholder>
+ </dockitem>
+</Root>
diff --git a/tests/testactions.c b/tests/testactions.c
new file mode 100644
index 0000000000..b5e1f6c7d1
--- /dev/null
+++ b/tests/testactions.c
@@ -0,0 +1,268 @@
+#undef GTK_DISABLE_DEPRECATED
+#include <gtk/gtk.h>
+
+#ifndef _
+# define _(String) (String)
+# define N_(String) (String)
+#endif
+
+static GtkActionGroup *action_group = NULL;
+static GtkToolbar *toolbar = NULL;
+
+static void
+activate_action (GtkAction *action)
+{
+ const gchar *name = gtk_action_get_name (action);
+ const gchar *typename = G_OBJECT_TYPE_NAME (action);
+
+ g_message ("Action %s (type=%s) activated", name, typename);
+}
+
+static void
+toggle_action (GtkAction *action)
+{
+ const gchar *name = gtk_action_get_name (action);
+ const gchar *typename = G_OBJECT_TYPE_NAME (action);
+
+ g_message ("Action %s (type=%s) activated (active=%d)", name, typename,
+ gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+static void
+toggle_cnp_actions (GtkAction *action)
+{
+ gboolean sensitive;
+
+ sensitive = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+ action = gtk_action_group_get_action (action_group, "cut");
+ g_object_set (action, "sensitive", sensitive, NULL);
+ action = gtk_action_group_get_action (action_group, "copy");
+ g_object_set (action, "sensitive", sensitive, NULL);
+ action = gtk_action_group_get_action (action_group, "paste");
+ g_object_set (action, "sensitive", sensitive, NULL);
+
+ action = gtk_action_group_get_action (action_group, "toggle-cnp");
+ if (sensitive)
+ g_object_set (action, "label", _("Disable Cut and paste ops"), NULL);
+ else
+ g_object_set (action, "label", _("Enable Cut and paste ops"), NULL);
+}
+
+static void
+show_accel_dialog (GtkAction *action)
+{
+ g_message ("Sorry, accel dialog not available");
+}
+
+static void
+toolbar_style (GtkAction *action,
+ gpointer user_data)
+{
+ GtkToolbarStyle style;
+
+ g_return_if_fail (toolbar != NULL);
+ style = GPOINTER_TO_INT (user_data);
+
+ gtk_toolbar_set_style (toolbar, style);
+}
+
+static void
+toolbar_size (GtkAction *action,
+ gpointer user_data)
+{
+ GtkIconSize size;
+
+ g_return_if_fail (toolbar != NULL);
+ size = GPOINTER_TO_INT (user_data);
+
+ gtk_toolbar_set_icon_size (toolbar, size);
+}
+
+/* convenience functions for declaring actions */
+static GtkActionGroupEntry entries[] = {
+ { "Menu1Action", N_("Menu _1"), NULL, NULL, NULL, NULL, NULL },
+ { "Menu2Action", N_("Menu _2"), NULL, NULL, NULL, NULL, NULL },
+
+ { "cut", N_("C_ut"), GTK_STOCK_CUT, "<control>X",
+ N_("Cut the selected text to the clipboard"),
+ G_CALLBACK (activate_action), NULL },
+ { "copy", N_("_Copy"), GTK_STOCK_COPY, "<control>C",
+ N_("Copy the selected text to the clipboard"),
+ G_CALLBACK (activate_action), NULL },
+ { "paste", N_("_Paste"), GTK_STOCK_PASTE, "<control>V",
+ N_("Paste the text from the clipboard"),
+ G_CALLBACK (activate_action), NULL },
+ { "bold", N_("_Bold"), GTK_STOCK_BOLD, "<control>B",
+ N_("Change to bold face"),
+ G_CALLBACK (toggle_action), NULL, TOGGLE_ACTION },
+
+ { "justify-left", N_("_Left"), GTK_STOCK_JUSTIFY_LEFT, "<control>L",
+ N_("Left justify the text"),
+ G_CALLBACK (toggle_action), NULL, RADIO_ACTION },
+ { "justify-center", N_("C_enter"), GTK_STOCK_JUSTIFY_CENTER, "<control>E",
+ N_("Center justify the text"),
+ G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+ { "justify-right", N_("_Right"), GTK_STOCK_JUSTIFY_RIGHT, "<control>R",
+ N_("Right justify the text"),
+ G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+ { "justify-fill", N_("_Fill"), GTK_STOCK_JUSTIFY_FILL, "<control>J",
+ N_("Fill justify the text"),
+ G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+ { "quit", NULL, GTK_STOCK_QUIT, "<control>Q",
+ N_("Quit the application"),
+ G_CALLBACK (gtk_main_quit), NULL },
+ { "toggle-cnp", N_("Enable Cut/Copy/Paste"), NULL, NULL,
+ N_("Change the sensitivity of the cut, copy and paste actions"),
+ G_CALLBACK (toggle_cnp_actions), NULL, TOGGLE_ACTION },
+ { "customise-accels", N_("Customise _Accels"), NULL, NULL,
+ N_("Customise keyboard shortcuts"),
+ G_CALLBACK (show_accel_dialog), NULL },
+ { "toolbar-icons", N_("Icons"), NULL, NULL,
+ NULL, G_CALLBACK (toolbar_style), GINT_TO_POINTER (GTK_TOOLBAR_ICONS),
+ RADIO_ACTION, NULL },
+ { "toolbar-text", N_("Text"), NULL, NULL,
+ NULL, G_CALLBACK (toolbar_style), GINT_TO_POINTER (GTK_TOOLBAR_TEXT),
+ RADIO_ACTION, "toolbar-icons" },
+ { "toolbar-both", N_("Both"), NULL, NULL,
+ NULL, G_CALLBACK (toolbar_style), GINT_TO_POINTER (GTK_TOOLBAR_BOTH),
+ RADIO_ACTION, "toolbar-icons" },
+ { "toolbar-both-horiz", N_("Both Horizontal"), NULL, NULL,
+ NULL, G_CALLBACK (toolbar_style), GINT_TO_POINTER(GTK_TOOLBAR_BOTH_HORIZ),
+ RADIO_ACTION, "toolbar-icons" },
+ { "toolbar-small-icons", N_("Small Icons"), NULL, NULL,
+ NULL,
+ G_CALLBACK (toolbar_size), GINT_TO_POINTER (GTK_ICON_SIZE_SMALL_TOOLBAR) },
+ { "toolbar-large-icons", N_("Large Icons"), NULL, NULL,
+ NULL,
+ G_CALLBACK (toolbar_size), GINT_TO_POINTER (GTK_ICON_SIZE_LARGE_TOOLBAR) },
+};
+static guint n_entries = G_N_ELEMENTS (entries);
+
+/* XML description of the menus for the test app. The parser understands
+ * a subset of the Bonobo UI XML format, and uses GMarkup for parsing */
+static const gchar *ui_info =
+"<Root>\n"
+" <menu>\n"
+" <submenu name=\"Menu _1\" verb=\"Menu1Action\">\n"
+" <menuitem name=\"cut\" verb=\"cut\" />\n"
+" <menuitem name=\"copy\" verb=\"copy\" />\n"
+" <menuitem name=\"paste\" verb=\"paste\" />\n"
+" <separator name=\"sep1\" />\n"
+" <menuitem name=\"bold1\" verb=\"bold\" />\n"
+" <menuitem name=\"bold2\" verb=\"bold\" />\n"
+" <separator name=\"sep2\" />\n"
+" <menuitem name=\"toggle-cnp\" verb=\"toggle-cnp\" />\n"
+" <separator name=\"sep3\" />\n"
+" <menuitem name=\"quit\" verb=\"quit\" />\n"
+" </submenu>\n"
+" <submenu name=\"Menu _2\" verb=\"Menu2Action\">\n"
+" <menuitem name=\"cut\" verb=\"cut\" />\n"
+" <menuitem name=\"copy\" verb=\"copy\" />\n"
+" <menuitem name=\"paste\" verb=\"paste\" />\n"
+" <separator name=\"sep4\"/>\n"
+" <menuitem name=\"bold\" verb=\"bold\" />\n"
+" <separator name=\"sep5\"/>\n"
+" <menuitem name=\"justify-left\" verb=\"justify-left\" />\n"
+" <menuitem name=\"justify-center\" verb=\"justify-center\" />\n"
+" <menuitem name=\"justify-right\" verb=\"justify-right\" />\n"
+" <menuitem name=\"justify-fill\" verb=\"justify-fill\" />\n"
+" <separator name=\"sep6\"/>\n"
+" <menuitem name=\"customise-accels\" verb=\"customise-accels\" />\n"
+" <separator name=\"sep7\"/>\n"
+" <menuitem verb=\"toolbar-icons\" />\n"
+" <menuitem verb=\"toolbar-text\" />\n"
+" <menuitem verb=\"toolbar-both\" />\n"
+" <menuitem verb=\"toolbar-both-horiz\" />\n"
+" <separator name=\"sep8\"/>\n"
+" <menuitem verb=\"toolbar-small-icons\" />\n"
+" <menuitem verb=\"toolbar-large-icons\" />\n"
+" </submenu>\n"
+" </menu>\n"
+" <dockitem name=\"toolbar\">\n"
+" <toolitem name=\"cut\" verb=\"cut\" />\n"
+" <toolitem name=\"copy\" verb=\"copy\" />\n"
+" <toolitem name=\"paste\" verb=\"paste\" />\n"
+" <separator name=\"sep9\" />\n"
+" <toolitem name=\"bold\" verb=\"bold\" />\n"
+" <separator name=\"sep10\" />\n"
+" <toolitem name=\"justify-left\" verb=\"justify-left\" />\n"
+" <toolitem name=\"justify-center\" verb=\"justify-center\" />\n"
+" <toolitem name=\"justify-right\" verb=\"justify-right\" />\n"
+" <toolitem name=\"justify-fill\" verb=\"justify-fill\" />\n"
+" <separator name=\"sep11\"/>\n"
+" <toolitem name=\"quit\" verb=\"quit\" />\n"
+" </dockitem>\n"
+"</Root>\n";
+
+static void
+add_widget (GtkMenuMerge *merge,
+ GtkWidget *widget,
+ GtkContainer *container)
+{
+
+ gtk_container_add (container, widget);
+ gtk_widget_show (widget);
+
+ if (GTK_IS_TOOLBAR (widget))
+ {
+ toolbar = GTK_TOOLBAR (widget);
+ gtk_toolbar_set_show_arrow (toolbar, TRUE);
+ }
+}
+
+static void
+create_window (GtkActionGroup *action_group)
+{
+ GtkMenuMerge *merge;
+ GtkWidget *window;
+ GtkWidget *box;
+ GError *error = NULL;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (window), -1, -1);
+ gtk_window_set_title (GTK_WINDOW (window), "Action Test");
+ g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
+
+ box = gtk_vbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (window), box);
+ gtk_widget_show (box);
+
+ merge = gtk_menu_merge_new ();
+ gtk_menu_merge_insert_action_group (merge, action_group, 0);
+ g_signal_connect (merge, "add_widget", G_CALLBACK (add_widget), box);
+
+ gtk_window_add_accel_group (GTK_WINDOW (window),
+ gtk_menu_merge_get_accel_group (merge));
+
+ if (!gtk_menu_merge_add_ui_from_string (merge, ui_info, -1, &error))
+ {
+ g_message ("building menus failed: %s", error->message);
+ g_error_free (error);
+ }
+
+ gtk_widget_show (window);
+}
+
+int
+main (int argc, char **argv)
+{
+ gtk_init (&argc, &argv);
+
+ if (g_file_test ("accels", G_FILE_TEST_IS_REGULAR))
+ gtk_accel_map_load ("accels");
+
+ action_group = gtk_action_group_new ("TestActions");
+ gtk_action_group_add_actions (action_group, entries, n_entries);
+
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (action_group, "toggle-cnp")), TRUE);
+
+ create_window (action_group);
+
+ gtk_main ();
+
+ g_object_unref (action_group);
+
+ gtk_accel_map_save ("accels");
+
+ return 0;
+}
diff --git a/tests/testmerge.c b/tests/testmerge.c
new file mode 100644
index 0000000000..8e501af7a3
--- /dev/null
+++ b/tests/testmerge.c
@@ -0,0 +1,419 @@
+#include <stdio.h>
+#include <string.h>
+#include <gtk/gtk.h>
+
+#ifndef _
+# define _(String) (String)
+# define N_(String) (String)
+#endif
+
+struct { const gchar *filename; guint merge_id; } merge_ids[] = {
+ { "merge-1.ui", 0 },
+ { "merge-2.ui", 0 },
+ { "merge-3.ui", 0 }
+};
+
+static void
+dump_tree (GtkWidget *button,
+ GtkMenuMerge *merge)
+{
+ gchar *dump;
+
+ dump = gtk_menu_merge_get_ui (merge);
+ g_message (dump);
+ g_free (dump);
+}
+
+static void
+activate_action (GtkAction *action)
+{
+ const gchar *name = gtk_action_get_name (action);
+ const gchar *typename = G_OBJECT_TYPE_NAME (action);
+
+ g_message ("Action %s (type=%s) activated", name, typename);
+}
+
+static void
+toggle_action (GtkAction *action)
+{
+ const gchar *name = gtk_action_get_name (action);
+ const gchar *typename = G_OBJECT_TYPE_NAME (action);
+
+ g_message ("Action %s (type=%s) activated (active=%d)", name, typename,
+ gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+
+static GtkActionGroupEntry entries[] = {
+ { "StockFileMenuAction", N_("_File"), NULL, NULL, NULL, NULL, NULL },
+ { "StockEditMenuAction", N_("_Edit"), NULL, NULL, NULL, NULL, NULL },
+ { "StockHelpMenuAction", N_("_Help"), NULL, NULL, NULL, NULL, NULL },
+ { "Test", N_("Test"), NULL, NULL, NULL, NULL, NULL },
+
+ { "NewAction", NULL, GTK_STOCK_NEW, "<control>n", NULL,
+ G_CALLBACK (activate_action), NULL },
+ { "New2Action", NULL, GTK_STOCK_NEW, "<control>m", NULL,
+ G_CALLBACK (activate_action), NULL },
+ { "OpenAction", NULL, GTK_STOCK_OPEN, "<control>o", NULL,
+ G_CALLBACK (activate_action), NULL },
+ { "QuitAction", NULL, GTK_STOCK_QUIT, "<control>q", NULL,
+ G_CALLBACK (gtk_main_quit), NULL },
+ { "CutAction", NULL, GTK_STOCK_CUT, "<control>x", NULL,
+ G_CALLBACK (activate_action), NULL },
+ { "CopyAction", NULL, GTK_STOCK_COPY, "<control>c", NULL,
+ G_CALLBACK (activate_action), NULL },
+ { "PasteAction", NULL, GTK_STOCK_PASTE, "<control>v", NULL,
+ G_CALLBACK (activate_action), NULL },
+ { "justify-left", NULL, GTK_STOCK_JUSTIFY_LEFT, "<control>L",
+ N_("Left justify the text"),
+ G_CALLBACK (toggle_action), NULL, RADIO_ACTION, NULL },
+ { "justify-center", NULL, GTK_STOCK_JUSTIFY_CENTER, "<control>E",
+ N_("Center justify the text"),
+ G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+ { "justify-right", NULL, GTK_STOCK_JUSTIFY_RIGHT, "<control>R",
+ N_("Right justify the text"),
+ G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+ { "justify-fill", NULL, GTK_STOCK_JUSTIFY_FILL, "<control>J",
+ N_("Fill justify the text"),
+ G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+ { "AboutAction", N_("_About"), NULL, NULL, NULL,
+ G_CALLBACK (activate_action), NULL },
+};
+static guint n_entries = G_N_ELEMENTS (entries);
+
+static void
+add_widget (GtkMenuMerge *merge,
+ GtkWidget *widget,
+ GtkBox *box)
+{
+ gtk_box_pack_start (box, widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+}
+
+static void
+toggle_merge (GtkWidget *button,
+ GtkMenuMerge *merge)
+{
+ gint mergenum;
+
+ mergenum = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "mergenum"));
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
+ {
+ GError *err = NULL;
+
+ g_message ("merging %s", merge_ids[mergenum].filename);
+ merge_ids[mergenum].merge_id =
+ gtk_menu_merge_add_ui_from_file (merge, merge_ids[mergenum].filename, &err);
+ if (err != NULL)
+ {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (button)),
+ 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
+ "could not merge %s: %s", merge_ids[mergenum].filename,
+ err->message);
+
+ g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (gtk_object_destroy), NULL);
+ gtk_widget_show (dialog);
+
+ g_clear_error (&err);
+ }
+ }
+ else
+ {
+ g_message ("unmerging %s (merge_id=%u)", merge_ids[mergenum].filename,
+ merge_ids[mergenum].merge_id);
+ gtk_menu_merge_remove_ui (merge, merge_ids[mergenum].merge_id);
+ }
+}
+
+static void
+set_name_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GtkAction *action;
+ char *name;
+
+ gtk_tree_model_get (tree_model, iter, 0, &action, -1);
+ g_object_get (G_OBJECT (action), "name", &name, NULL);
+ g_object_set (G_OBJECT (cell), "text", name, NULL);
+ g_free (name);
+ g_object_unref (action);
+}
+
+static void
+set_sensitive_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GtkAction *action;
+ gboolean sensitive;
+
+ gtk_tree_model_get (tree_model, iter, 0, &action, -1);
+ g_object_get (G_OBJECT (action), "sensitive", &sensitive, NULL);
+ g_object_set (G_OBJECT (cell), "active", sensitive, NULL);
+ g_object_unref (action);
+}
+
+
+static void
+set_visible_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GtkAction *action;
+ gboolean visible;
+
+ gtk_tree_model_get (tree_model, iter, 0, &action, -1);
+ g_object_get (G_OBJECT (action), "visible", &visible, NULL);
+ g_object_set (G_OBJECT (cell), "active", visible, NULL);
+ g_object_unref (action);
+}
+
+static void
+sensitivity_toggled (GtkCellRendererToggle *cell,
+ const gchar *path_str,
+ GtkTreeModel *model)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GtkAction *action;
+ gboolean sensitive;
+
+ path = gtk_tree_path_new_from_string (path_str);
+ gtk_tree_model_get_iter (model, &iter, path);
+
+ gtk_tree_model_get (model, &iter, 0, &action, -1);
+ g_object_get (G_OBJECT (action), "sensitive", &sensitive, NULL);
+ g_object_set (G_OBJECT (action), "sensitive", !sensitive, NULL);
+ gtk_tree_model_row_changed (model, path, &iter);
+ gtk_tree_path_free (path);
+}
+
+static void
+visibility_toggled (GtkCellRendererToggle *cell,
+ const gchar *path_str,
+ GtkTreeModel *model)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GtkAction *action;
+ gboolean visible;
+
+ path = gtk_tree_path_new_from_string (path_str);
+ gtk_tree_model_get_iter (model, &iter, path);
+
+ gtk_tree_model_get (model, &iter, 0, &action, -1);
+ g_object_get (G_OBJECT (action), "visible", &visible, NULL);
+ g_object_set (G_OBJECT (action), "visible", !visible, NULL);
+ gtk_tree_model_row_changed (model, path, &iter);
+ gtk_tree_path_free (path);
+}
+
+static gint
+iter_compare_func (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer user_data)
+{
+ GValue a_value = { 0, }, b_value = { 0, };
+ GtkAction *a_action, *b_action;
+ const gchar *a_name, *b_name;
+ gint retval = 0;
+
+ gtk_tree_model_get_value (model, a, 0, &a_value);
+ gtk_tree_model_get_value (model, b, 0, &b_value);
+ a_action = GTK_ACTION (g_value_get_object (&a_value));
+ b_action = GTK_ACTION (g_value_get_object (&b_value));
+
+ a_name = gtk_action_get_name (a_action);
+ b_name = gtk_action_get_name (b_action);
+ if (a_name == NULL && b_name == NULL)
+ retval = 0;
+ else if (a_name == NULL)
+ retval = -1;
+ else if (b_name == NULL)
+ retval = 1;
+ else
+ retval = strcmp (a_name, b_name);
+
+ g_value_unset (&b_value);
+ g_value_unset (&a_value);
+
+ return retval;
+}
+
+static GtkWidget *
+create_tree_view (GtkMenuMerge *merge)
+{
+ GtkWidget *tree_view, *sw;
+ GtkListStore *store;
+ GList *p;
+ GtkCellRenderer *cell;
+
+ store = gtk_list_store_new (1, GTK_TYPE_ACTION);
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), 0,
+ iter_compare_func, NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), 0,
+ GTK_SORT_ASCENDING);
+
+ for (p = gtk_menu_merge_get_action_groups (merge); p; p = p->next)
+ {
+ GList *actions, *l;
+
+ actions = gtk_action_group_list_actions (p->data);
+
+ for (l = actions; l; l = l->next)
+ {
+ GtkTreeIter iter;
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, l->data, -1);
+ }
+ }
+
+ tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
+ g_object_unref (store);
+
+ gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (tree_view),
+ -1, "Action",
+ gtk_cell_renderer_text_new (),
+ set_name_func, NULL, NULL);
+
+ gtk_tree_view_column_set_sort_column_id (gtk_tree_view_get_column (GTK_TREE_VIEW (tree_view), 0), 0);
+
+ cell = gtk_cell_renderer_toggle_new ();
+ g_signal_connect (cell, "toggled", G_CALLBACK (sensitivity_toggled), store);
+ gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (tree_view),
+ -1, "Sensitive",
+ cell,
+ set_sensitive_func, NULL, NULL);
+
+ cell = gtk_cell_renderer_toggle_new ();
+ g_signal_connect (cell, "toggled", G_CALLBACK (visibility_toggled), store);
+ gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (tree_view),
+ -1, "Visible",
+ cell,
+ set_visible_func, NULL, NULL);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_container_add (GTK_CONTAINER (sw), tree_view);
+
+ return sw;
+}
+
+static gboolean
+area_press (GtkWidget *drawing_area,
+ GdkEventButton *event,
+ GtkMenuMerge *merge)
+{
+ gtk_widget_grab_focus (drawing_area);
+
+ if (event->button == 3 &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ GtkWidget *menu = gtk_menu_merge_get_widget (merge, "/popups/FileMenu");
+
+ if (GTK_IS_MENU (menu))
+ {
+ gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+ NULL, drawing_area,
+ 3, event->time);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+
+}
+
+int
+main (int argc, char **argv)
+{
+ GtkActionGroup *action_group;
+ GtkMenuMerge *merge;
+ GtkWidget *window, *table, *frame, *menu_box, *vbox, *view, *area;
+ GtkWidget *button;
+ gint i;
+
+ gtk_init (&argc, &argv);
+
+ action_group = gtk_action_group_new ("TestActions");
+ gtk_action_group_add_actions (action_group, entries, n_entries);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (window), -1, 400);
+ g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 2);
+ gtk_container_set_border_width (GTK_CONTAINER (table), 2);
+ gtk_container_add (GTK_CONTAINER (window), table);
+
+ frame = gtk_frame_new ("Menus and Toolbars");
+ gtk_table_attach (GTK_TABLE (table), frame, 0,2, 1,2,
+ GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
+
+ menu_box = gtk_vbox_new (FALSE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (menu_box), 2);
+ gtk_container_add (GTK_CONTAINER (frame), menu_box);
+
+ area = gtk_drawing_area_new ();
+ gtk_widget_set_events (area, GDK_BUTTON_PRESS_MASK);
+ gtk_widget_set_size_request (area, -1, 40);
+ gtk_box_pack_end (GTK_BOX (menu_box), area, FALSE, FALSE, 0);
+ gtk_widget_show (area);
+
+ merge = gtk_menu_merge_new ();
+
+ g_signal_connect (area, "button_press_event",
+ G_CALLBACK (area_press), merge);
+
+ gtk_menu_merge_insert_action_group (merge, action_group, 0);
+ g_signal_connect (merge, "add_widget", G_CALLBACK (add_widget), menu_box);
+
+ gtk_window_add_accel_group (GTK_WINDOW (window),
+ gtk_menu_merge_get_accel_group (merge));
+
+ frame = gtk_frame_new ("UI Files");
+ gtk_table_attach (GTK_TABLE (table), frame, 0,1, 0,1,
+ GTK_FILL, GTK_FILL|GTK_EXPAND, 0, 0);
+
+ vbox = gtk_vbox_new (FALSE, 2);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+
+ for (i = 0; i < G_N_ELEMENTS (merge_ids); i++)
+ {
+ button = gtk_check_button_new_with_label (merge_ids[i].filename);
+ g_object_set_data (G_OBJECT (button), "mergenum", GINT_TO_POINTER (i));
+ g_signal_connect (button, "toggled", G_CALLBACK (toggle_merge), merge);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ }
+
+ button = gtk_button_new_with_mnemonic ("_Dump Tree");
+ g_signal_connect (button, "clicked", G_CALLBACK (dump_tree), merge);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+
+ view = create_tree_view (merge);
+ gtk_table_attach (GTK_TABLE (table), view, 1,2, 0,1,
+ GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0);
+
+ gtk_widget_show_all (window);
+ gtk_main ();
+
+
+ return 0;
+}