diff options
-rw-r--r-- | ChangeLog | 21 | ||||
-rw-r--r-- | ChangeLog.pre-2-10 | 21 | ||||
-rw-r--r-- | ChangeLog.pre-2-4 | 21 | ||||
-rw-r--r-- | ChangeLog.pre-2-6 | 21 | ||||
-rw-r--r-- | ChangeLog.pre-2-8 | 21 | ||||
-rw-r--r-- | demos/gtk-demo/appwindow.c | 234 | ||||
-rw-r--r-- | gtk/Makefile.am | 15 | ||||
-rw-r--r-- | gtk/gtk.h | 6 | ||||
-rw-r--r-- | gtk/gtkaction.c | 902 | ||||
-rw-r--r-- | gtk/gtkaction.h | 107 | ||||
-rw-r--r-- | gtk/gtkactiongroup.c | 356 | ||||
-rw-r--r-- | gtk/gtkactiongroup.h | 109 | ||||
-rw-r--r-- | gtk/gtkmenumerge.c | 1652 | ||||
-rw-r--r-- | gtk/gtkmenumerge.h | 109 | ||||
-rw-r--r-- | gtk/gtkradioaction.c | 240 | ||||
-rw-r--r-- | gtk/gtkradioaction.h | 73 | ||||
-rw-r--r-- | gtk/gtktoggleaction.c | 246 | ||||
-rw-r--r-- | gtk/gtktoggleaction.h | 76 | ||||
-rw-r--r-- | gtk/gtktoggleactionprivate.h | 42 | ||||
-rw-r--r-- | tests/Makefile.am | 19 | ||||
-rw-r--r-- | tests/merge-1.ui | 20 | ||||
-rw-r--r-- | tests/merge-2.ui | 27 | ||||
-rw-r--r-- | tests/merge-3.ui | 23 | ||||
-rw-r--r-- | tests/testactions.c | 268 | ||||
-rw-r--r-- | tests/testmerge.c | 419 |
25 files changed, 4930 insertions, 118 deletions
@@ -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 \ @@ -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. <popups>) 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; +} |