[Previous] [Contents] [Next]

13 Menu Widget

There are two ways to create menus: there's the easy way, and there's the hard way. Both have their uses, but you can usually use the Itemfactory (the easy way). The "hard" way is to create all the menus using the calls directly. The easy way is to use the gtk_item_factory() calls. This is much simpler, but there are advantages and disadvantages to each approach.

The Itemfactory is much easier to use, and to add new menus to, although writing a few wrapper functions to create menus using the manual method could go a long way towards usability. With the Itemfactory, it is not possible to add images or the character '/' to the menus.

13.1 Manual Menu Creation

In the true tradition of teaching, we'll show you the hard way first. :)

There are three widgets that go into making a menubar and submenus:

This is slightly complicated by the fact that menu item widgets are used for two different things. They are both the widgets that are packed into the menu, and the widget that is packed into the menubar, which, when selected, activates the menu.

Let's look at the functions that are used to create menus and menubars. This first function is used to create a new menubar.

function gtk_menu_bar_new () : PGtkWidget;

This rather self explanatory function creates a new menubar. You use gtk_container_add() to pack this into a window, or the box_pack functions to pack it into a box - the same as buttons.

function gtk_menu_new () : PGtkWidget;

This function returns a pointer to a new menu; it is never actually shown (with gtk_widget_show()), it is just a container for the menu items. I hope this will become more clear when you look at the example below.

The next three calls are used to create menu items that are packed into the menu (and menubar).

function gtk_menu_item_new () : PGtkWidget;

function gtk_menu_item_new_with_label (a_label : pchar) : PGtkWidget;

function gtk_menu_item_new_with_mnemonic (a_label : pchar) : PGtkWidget;

These calls are used to create the menu items that are to be displayed. Remember to differentiate between a "menu" as created with gtk_menu_new() and a "menu item" as created by the gtk_menu_item_new() functions. The menu item will be an actual button with an associated action, whereas a menu will be a container holding menu items.

The gtk_menu_new_with_label() and gtk_menu_item_new() functions are just as you'd expect after reading about the buttons. One creates a new menu item with a label already packed into it, and the other just creates a blank menu item.

Once you've created a menu item you have to put it into a menu. This is done using the function gtk_menu_shell_append(). In order to capture when the item is selected by the user, we need to connect to the activate signal in the usual way. So, if we wanted to create a standard File menu, with the options Open, Save, and Quit, the code would look something like:

   { Don't need to show menus. }
   file_menu := gtk_menu_new();

   { Create the menu items }
   open_item := gtk_menu_item_new_with_label('Open');
   save_item := gtk_menu_item_new_with_label('Save');
   quit_item := gtk_menu_item_new_with_label('Quit');

   { Add them to the menu }
   gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), open_item);
   gtk_menu_shell_append(GTK_MENU_MENU_SHELL(file_menu), save_item);
   gtk_menu_shell_append(GTK_MENU_MENU_SHELL(file_menu), quit_item);

   { Attach the callback functions to the activate signal }
   g_signal_connect_swapped(open_items, 'activate',
                            G_CALLBACK(@menuitem_response),
                            pchar('file.open'));
   g_signal_connect_swapped(save_items, 'activate',
                            G_CALLBACK(@menuitem_response),
                            pchar('file.save'));

   { We can attach the Quit menu item to our exit function.}
   g_signal_connect_swapped(quit_items, 'activate',
                            G_CALLBACK(@destroy),
                            pchar('file.quit'));

   { We do need to show menu items. }
   gtk_widget_show(open_item);
   gtk_widget_show(save_item);
   gtk_widget_show(quit_item);

At this point we have our menu. Now we need to create a menubar and a menu item for the File entry, to which we add our menu. The code looks like this:

   menu_bar := gtk_menu_bar_new();
   gtk_container_add(GTK_CONTAINER(window), menu_bar);
   gtk_widget_show(menu_bar);

   file_item := gtk_menu_item_new_with_label('File');
   gtk_widget_show(file_item);

Now we need to associate the menu with file_item. This is done with the procedure

procedure gtk_menu_item_set_submenu (menu_item : PGtkMenuItem;
                                     submenu : PGtkWidget);

So, our example would continue with

   gtk_menu_item_set_submenu(GTK_MENU_ITEM(file_item), file_menu);

All that is left to do is to add the menu to the menubar, which is accomplished using the procedure:

