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()


CategoryDeveloperTutorial

Newcomers/UIManagerTutorial (last edited 2015-09-16 22:58:28 by CarlosSoriano)