What is GtkUIManager?
GtkUIManager in my opinion, is slightly misnamed. It is a object that greatly simplifies creation and handling of Menu and Toolbar widgets; allowing the programmer to specify a widget layout in XML and actions to be performed by the widgets in simple C structs, then automatically builds and manages the widgets and actions you specified. The misnaming comes from the fact that it only works with Menus and Toolbars, and therefore is NOT a decent replacement for a libglade based UI layout.
GtkUIManager also facilitates "menu-merging", where items can be dynamically added or removed from the menu/toolbar by the programmer, which is a feature previously only BonoboUI had, but GtkUIManager has the advantage of being far simpler to program.
How does it work?
Key to understanding GtkUIManager is realizing that a menu and toolbar are different graphical representations of the same set of underlying actions, in essence two different views of the same model. The underlying actions, such as "open a new file", "copy some text", or "perform a search", are modeled by an object called GtkAction. This object holds the callback function which implements the action in C code, and some meta-data such as a display name, icon, tool-tip, and an internal "lookup name".
When you feed in the view layout from an XML file, and feed in the actions from a GtkActionGroup, the UI manager does the boring work of creating and packing the correct GTK+ widgets. When it has a new widget for you to place in your application, it emits the signal "add_widget" and passes you the new widget. However for the layout and actions to correspond correctly, you need to give it a special "lookup name" which the UI manager uses to match action to widget.
There is one more object you need to know about: the GtkActionGroup, which is simply a way of organizing actions into logical groupings. Any number of action groups may be created and inserted into a UI manager, however there is no way to add single actions, so at least one group must be made. If you want to use the merging feature of GtkUIManager, you'll need to keep the actions to be merged in or out in a separate action group.
I'm Daft. Give Me the Play-by-Play.
1. Define the UI in XML.
GtkUIManager uses tags defined by the DTD in the API documentation, but the examples should be sufficient to figure out what your doing. Here is our example:
<ui> <menubar name="MainMenu"> <menu name="FileMenu" action="FileMenuAction"> <menuitem name="Quit" action="QuitAction" /> <placeholder name="FileMenuAdditions" /> </menu> <menu name="CharacterMenu" action="CharacterMenuAction"> <menuitem name="Clear" action="ClearAction"/> <menuitem name="Lookup" action="LookupAction"/> </menu> </menubar> <toolbar name="MainToolbar" action="MainMenuBarAction"> <placeholder name="ToolItems"> <separator/> <toolitem name="Clear" action="ClearAction"/> <toolitem name="Lookup" action="LookupAction"/> <separator/> </placeholder> </toolbar> </ui>
2. Fill in GtkActionEntries
GTK+ provides a convenience structure called GtkActionEntry, which just holds all the parameters that are passed to the GtkAction's contructor. If you make a array of them, you can use a convenience function to build a GtkActionGroup. GtkUIManager will know how to match up the entries to the XML above by the "action" field in the XML, and by the "name" field in the entry. For example the top-level file menu points to action="FileMenuAction" and entry struct has the name field as "FileMenuAction". The callbacks listed here will be defined later.
static GtkActionEntry entries[] = { { "FileMenuAction", NULL, "_File" }, /* name, stock id, label */ { "CharacterMenuAction", NULL, "_Character" }, { "LookupAction", GTK_STOCK_FIND, /* name, stock id */ "_Lookup", "<control>L", /* label, accelerator */ "Look-up the character drawn", /* tooltip */ G_CALLBACK (lookup_character_action) }, { "ClearAction", GTK_STOCK_OPEN, "_Clear","<control>C", "Clear the drawing area", G_CALLBACK (clear_character_action) }, { "QuitAction", GTK_STOCK_QUIT, "_Quit", "<control>Q", "Quit", G_CALLBACK (quit_action) } }; static guint n_entries = G_N_ELEMENTS (entries);
3. Create new GtkActionGroup and add entries
- Since we made the nice entry array, this part is trivial.
action_group = gtk_action_group_new ("TestActions"); gtk_action_group_add_actions (action_group, entries, n_entries, NULL);
4. Create new GtkUIManager and insert action group
menu_manager = gtk_ui_manager_new (); gtk_ui_manager_insert_action_group (menu_manager, action_group, 0);
5. Load UI XML into ui manager
- Make sure to read up on how to handle GErrors.
error = NULL; gtk_ui_manager_add_ui_from_file (menu_manager, "test-ui-manager-ui.xml", &error); if (error) { g_message ("building menus failed: %s", error->message); g_error_free (error); }
6. Define callbacks declared in GtkActionEntries
static void lookup_character_action () { g_print ("lookup\n"); } static void clear_character_action () { g_print ("clear\n"); } static void quit_action () { gtk_main_quit(); }
7. Make a new vertical packing box to place the menu and toolbar (and the rest of the UI) in.
menu_box = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER(window), menu_box);
8. Add the menu and the toolbar to the vertical packing box.
menubar = gtk_ui_manager_get_widget (menu_manager, "/MainMenu"); gtk_box_pack_start (GTK_BOX (menu_box), menubar, FALSE, FALSE, 0); toolbar = gtk_ui_manager_get_widget (menu_manager, "/MainToolbar"); gtk_box_pack_start (GTK_BOX (menu_box), toolbar, FALSE, FALSE, 0);
9. Enable the keyboard shortcuts
gtk_window_add_accel_group (GTK_WINDOW (window), gtk_ui_manager_get_accel_group (menu_manager));
Here is the complete listing
/* test-ui-mananager.c : Simple tutorial for GtkUIManager * Copyright (C) 2005 Ryan McDougall. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <gtk/gtk.h> /* Create callbacks that implement our Actions */ static void lookup_character_action () { g_print ("lookup\n"); } static void clear_character_action () { g_print ("clear\n"); } static void quit_action () { gtk_main_quit(); } /* Create a list of entries which are passed to the Action constructor. * This is a huge convenience over building Actions by hand. */ static GtkActionEntry entries[] = { { "FileMenuAction", NULL, "_File" }, /* name, stock id, label */ { "CharacterMenuAction", NULL, "_Character" }, { "LookupAction", GTK_STOCK_FIND, /* name, stock id */ "_Lookup", "<control>L", /* label, accelerator */ "Look-up the character drawn", /* tooltip */ G_CALLBACK (lookup_character_action) }, { "ClearAction", GTK_STOCK_OPEN, "_Clear","<control>C", "Clear the drawing area", G_CALLBACK (clear_character_action) }, { "QuitAction", GTK_STOCK_QUIT, "_Quit", "<control>Q", "Quit", G_CALLBACK (quit_action) } }; static guint n_entries = G_N_ELEMENTS (entries); /*---------------------------------------------------------------------------*/ int main (int argc, char** argv) { GtkWidget *window; /* The main window */ GtkWidget *menu_box; /* Packing box for the menu and toolbars */ GtkActionGroup *action_group; /* Packing group for our Actions */ GtkUIManager *menu_manager; /* The magic widget! */ GError *error; /* For reporting exceptions or errors */ GtkWidget *menubar; /* The actual menubar */ GtkWidget *toolbar; /* The actual toolbar */ /* Always the first step is to start up GTK+ itself */ gtk_init (&argc, &argv); /* Create our objects */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); menu_box = gtk_vbox_new (FALSE, 0); action_group = gtk_action_group_new ("TestActions"); gtk_action_group_set_translation_domain (action_group, "blah"); menu_manager = gtk_ui_manager_new (); /* Pack up our objects: * menu_box -> window * actions -> action_group * action_group -> menu_manager */ gtk_container_add (GTK_CONTAINER(window), menu_box); gtk_action_group_add_actions (action_group, entries, n_entries, NULL); gtk_ui_manager_insert_action_group (menu_manager, action_group, 0); /* Read in the UI from our XML file */ error = NULL; gtk_ui_manager_add_ui_from_file (menu_manager, "test-ui-manager-ui.xml", &error); if (error) { g_message ("building menus failed: %s", error->message); g_error_free (error); } /* Connect up important signals */ g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL); /* Get the menubar and the toolbar and put them in the vertical packing box */ menubar = gtk_ui_manager_get_widget (menu_manager, "/MainMenu"); gtk_box_pack_start (GTK_BOX (menu_box), menubar, FALSE, FALSE, 0); toolbar = gtk_ui_manager_get_widget (menu_manager, "/MainToolbar"); gtk_box_pack_start (GTK_BOX (menu_box), toolbar, FALSE, FALSE, 0); /* Make sure that the accelerators work */ gtk_window_add_accel_group (GTK_WINDOW (window), gtk_ui_manager_get_accel_group (menu_manager)); /* Show the window and run the main loop, we're done! */ gtk_widget_show_all (window); gtk_main (); return 0; }
Copyright RyanMcDougall (2005)
I'm even more Daft. In Python, please
Take the same xml file, with the following python code:
#! /usr/bin/python """ /* test-ui-mananager.py : Simple tutorial for GtkUIManager * Copyright (C) 2005 Ryan McDougall. * Ported to Python + GI + Gtk3 by Pietro Battiston, March 2013. * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ """ from gi.repository import Gtk # Create callbacks that implement our Actions def lookup_character_action(*args): print "lookup", args def clear_character_action(*args): print "clear", args def quit_action(*args): Gtk.main_quit() entries = [ ( "FileMenuAction", None, "_File" ), ( "CharacterMenuAction", None, "_Character" ), ( "LookupAction", Gtk.STOCK_FIND, "_Lookup", "<control>L", "Look-up the character drawn", lookup_character_action ), ( "ClearAction", Gtk.STOCK_OPEN, "_Clear", "<control>C", "Clear the drawing area", clear_character_action ), ( "QuitAction", Gtk.STOCK_QUIT, "_Quit", "<control>Q", "Quit", quit_action )] ################################################################################ def main(): # Create our objects window = Gtk.Window( Gtk.WindowType.TOPLEVEL ) menu_box = Gtk.Box( Gtk.Orientation.VERTICAL ) action_group = Gtk.ActionGroup( "TestActions" ) action_group.set_translation_domain( "blah" ) menu_manager = Gtk.UIManager() # Pack up our objects: # * menu_box -> window # * actions -> action_group # * action_group -> menu_manager window.add( menu_box ) action_group.add_actions( entries, None ) menu_manager.insert_action_group( action_group ) # Read in the UI from our XML file menu_manager.add_ui_from_file( "uimanager_tutorial.xml" ) # Connect up important signals window.connect( "destroy", Gtk.main_quit ) # Get the menubar and the toolbar and put them in the vertical packing box menubar = menu_manager.get_widget( "/MainMenu" ) menu_box.pack_start( menubar, False, False, 0 ) toolbar = menu_manager.get_widget( "/MainToolbar" ) menu_box.pack_start( toolbar, True, True, 0 ) # Make sure that the accelerators work window.add_accel_group( menu_manager.get_accel_group() ) # Show the window and run the main loop, we're done! window.show_all() Gtk.main() if __name__ == "__main__": main()