[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.
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:
a menu item, which is what the user wants to select, e.g., Save
a menu, which acts as a container for the menu items,
a menubar, which is a container for each of the individual menus.
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 two 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;
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_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_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:
file_menu := gtk_menu_new();
{ Don't need to show menus. }
{ 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_append(GTK_MENU(file_menu), open_item);
gtk_menu_append(GTK_MENU(file_menu), save_item);
gtk_menu_append(GTK_MENU(file_menu), quit_item);
{ Attach the callback functions to the activate signal: }
gtk_signal_connect(GTK_OBJECT(open_items), 'activate',
GTK_SIGNAL_FUNC(@menuitem_response),
pchar('file.open'));
gtk_signal_connect(GTK_OBJECT(save_items), 'activate',
GTK_SIGNAL_FUNC(@menuitem_response),
pchar('file.save'));
{ We can attach the Quit menu item to our exit function.}
gtk_signal_connect(GTK_OBJECT(quit_items), 'activate',
GTK_SIGNAL_FUNC(@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 function
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_bar_append( menu_bar :
pGtkMenuBar ; menu_item : pGtkWidget );
which in our case looks like this:
gtk_menu_bar_append(GTK_MENU_BAR(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:
- Create a new menu using gtk_menu_new().
- Use multiple calls to gtk_menu_item_new() for each item you wish to have on your
menu.
- Use gtk_menu_append() to put each of these new items on to the menu.
- Create a menu item using gtk_menu_item_new(). This will be the root of the menu,
the text appearing here will be on the menubar itself.
- Use gtk_menu_item_set_submenu() to attach the menu to the root menu item (the
one created in the above step).
- Create a new menubar using gtk_menu_bar_new(). This step only needs to be done
once when creating a series of menus on one menu bar.
- Use gtk_menu_bar_append() to put the root menu onto the menubar.
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:
- Create an event handling function. It needs to have the prototype
FUNCTION handler( widget : pGtkWidget ;
event : pGdkEvent ; data : gpointer ) :
gint; cdecl;
and it will use the event to find out where to pop up the menu.
- In the event handler, if the event is a mouse button press, treat event as a button event (which it is) and
use it as shown in the sample code to pass information to gtk_menu_popup().
- Bind that event handler to a widget with
gtk_signal_connect(GTK_OBJECT(widget),
'event',
GTK_SIGNAL_FUNC(@handler), menu);
where widget is the widget you are binding to, handler is the handling function, and menu is a menu created with
gtk_menu_new(). This can be a menu which is also posted by a menu bar, as shown in
the sample code.
[Previous] [Contents] [Next]
The following example should help to clarify the descriptions of the previous section. This little screen-shot admittedly
doesn't look that exciting, but all the basics are covered (including reacting to a right-click of the mouse). Compile
and run the program yourself in order to see more of what it does.
PROGRAM menus;
USES gtk, gdk, glib, sysutils;
{ ------------------------------button_press---------------------------- }
FUNCTION button_press( widget : pGtkWidget ;
event : pGdkEvent ; data : gpointer ) :
gint; cdecl;
{ Respond to a button-press by posting a menu passed in as the data argument }
VAR
bevent : pGdkEventButton;
BEGIN
bevent := pGdkEventButton(event);
{ Check whether we had a right-click }
IF (bevent^.button = 3) THEN
BEGIN
gtk_menu_popup(GTK_MENU(data), NIL,
NIL, NIL, NIL,
bevent^.button, bevent^.time);
{ Tell calling code that we have handled this event; the buck stops here.}
button_press := 1; { i.e. true }
END;
{ Tell calling code that we have not handled this event; pass it on. }
button_press := 0; { i.e. false }
END;
{ ------------------------------------------button_press----------------------------- }
{ ------------------------------------menuitem_response------------------------- }
PROCEDURE menuitem_response( Widget : pGtkWidget ;
data : pgpointer ); cdecl;
{ Print a string when a menu item is selected }
VAR
TheLabel : pGtkWidget;
LabelText : pchar;
S : AnsiString;
BEGIN
{ Retrieve the label from the clicked menu item }
TheLabel :=
g_list_nth_data(gtk_container_children(GTK_CONTAINER(Widget)),
0);
gtk_label_get(gtk_label(TheLabel), @LabelText);
S := 'Chosen menu : ' + Strpas(LabelText);
gtk_label_set_text(GTK_LABEL(data), pchar(S));
END;
{ ----------------------------menuitem_response---------------------------- }
{ ---------------------------Global Variables---------------------------- }
VAR
window, menu, menu_bar, root_menu, menu_items, vbox, button, echoLabel :
pGtkWidget;
i : Integer;
label_str : String;
l_str_as_char_ptr : pchar;
{ -----------------------------------Main Program------------------------------ }
BEGIN
gtk_init(@argc, @argv); { Initialise GTK }
window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
{ Create a new window }
gtk_widget_set_usize(GTK_WIDGET(window), 200, 150);
gtk_window_set_title(GTK_WINDOW(window), 'GTK Menu Test');
gtk_signal_connect(GTK_OBJECT(window), 'delete_event',
GTK_SIGNAL_FUNC(@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();
{ A label to give us feedback on button presses }
echoLabel := gtk_label_new('Chosen item: -');
gtk_widget_show(echoLabel);
{ Next we make a little loop that makes three menu-entries for our test menu.
Notice the call to
gtk_menu_append(). Here we are adding a list of menu items to our menu. }
FOR i := 1 TO 3 DO
BEGIN
{ Copy the names to the buffer }
label_str := 'Menu item << ' +
IntToStr(i) + ' >>';
l_str_as_char_ptr := StrAlloc(Length(label_str) + 1);
StrPCopy(l_str_as_char_ptr, label_str);
{ Create a new menu-item with a name: }
menu_items := gtk_menu_item_new_with_label(l_str_as_char_ptr);
{ ...and add it to the menu: }
gtk_menu_append(GTK_MENU(menu), menu_items);
{ Do something interesting when the menuitem is selected: }
gtk_signal_connect(GTK_OBJECT(menu_items), 'activate',
GTK_SIGNAL_FUNC(@menuitem_response), echoLabel );
gtk_widget_show(menu_items); { Show the widget }
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_box_pack_start(GTK_BOX(vbox), echoLabel, true,
true, 10); { Add our label }
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('Right-click Here');
gtk_signal_connect(GTK_OBJECT(button), 'event',
GTK_SIGNAL_FUNC(@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_bar_append(GTK_MENU_BAR(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.
{ ----------------------------------------Main Program----------------------------- }
You may also set a menu item to be insensitive and, using an accelerator table, bind keys to menu functions.
[Previous] [Contents] [Next]
Now that we've shown you creation of menus the hard way, here's how you do it using the
gtk_item_factory() calls.
[Previous] [Contents] [Next]
Here is an example using the GTK item factory.
PROGRAM ifmenu;
USES gtk, gdk, glib, sysutils;
{ ----------------------------print_hello--------------------------- }
PROCEDURE print_hello( Data : gpointer ;
Action : guint ; Widget : pGtkWidget );
cdecl;
{ Obligatory basic callback. }
BEGIN
writeln('Hello, World!');
END;
{ ----------------------------print_hello--------------------------- }
{ This is the GtkItemFactoryEntry structure used to generate new menus.
Item 1: The menu path. The letter after the underscore indicates an accelerator key once the menu is open.
Item 2: The accelerator key for the entry
Item 3: The callback function.
Item 4: The callback action. This changes the parameters with which the function is called. The default is 0.
Item 5: The item type, used to define what kind of an item it is.
Here are the possible values:
NIL -> "<Item>"
"" -> "<Item>"
"<Title>" -> create a title item
"<Item>" -> create a simple item
"<CheckItem>" -> create a check item
"<ToggleItem>" -> create a toggle item
"<RadioItem>" -> create a radio item
"<path>" -> path of a radio item to link against
"<Separator>" -> create a separator
"<Branch>" -> create an item to hold sub items (optional)
"<LastBranch>" -> create a right justified branch.
}
{ -------------------------Global Variables------------------------------ }
VAR
window, menu_bar, main_vbox : pGtkWidget;
TYPE
FactCB = tGtkItemFactoryCallback;
CONST
num_menu_items = 11;
menu_items : ARRAY[1..num_menu_items] OF
tGtkItemFactoryEntry = (
(path : '/_File'; accelerator : NIL;
callback : NIL; callback_action : 0;
item_type : '<Branch>'),
(path : '/File/_New'; accelerator : '<ctrl>N';
callback : FactCB(@print_hello); callback_action : 0;
item_type : NIL),
(path : '/File/_Open'; accelerator : '<ctrl>O';
callback : FactCB(@print_hello); callback_action : 0;
item_type : NIL),
(path : '/File/_Save'; accelerator : '<ctrl>S';
callback : FactCB(@print_hello); callback_action : 0;
item_type : NIL),
(path : '/File/Save _As'; accelerator : NIL;
callback : NIL; callback_action : 0;
item_type : NIL),
(path : '/File/sep1'; accelerator : NIL;
callback : NIL; callback_action : 0;
item_type : '<Separator>'),
(path : '/File/Quit'; accelerator : '<ctrl>Q';
callback : FactCB(@gtk_main_quit); callback_action : 0;
item_type : NIL),
(path : '/_Options'; accelerator : NIL;
callback : NIL; callback_action : 0;
item_type : '<Branch>'),
(path : '/Options/Test'; accelerator : NIL;
callback : NIL; callback_action : 0;
item_type : NIL),
(path : '/_Help'; accelerator : NIL;
callback : NIL; callback_action : 0;
item_type : '<LastBranch>'),
(path : '/_Help/About'; accelerator : NIL;
callback : NIL; callback_action : 0;
item_type : NIL)
);
{ --------------------------------make_menu-------------------------------- }
PROCEDURE make_menu;
VAR
it_factory : pGtkItemFactory;
it_accel : pGtkAccelGroup;
BEGIN
it_accel := gtk_accel_group_new();
{ 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. }
it_factory := gtk_item_factory_new(GTK_MENU_BAR_TYPE,
'<main>', it_accel);
{ 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 menu items.}
gtk_item_factory_create_items(it_factory, num_menu_items, @menu_items,
NIL);
{ Add the new accelerator group to the window. }
gtk_window_add_accel_group(GTK_WINDOW(window), it_accel);
menu_bar := gtk_item_factory_get_widget(it_factory, '<main>');
END;
{ -------------------------------------------make_menu-------------------------------- }
{ -----------------------------------Main Program------------------------------ }
BEGIN
gtk_init(@argc, @argv); { Initialise GTK }
window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
{ Create a new window }
gtk_widget_set_usize(GTK_WIDGET(window), 300, 200);
gtk_window_set_title(GTK_WINDOW(window), 'Item Factory');
gtk_signal_connect(GTK_OBJECT(window), 'destroy',
GTK_SIGNAL_FUNC(@gtk_main_quit), NIL);
main_vbox := gtk_vbox_new(FALSE, 1);
gtk_container_add(GTK_CONTAINER(window), main_vbox);
gtk_widget_show(main_vbox);
make_menu();
gtk_box_pack_start(GTK_BOX(main_vbox), menu_bar, FALSE,
TRUE, 0);
gtk_widget_show(menu_bar);
gtk_widget_show(window);
gtk_main();
END.
{ --------------------------------Main Program--------------------------------- }
[Previous] [Contents] [Next]