procedure gtk_menu_shell_append (menu_shell : PGtkMenuShell;
                                 menu_item : PGtkWidget);

which in our case looks like this:

   gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), file_item);

If we wanted the menu right justified on the menubar, such as help menus often are, we can use the following procedure (again on file_item in the current example) before attaching it to the menubar.

procedure gtk_menu_item_right_justify (menu_item : PGtkMenuItem);

Here is a summary of the steps needed to create a menu bar with menus attached:

Creating a popup menu is nearly the same. The difference is that the menu is not posted "automatically" by a menubar, but explicitly by calling the function gtk_menu_popup() from a button-press event, for example. Take these steps:


13.2 Manual Menu Example

That should about do it. Let's take a look at an example to help clarify.

program MenuExample;

uses gtk2, gdk2, glib2, sysutils;

{ Respond to a button-press by posting a menu passed in as widget.

  Note that the "widget" argument is the menu being posted, NOT
  the button that was pressed. }

function button_press (widget : PGtkWidget;
                       event : PGdkEvent) : gboolean; cdecl;
var
   bevent : PGdkEventButton;
begin	        
   if (event^._type = GDK_BUTTON_PRESS) then
   begin
      bevent := PGdkEventButton(event);
      gtk_menu_popup(GTK_MENU(widget), nil, nil, nil, nil,
                     bevent^.button, bevent^.time);
      { Tell calling code that we have handled this event; the buck
        stops here.}
      button_press := true;
   end
   else
      { Tell calling code that we have not handled this event; pass it on. }
      button_press := false;
end;

{ Print a string when a menu item is selected }

procedure menuitem_response (a_string : pchar); cdecl;
begin
   writeln(a_string);
end;

var
   window, menu, menu_bar, root_menu, menu_items, vbox, button : PGtkWidget;
   i : Integer;
   buf : String; 
   buf_as_char_ptr : pChar;

begin
   gtk_init(@argc, @argv);
   
   { Create a new window }
   window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
   gtk_widget_set_size_request(GTK_WIDGET(window), 200, 100);
   gtk_window_set_title(GTK_WINDOW(window), 'GTK Menu Test');
   g_signal_connect(window, 'delete-event',
                    G_CALLBACK(@gtk_main_quit), nil);

   { Init the menu-widget, and remember -- never gtk_show_widget()
     the menu widget!!  This is the menu that holds the menu items,
     the one that will pop up when you click on the "Root Menu" in the app }
   menu := gtk_menu_new();
   
   { Next we make a little loop that makes three menu-entries for "test menu".
     Notice the call to gtk_menu_shell_append.  Here we are adding a list of
     menu items to our menu.  Normally, we'd also catch the "clicked"
     signal on each of the menu items and setup a callback for it,
     but it's omitted here to save space.}
   for i := 0 to 2 do
   begin
      { Copy the names to the buffer }
      buf := 'Test-undermenu - ' + IntToStr(i);  //!!!
      buf_as_char_ptr := StrAlloc(Length(buf) + 1);	
      StrPCopy(buf_as_char_ptr, buf); 

      { Create a new menu-item with a name... }
      menu_items := gtk_menu_item_new_with_label(buf_as_char_ptr); 

      { ...and add it to the menu. }				     
      gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_items);

      { Do something interesting when the menuitem is selected }
      g_signal_connect_swapped(menu_items, 'activate',
                               G_CALLBACK(@menuitem_response),
                               g_strdup(buf_as_char_ptr));

      { Show the widget }
      gtk_widget_show(menu_items);
   end;

   { This is the root menu, and will be the label displayed
     on the menu bar.  There won't be a signal handler attached,
     as it only pops up the rest of the menu when pressed. }
   root_menu := gtk_menu_item_new_with_label('Root Menu');
   
   gtk_widget_show(root_menu);

   { Now we specify that we want our newly created "menu" to be the menu
     for the "root menu" }
   gtk_menu_item_set_submenu(GTK_MENU_ITEM(root_menu), menu);

   { A vbox to put a menu, a label and a button in:}
   vbox := gtk_vbox_new(false, 0);
   gtk_container_add(GTK_CONTAINER(window), vbox);
   gtk_widget_show(vbox);

   { Create a menu-bar to hold the menus and add it to our main window }
   menu_bar := gtk_menu_bar_new();
   gtk_box_pack_start(GTK_BOX(vbox), menu_bar, false, false, 2);
   gtk_widget_show(menu_bar);

   { Create a button to which to attach menu as a popup }
   button := gtk_button_new_with_label('press me');
   g_signal_connect_swapped(button, 'event',
                            G_CALLBACK(@button_press), menu);
   gtk_box_pack_end(GTK_BOX(vbox), button, true, true, 2);
   gtk_widget_show(button);

   { And finally we append the menu-item to the menu-bar -- this is
     the "root" menu-item I have been raving about =) }
   gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), root_menu);
   
   { Always display the window as the last step so it all splashes
     on the screen at once.}
   gtk_widget_show(window);
   
   gtk_main();
end.

You may also set a menu item to be insensitive and, using an accelerator table, bind keys to menu functions.


13.3 Using ItemFactory

Now that we've shown you creation of menus the hard way, here's how you do it using the gtk_item_factory() calls.

ItemFactory creates a menu out of an array of ItemFactory entries. This means you can define your menu in its simplest form and then create the menu/menubar widgets with a minimum of function calls.

13.3.1 ItemFactory entries

At the core of ItemFactory is the ItemFactoryEntry. This structure defines one menu item, and when an array of these entries is defined a whole menu is formed. The ItemFactory entry struct definition looks like this:

type
   TGtkItemFactoryEntry = record
                             path : pgchar;
                             accelerator : pgchar;

                             callback : GtkItemFactoryCallback;
                             callback_action : guint;

                             item_type : pgchar;
                          end;

Each field defines part of the menu item.

path is a string which defines both the name and the path of a menu item, for example, "/File/Open" would be the name of a menu item which would come under the ItemFactory entry with path "/File". Note however that "/File/Open" would be displayed in the File menu as "Open". Also note since the forward slashes are used to define the path of the menu, they cannot be used as part of the name. A letter preceded by an underscore indicates an accelerator (shortcut) key once the menu is open.

accelerator is a string that indicates a key combination that can be used as a shortcut to that menu item. The string can be made up of either a single character, or a combination of modifier keys with a single character. It is case insensitive.

The available modifier keys are:

'<ALT>'                            - alt
'<CTL>' or '<CTRL>' or '<CONTROL>' - control
'<MOD1>' to '<MOD5>'               - modn
'<SHFT>' or '<SHIFT>'              - shift

Examples:

'<ConTroL>a'
'<SHFT><ALT><CONTROL>X'

callback is the function that is called when the menu item emits the "activate" signal. The form of the callback is described in the Callback Description section.

item_type is a string that defines what type of widget is packed into the menu items container. It can be:

nil or '' or '<Item>'  - create a simple item
'<Title>'              - create a title item
'<CheckItem>'          - create a check item
'<ToggleItem>'         - create a toggle item
'<RadioItem>'          - create a (root) radio item
'Path'                 - create a sister radio item
'<Tearoff>'            - create a tearoff
'<Separator>'          - create a separator
'<Branch>'             - create an item to hold submenus (optional)
'<LastBranch>'         - create a right justified branch
'<StockItem>'          - create a simple item with a stock image. 
                         see gtkstock.h for builtin stock items

Note that <LastBranch> is only useful for one submenu of a menubar.

Callback Description

The callback for an ItemFactory entry can take two forms. If callback_action is zero, it is of the following form:

procedure callback ();

otherwise it is of the form:

procedure callback (callback_data : gpointer,
                    callback_action : guint,
                    widget :PGtkWidget);

callback_data is a pointer to an arbitrary piece of data and is set during the call to gtk_item_factory_create_items().

callback_action is the same value as callback_action in the ItemFactory entry.

widget is a pointer to a menu item widget (described in (Manual Menu Creation).

ItemFactory entry examples

Creating a simple menu item:

var
  entry : TGtkItemFactoryEntry = (path:'/_File/_Open...';
                                  accelerator:'<CTRL>O';
                                  callback:TGtkItemFactoryCallback(@print_hello);
				  callback_action:0;
                                  item_type:'<Item>'};

This will define a new simple menu entry "/File/Open" (displayed as "Open"), under the menu entry "/File". It has the accelerator (shortcut) control+'O' that when clicked calls the procedure print_hello(). print_hello() is of the form print_hello() since the callback_action field is zero. When displayed the 'O' in "Open" will be underlined and if the menu item is visible on the screen pressing 'O' will activate the item. Note that "File/_Open" could also have been used as the path instead of "/_File/_Open".

Creating an entry with a more complex callback:

var
  entry : TGtkItemFactoryEntry = (path:'/_View/Display _FPS';
                                  accelerator:nil;
                                  callback:TGtkItemFactoryCallback(@print_state);
				  callback_action:7;
                                  item_type:'<CheckItem>');

This defines a new menu item displayed as "Display FPS" which is under the menu item "View". When clicked the function print_state() will be called. Since callback_action is not zero print_state() is of the form:

procedure print_state (callback_data : gpointer;
                       callback_action : guint;
                       widget : PGtkWidget);

with callback_action equal to 7.


13.4 Item Factory Example

Here is an example using the GTK item factory.

program ItemFactory;

uses gtk2, gdk2, glib2, sysutils;

{ Obligatory basic callback. }

procedure print_hello (Widget : PGtkWidget; data : gpointer); cdecl;
begin
   writeln('Hello, World!');
end;

{ For the check button }

procedure print_toggle (callback_data : gpointer;
                        callback_action : guint;
                        menu_item : PGtkWidget); cdecl;
begin
    writeln('Check button state - ',
            gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item)));
end;

{ For the radio buttons }

procedure print_selected (callback_data : gpointer;
                          callback_action : guint;
                          menu_item : PGtkWidget); cdecl;
begin
   if gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item)) then
      writeln('Radio button ', callback_action, ' selected');
end;

{ Our menu, an array of GtkItemFactoryEntry structures that defines each
  menu item }

const
   nmenu_items = 16;
   menu_items : array [1..nmenu_items] of TGtkItemFactoryEntry =
   ((path:'/_File'; accelerator:nil; callback:nil; callback_action:0;
     item_type:'<Branch>'),                                    
    (path:'/File/_New'; accelerator:'<ctrl>N';
     callback:TGtkItemFactoryCallback(@print_hello);
     callback_action:0; item_type:nil),
    (path:'/File/_Open'; accelerator:'<ctrl>O';
     callback:TGtkItemFactoryCallback(@print_hello);
     callback_action:0; item_type:nil),
    (path:'/File/_Save'; accelerator:'<ctrl>S';
     callback:TGtkItemFactoryCallback(@print_hello);
     callback_action:0; item_type:nil),
    (path:'/File/Save _As'; accelerator:nil;
     callback:nil; callback_action:0; item_type:'<Item>'),
    (path:'/File/sep1'; accelerator:nil;
     callback:nil; callback_action:0; item_type:'<Separator>'),
    (path:'/File/Quit'; accelerator:'<ctrl>Q';
     callback:TGtkItemFactoryCallback(@gtk_main_quit);
     callback_action:0; item_type:nil),
    (path:'/_Options'; accelerator:nil;
     callback:nil; callback_action:0; item_type:'<Branch>'),
    (path:'/Options/tear'; accelerator:nil; callback:nil; callback_action:0;
     item_type:'<Tearoff>'),
    (path:'/Options/Check'; accelerator:nil;
     callback:TGtkItemFactoryCallback(@print_toggle);
     callback_action:1; item_type:'<CheckItem>'),
    (path:'/Options/sep'; accelerator:nil; callback:nil; callback_action:0;
     item_type:'<Separator>'),
    (path:'/Options/Rad1'; accelerator:nil;
     callback:TGtkItemFactoryCallback(@print_selected);
     callback_action:1; item_type:'<RadioItem>'),
    (path:'/Options/Rad2'; accelerator:nil;
     callback:TGtkItemFactoryCallback(@print_selected);
     callback_action:2; item_type:'/Options/Rad1'),
    (path:'/Options/Rad3'; accelerator:nil;
     callback:TGtkItemFactoryCallback(@print_selected);
     callback_action:3; item_type:'/Options/Rad1'),
    (path:'/_Help'; accelerator:nil;
     callback:nil; callback_action:0; item_type:'<LastBranch>'),
    (path:'/_Help/About'; accelerator:nil;
     callback:nil; callback_action:0; item_type:'<Item>'));

{ Returns a menubar widget made from the above menu }

function get_menubar_menu (window : PGtkWidget) : PGtkWidget;
var
   item_factory : PGtkItemFactory;
   accel_group  : PGtkAccelGroup;
begin
   { Make an accelerator group (shortcut keys) }
   accel_group := gtk_accel_group_new();

   { Make an ItemFactory (that makes a menubar) }

   { This function initializes the item factory.
     Param 1: The type of menu - can be GTK_TYPE_MENU_BAR, GTK_TYPE_MENU,
              or GTK_TYPE_OPTION_MENU.
     Param 2: The path of the menu.
     Param 3: A pointer to a gtk_accel_group.  The item factory sets up
              the accelerator table while generating menus.
   }

   item_factory := gtk_item_factory_new(GTK_TYPE_MENU_BAR, '<main>',
                                        accel_group);

   { This function generates the menu items. Pass the item factory,
     the number of items in the array, the array itself, and any
     callback data for the the menu items. }
   gtk_item_factory_create_items(item_factory, nmenu_items, @menu_items, nil);

   { Attach the new accelerator group to the window. }
   gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
   
   { Finally, return the actual menu bar created by the item factory. }
   get_menubar_menu := gtk_item_factory_get_widget(item_factory, '<main>');
end;

{ Popup the menu when the popup button is pressed }

function popup_cb (widget : PGtkWidget; event : PGdkEvent;
                   menu : PGtkWidget) : gboolean; cdecl;
var
   bevent : PGdkEventButton;
begin
   bevent := PGdkEventButton(event);
   { Only take button presses }
   if (event^._type <> GDK_BUTTON_PRESS) then
      popup_cb := false
   else
   begin   
      { Show the menu }
      gtk_menu_popup(GTK_MENU(menu), nil, nil, nil, nil,
                     bevent^.button, bevent^.time);
  
      popup_cb := true;
   end;
end;

{ Same as with get_menubar_menu() but just return a button with a signal to
  call a popup menu }

function get_popup_menu () : PGtkWidget;
var
   item_factory : PGtkItemFactory; 
   button, menu : PGtkWidget;
begin  
   { Same as before but don't bother with the accelerators }
   item_factory := gtk_item_factory_new(GTK_TYPE_MENU, '<main>', nil);
   gtk_item_factory_create_items(item_factory, nmenu_items, @menu_items, nil);
   menu := gtk_item_factory_get_widget(item_factory, '<main>');
  
   { Make a button to activate the popup menu }
   button := gtk_button_new_with_label('Popup');
   { Make the menu popup when clicked }
   g_signal_connect(button, 'event',
                    G_CALLBACK(@popup_cb), gpointer(menu));

   get_popup_menu := button;
end;

{ Same again but return an option menu }

function get_option_menu () : PGtkWidget;
var
   item_factory : PGtkItemFactory; 
   option_menu : PGtkWidget;
begin
   { Same again, not bothering with the accelerators }
   item_factory := gtk_item_factory_new(GTK_TYPE_OPTION_MENU, '<main>',
                                        nil);
   gtk_item_factory_create_items(item_factory, nmenu_items, @menu_items, nil);
   option_menu := gtk_item_factory_get_widget(item_factory, '<main>');

   get_option_menu := option_menu;
end;

{ You have to start somewhere }

var
   window, mainvbox, menubar, option_menu, popup_button : pGtkWidget;

begin
   { Initialize GTK }
   gtk_init(@argc, @argv); 

   { Make a window }
   window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
   g_signal_connect(window, 'destroy',
                    G_CALLBACK(@gtk_main_quit), nil);
   gtk_window_set_title(GTK_WINDOW(window), 'Item Factory');
   gtk_widget_set_size_request(GTK_WIDGET(window), 300, 200);

   { Make a vbox to put the three menus in }
   mainvbox := gtk_vbox_new(false, 1);
   gtk_container_set_border_width(GTK_CONTAINER(mainvbox), 1);
   gtk_container_add(GTK_CONTAINER(window), mainvbox);
 
   { Get the three types of menu }

   { Note: all three menus are separately created, so they are not the
     same menu }
   menubar := get_menubar_menu(window);
   popup_button := get_popup_menu();
   option_menu := get_option_menu();
   
   { Pack it all together }
   gtk_box_pack_start(GTK_BOX(mainvbox), menubar, false, true, 0);
   gtk_box_pack_end(GTK_BOX(mainvbox), popup_button, false, true, 0);
   gtk_box_pack_end(GTK_BOX(mainvbox), option_menu, false, true, 0);
   
   { Show the widgets }
   gtk_widget_show_all(window);

   { Finished! }
   gtk_main();
end.

[Previous] [Contents] [Next]