11—
Menus
Different Types of Menus
There are several ways to create and utilize a menu from within your Perl/Tk application. Here are some examples of how you can use a menu-type widget:*
• Create File, Edit, and Help menus across the top of your application.
• Display a list of fonts from which the user can choose (the selected font can be marked with a checkmark).
• Display a list of editing commands that become available when the user right-clicks on another object (such as a listbox or entry widget) in your window.
You can build each of these different types of menus with the basic menu widget. The menu widget itself is a list of items that are displayed one item per line in a box. Each item can have an associated callback that is called when the menu item is invoked or selected. Unlike the other widgets we have seen so far, you cannot use any of the geometry managers on a menu. Instead, you must use a method called post to display your menu widget (post will be discussed later in this chapter).
Figure 11-1 shows the contents of a typical menu widget. It contains several items, a separator, and a few more items. Separators are useful for grouping together related commands and providing a visual break if one menu contains a number of commands.
* Typically, a menu contains commands that aren't used frequently, such as configuration options, File Open, File Close, Help, and so on. You would be wise to put frequently accessed commands in the window to provide easier access for the user.
0219-01.gif
Figure 11-1.
Simple menu widget with five items: Item1, Item2, Separator, Item3, and Item4
Menus are a great way to replace checkbuttons and radiobuttons. If you have five radiobuttons, you can place them on a menu and save a ton of screen space for more important widgets.
A menubutton widget is based on the menu widget and has a button that controls when the menu is displayed. When the button is pressed, the menu is displayed directly below the button. The button contains a text string that describes the items in the menu. A menubutton is the type of menu you'll use 90% of the time. Figure 11-2 shows a block diagram of a menubutton after the button has been pressed. The button part of the menubutton is the where the word ''File" appears.
0219-02.gif
Figure 11-2.
A menubutton widget that uses a menu widget
The main advantage of using a menubutton widget is that it handles the display functions of the menu. Because this is the most frequently used menu-related widget, it will be covered first.
The last menu-related widget covered is the optionmenu, which behaves differently than the other type of menus. The optionmenu allows the user to select one item from a list of items. For example, you can use an optionmenu to add the following options to your program:
• Allow users to select a favorite color from a list of colors.
• Allow users to select the country in which they live.
• Allow users to choose how verbose they would like the application to be: Silent, Semi-Verbose, and Verbose.
Figure 11-3 shows a block diagram of an optionmenu with Item3 selected.
0220-01.gif
Figure 11-3.
Example of an optionmenu widget
Menus simply give you a way to group related tasks together, and the optionmenu allows you to group several choices together. There is a callback associated with each menu item, much like the callbacks associated with button widgets. Instead of using 10 separate buttons, you can create 2 menus that each contain 5 menu items. This saves on display space and helps users understand that those items have a similar purpose and have been grouped together for their convenience.
The Menubutton Widget
0220-02.gif
As described earlier, the menubutton widget has a menu that drops down from a button when the button is pressed. The menu is removed from the window when an item from the menu is selected or when the user clicks elsewhere in the application.
Many applications use a menubutton-type construct. The menubuttons are normally grouped across the top of the application and have names like File, Edit, Options, and Help. Figure 11-4 shows an example of several menubuttons grouped together in a frame.*
Creating a Menubutton
When you create a menubutton widget, use the parent widget to invoke the Menubutton method, which then creates a menubutton widget reference. The options you send with the Menubutton method can configure both the button that is initially displayed on the screen and the actual menu items:
$mbutton = $parent->Menubutton ( [ options... ] )->pack;
* You can accomplish this same look in a window by using a menubar widget. However, the additional functionality that it provides is minimal, so we won't be covering it in this book. To get this look, create a frame widget with a relief of "ridge" and borderwidth of 2. Pack the menubuttons with -side => "left" for all but the help menu, which has -side => "right".
0221-01.gif
Figure 11-4.
Example of window with several menubuttons across the top
When it is first displayed with one of the geometry managers, you will only see the button part of the menubutton, which is a button with "flat" relief. The menu part of the menubutton won't appear until you press the button. Figure 11-5 shows the menubutton widget before and after the button is pressed. Notice how the relief of the button changes after it is pressed.
0221-02.gif
Figure 11-5.
Menubutton before and after the button is pressed
Menubutton Options
The options specified with the Menubutton command (or via the configure method) can affect only the button part of the menubutton, both the button and the menu, or just the menu.* The options that affect the menu are valid for the menu widget as well as the menubutton widget. We will cover the available options briefly (and some not so briefly) in order to discuss the effects of each. The brief synopsis of all the options and their effects appears first.
When the description says "Affects the button only," the behavior is the same as it would be for a button widget.
* The menubutton widget comprises other widgets (in this case, button and menu) to provide the overall functionality.
-activebackground => color
Affects the background color of the button and the currently highlighted menu item.
-activeforeground => color
Affects the text color of the button and the currently highlighted menu item.
-anchor => 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw' | 'center'
Affects the button only. Changes the position of the text within the button.
-background => color
Affects the button and the menu. All the background color changes to color when the state of the button and menuitems is
'normal'.
-bitmap => bitmapname
Affects the button only. Displays bitmap instead of text.
-borderwidth => amount
Affects the button only. Changes the width of the button edges.
-cursor => cursorname
Affects the button only. Changes the cursor when it's over the button part of the menubutton.
-disabledforeground => color
Affects the button and the menu item text when the
-state for either is 'disabled'.
-direction => "above" | "below" | "left" | "right" | "flush"
Tk8.0 option only. The value "above" puts the menu above the menubutton, "below" puts it below the button, and "left" and "right" puts it on the appropriate side of the button. "flush" puts the menu directly over the button.
-font => fontname
Affects the button only. Changes the font of any text displayed in the button.
-foreground => color
Affects the button only. Changes the color of any text or bitmap to color.
-height => amount
Affects the button only. Changes the height of the button.
-highlightbackground => color
Affects the button only. Changes the color of the highlight rectangle displayed around the button when the button does not have the keyboard focus.
-highlightcolor => color
Affects the button only. Changes the color of the highlight rectangle displayed around the button when the button does not have the keyboard focus.
-highlightthickness => amount
Affects the button only. Default is 0. Changes the width of the highlight rectangle around all edges of the button.
-image => imgptr
Affects the button only. Displays an image instead of text.
-indicatoron => 0 | 1
Affects the button; indirectly affects the display mechanism for the menu. When set to 1, a small bar appears on the right side of the button next to any text, bitmap, or image.
-justify => 'left' | 'right' | 'center'
Affects the button only. Changes the justification of the text within the button.
-menu => $menu
Tells the menubutton to display the menu associated with $menu instead of anything specified via the -menuitems option.
-menuitems => list
Causes the menu to display a list of items to create.
-padx => amount
Affects the button only. Adds extra space to the left and right of the button inside the button edge.
-pady => amount
Affects the button only. Adds extra space to the top and bottom of the button inside the button edge.
-relief => 'flat'| 'groove' | 'raised' | 'ridge' | 'sunken'
Affects the button only. Default is 'flat'. The relief of the button changes to 'raised' when the button is pressed.
-state => 'normal'| 'active' | 'disabled'
Affects the button; indirectly affects menu (menu cannot be displayed if state is 'disabled' ).
-takefocus => 0| 1 | undef
Affects the button only. Default is 0. Determines whether or not the button can have the keyboard focus.
-tearoff => 0 | 1
Affects the menu only. Default is 1. If set to 0, does not display the tear-off dashed line in the menu.
-text => text string
Affects the button only. Displays the specified string on the button (ignored if the
-bitmap or -image option is used.)
-textvariable => \$variable
Affects the button only. The information displayed in $variable is displayed on the button.
-underline => charpos
Affects the button only. The character at the integer charpos is underlined. If the button has the keyboard focus, pressing the key causes the button that corresponds to the underlined character to be pressed.
-width => amount
Affects the button only. Changes width of the button to amount.
-wraplength => pos
Affects the button only. Default is 0. Determines the screen distance for the maximum amount of text displayed on one line.
Button-Only Options
The following options affect only the button portion of the menubutton, and behave exactly as described in Chapter 3: -cursor,, -anchor, -bitmap, -border- width, -font, -foreground, -height, -highlightbackground, -highlight- color, -highlightthickness, -image, -justify, -padx, -pady, -relief, -state, -takefocus, -text, -textvariable, -underline, -width, and -wraplength.
Tear-off-Items
Each menu you create can be "torn off" from its window. The first item on the menu is a dashed line (see Figure 11-6); when you select this item, the menu widget becomes its own window and remains present until you close it with the window manager.
You can move the menu around on the screen, but you can't resize it. The other menu items will behave normally when they are selected. Be careful; you can tear off the same menu multiple times. Torn-off menus won't be updated when other events in the program are updated, so it is a good idea to limit your use of tear-off menus.
To remove the tear-off ability, use -tearoff => 0 with your list of arguments when you create the menu and the dashed line will no longer appear.
The tear-off line in the menu actually counts as an item. It uses index 0 if it exists, so your menu items will then number from 1 and up. If you use -tearoff=>0, then your menu items will number from 0 and up.
0225-01.gif
Figure 11-6.
Menu with tear-off item and menu without tear-off item
Color Options
Several options that determine color affect both  the button and the menu: -activebackground, -activeforeground, -background, and -disabledforeground.
Both -activebackground and -activeforeground affect the text/bitmap displayed in the button and the currently active menu item in the menu. The currently active menu item is the one that the mouse cursor is currently over. The menu item becomes slightly raised and might change color depending on these options. The effect of these options on the button is the same as it is for a normal button widget.
The -background option affects the entire menu and button background. The -disabledforeground option changes the color of the text of any menu items that have their own -state of 'disabled'; it also changes the text/bitmap color of the button if its -state is 'disabled'.
Button Indicator
In Chapter 4, we saw how the radiobutton and checkbutton widgets each have their own type of indicator. The button part of a menubutton also has an indicator that can displayed on it. The indicator is a small 3D bar displayed to the right of the text, bitmap, or image on the button (see Figure 11-7). Usually the indicator is used to show that something different will happen when you press the button. The option to display the indicator is -indicatoron, the same option used to display the indicator for the radiobutton and checkbutton widgets.
0225-02.gif
Figure 11-7.
Menubutton with indicator shown
Setting -indicatoron to 1 does not change the appearance of the menu at all. Usually, you would not use the -indicatoron option unless you were using the menubutton as a type of option menu or in a non-standard fashion.
Specifying Items for the Menu
Everything in this section will also apply to using the -menuitems option with the menu widget in addition to the menubutton widget.
The easiest way to add items to the menu in a menubutton is to use the -menuitems option. The value sent with the -menuitems option is a list of lists* that indicates not only the order of items in the menu but also any possible configuration options for that menu item. The best way to illustrate this is with an example.
$menub = $mw->Menubutton(-text => "Menubutton",
                         -menuitems => [[ 'command' => "Item 1"],
                                        [ 'command' => "Item 2"],
                                        "-",
                                        [ 'command' => "Item 3"],
                                        [ 'command' => "Item 4"]]);
In this snippet of code, we are creating the same menu that we displayed in Figure 11-5.
Here's a breakdown of the elements of the list and what they mean. The -menuitems option expects a list of lists. It is the sub-lists that contain information about each menu item. Each list that configures a menu item has a specified order to it. The first thing in the list is a string that will determine what type of menu item is created. The available types of menu items are "command", "radiobutton", "checkbutton", and "cascade". The second thing in the item list is a string that is displayed on the menu. After that, the options that affect that menu item type can be specified. To create a separator, use a string in place of an anonymous list.
As you can see, after -menuitems, I didn't assign any callbacks for any of the menu items. If you selected one, nothing would happen. To assign callbacks, we would change the statement to look like this:
$menub = $mw->Menubutton(-text => "Menubutton",
                         -menuitems => [[ 'command' => "Item 1",
                                         -command => \&do_item1 ],
                                        [ 'command' => "Item 2",
                                         -command => \&do_item2 ],
                                        "-"
* If you don't know what I mean by "list of lists," you'll find that the Camel book (Programming Perl) is a useful reference. The new version contains more information than you'll ever need to know about lists, hashes, and creating anonymous lists.
                                        [lsquo;command' => "Item 3",
                                          [-command => \&do_item3 ],
                                        [ 'command' => "Item 4",
                                          -command => \&do_item4 ]]);
I used the -command option to add the callbacks, and I used a different subroutine for each menu item. It doesn't make any sense to specify a callback for the separator item.
The first two items for each item list must be the item type string followed immediately by a text string that will be displayed in the menu. Even if you plan to display a different text string by using the -label option or to display an image, the second argument in this list must be a string.
It might be confusing that we use both "command" and -command. The first is the item type string and the second is the option(which should be followed by a callback).
You can also use the AddItems() method to add items to the menu: $menub-> AddItems ("command", -label => "Item1", -command => \&do_item1);. The argument list is slightly different (you send only the type, and then you must use the -label option to specify the text to appear on the menu, but all the options for each menu item type are exactly the same).
Certain options only apply to certain menu item types, which are discussed in the following sections.
Command item type
So far, each example of a menu has had only "command" and "separator" types of items. Usually, you'll also use the -command option so that something will happen when the menu item is selected.
Radiobutton item type
It is possible to put radiobuttons in a menu rather than inside the window where they take up space. They look and act just like a radiobutton would in the window except they are listed in the menu instead. The same radiobutton rules apply: You should always have at least two radiobuttons and they should be grouped logically by using the same -variable => $variable option for each group. Figure 11-8 shows an example of the placement of radiobuttons in a menu.
0228-01.gif
Figure 11-8.
Radiobuttons as menu items
In an example in Chapter 4, Checkbuttons and Radiobuttons, we used radiobuttons to select the background color of the window. We could also use those radiobuttons in a menu and save space in our application:
#!/usr/bin/perl -w
use Tk;
my $mw = MainWindow->new;
$mw->title("Menubutton");
$menub = $mw->Menubutton (-text => "Color")->pack(-side => 'left',
                                                -anchor => 'n');
foreach (qw/red yellow green blue grey/) {
  $menub->radiobutton (-label => $-
                      -command => \&set_bg,
                      -variable => \$background_color,
                      -value => $_);
}

MainLoop;

sub set_bg {
  print "Background value is now: $background_color\n";
  $mw->configure (-background => $background_color);
}
Figure 11-9 shows what the window looks like after it has been resized and the menu has been posted.
Checkbutton item type
You can also put checkbuttons in a menu to keep them out of the way. Use the -command option to configure the checkbutton to perform an action when it is selected. Figure 11-10 shows what checkbuttons look like in a menu.
Remember the checkbutton guidelines: each checkbutton should have its own -variable, because each can be selected or not.
0229-01.gif
Figure 11-9.
Using radiobuttons in a menu to set background color
0229-02.gif
Figure 11-10.
Checkbuttons in a menu (1, 3, and 5 selected manually)
Cascade item type
A cascade menu item points to another menu. When you select this type of menu item, another menu will pop up to the right of the current menu. This is the most complicated item type to implement because you have to create another entire menu to display (the next major section in this chapter covers the menu widget). Figure 11-11 shows what a cascade menu item looks like.
0229-03.gif
Figure 11-11.
Cascade menu item inside a menubutton widget
The submenu must be a child of the menu within the menubutton widget. This allows Perl/Tk to keep track of the correct hierarchy of the menus. The best way to create a submenu is to create the menubutton first and then create the submenu.
$menub = $mw->Menubutton (-text => "My Menu",
                         -menuitems => [["cascade" => "Submenu"]]);
$submenu = $menub->menu->Menu (-menuitems => [ ... ]);
We can use the menubutton widget's menu() method to return the actual menu item and then create the new menu as a child of the menu item. Now we can add the cascade item to the menubutton and configure it to point to the new submenu:
$menub->entryconfigure ("Submenu", -menu => $submenu);
Because of some problems with cascade menus, it is necessary to first create the cascade entry and then configure it with the actual menu it will display. Here is an entire Perl program for you to play around with so you can get the feel of cascade menus. It creates two submenus, one with numbers and one with letters:
#!/usr/bin/perl -w

use Tk;
my $mw = MainWindow->new;
$mw->title ("Menubutton");
# Create menubutton and put on the screen
$menub = $mw->Menubutton (-text => "Menubutton")->pack;

# make our sub menu to be cascaded a child of upper menu.
$menu1 = $menub->menu->Menu;
foreach (qw/one two three four/) {
  $menu1->add ('command', -label => $_);
}

# make second sub menu also a child of the upper menu
$menu2 = $menub->menu->Menu;
foreach (qw/A B C D/) {
  $menu2->radiobutton (-label => $_);
}

# now add the cascade items to the main menu
$menub->cascade (-label => "Numbers");
$menub->cascade (-label => "Letters");

# now configure those cascade entries to point to correct submenu
$menub->entryconfigure ("Numbers", -menu => $menu1);
$menub->entryconfigure ("Letters", -menu => $menu2);

MainLoop;
You can also create cascade menu items on a menu that cascades from another menu, but remember to create it as a child of that menu's menu.
Separator item type
Separators are noninteractive portions of a menu. They do nothing except provide a visual break between menu items. To create one, either call the separator method on the menubutton widget or use a string in the -menuitems list instead of another list.
0231-01.gif
Figure 11-12.
Separator in a menubutton widget
Figure 11-12 shows the separator line. It is a solid line, unlike the tear-off menu, which is a dashed line (not shown in Figure 11-12). The following code was used to create the menubutton shown in Figure 11-12:
$mw->Menubutton (-tearoff => 0,-menuitems => [ ['command' => "Item 1"],
                                               ['command' => "Item 2"],
                                                "-",
                                               ['command' => "Item 3"],
                                               ['command' => "Item 4"]])->pack;
We could have used any string at all in place of the"-" line. However, it is good style to always use the same string so it is easy to recognize when a separator item is created.
Accelerators
The -accelerator option allows you to place a text string to the right of the text or image displayed in the menu. The string usually contains a clue to a quick-key combination that will execute the command associated with the menu item. In Figure 11-13, Item 1 has the accelerator string Alt+1 next to it. The menuitem was created by using this list in the -menuitems option: [ 'command' => 'Item 1', -accelerator => "Alt+1"]. To make the Alt-1 key combination actually perform an action, you'll need to use bind (see Chapter 14, Binding Events).
0232-01.gif
Figure 11-13.
Menu with accelerator next to Item 1
Displaying an Image in a Menu Item
Each menu item is a type of button, so it makes sense that you can display an image instead of text. Figure 11-14 shows what happens when you also specify the -image option. The code that created the menu is as follows:
$img1 = $mw->Bitmap (-file =>
   "/usr/X11R6/include/X11/bitmaps/lineOp. xbm");
$mw->Menubutton (-text => "Menubutton",
                -menuitems => [[ 'command' => "Item 1"],
                               [ 'command' => "Item 2",
                                 -image => $imgl],
                               '-',
                               [ 'command' => "Item 3"],
                               [ 'command' => "Item 4"]
                              ])->pack(-side => 'left');
0232-02.gif
Figure 11-14.
An image displayed instead of text
In Chapter 4, I discussed using icons and how they can make options easier to understand. Try to use good judgment and not go crazy with the picture menu items. Too many vague icons (such as the one displayed in Figure 11-14) can make an application confusing.
Assigning a Different Menu
By default, when you use the -menuitems option, a menu is created. You can create your own menu widget and tell the menubutton widget to use it instead. But there is a trick involved. It's a chicken-before-the-egg problem. You need to create the menubutton and then create the menu widget as a child of the menubutton. Use configure to assign the new menu to the menubutton. Here is a code example:
# create menubutton w/ some fake menu items
$m1 = $mw->Menubutton(-text => "Text Menul",
                      -menuitems=> [['command' => "Item 1'],
                                     ['command' => "Item 2"],
                                     "-",
                                     [ 'command' => "Item 3"],
                                     [ 'command' => "Item 4"]
                                    ])->pack(-side => "left",
                                             -expand => 'y',
                                             -fill => 'both');

# Create a Menu as a child of the Menubutton $ml
$menu = $m1->Menu (-menuitems => [[ 'command' =>"Item 1"],
                          [ 'command' => "Item 2"],
                          [ 'command' => "Item 3"]]);

# Now use the $menu with the Menubutton
$ml->configure (-menu => $menu);
MainLoop;
As mentioned, you need to create the menubutton first and make it a child of $mw (the Main Window). I created some menu items that will be different on the new menu so you can tell which menu the menubutton is using.
Configuring a Menubutton
The cget method allows you to get configuration information about any of the options associated with a menubutton. You can use configure to query or change any of the options. Both configure and cget are explained fully in Appendix A, Configuring Widgets with configure and cget.
Configuring Menubutton Items
The menubutton widget has an entrycget method that is the same as the menu widget's entrycget method.
$value = $menub->entrycget (index, option);
The arguments are an index and the option to query. Valid index values are discussed in ''The Menu Widget" later in this chapter.
The entryconfigure method is also provided by the menubutton widget. It performs the same function the menu widget's entryconfigure method performs:
$menub->entryconfigure(index, [ option ]);
Adding Items to a Menubutton
The AddItems method gives you another way to put new items in the menu. It will always add the new item(s) to the end of the menu in the order they appear in the list. Similar to the arguments sent to the -menuitems option, the arguments sent to AddItems are included in several lists. There is no need to enclose the item lists inside another list level because the only thing you send to AddItems is item lists. Here is an example:
$menub = $mw->Menubutton (-text => "File") ->pack;
$menub->AddItems (["command" => "Open", -command => \&do_open],
                 ["command" => "Close", -command => \&do_close],
                 "-",
                 ["command" => "Exit", -command => sub { exit } ]);
This use of AddItems is just another way of saying the following:
$menub = $mw->Menubutton (-text => "File", -menuitems =>
           [ ["command" => "Open", -command => \&do_open],
             ["command" => "Close", -command => \&do_close],
              "-",
             ["command" => "Exit", -command => sub { exit } ]
           ])->pack;
Notice the extra set of [ ] around the lists containing the menu item information. All the information in between the [] is exactly the same as it was when it was sent to AddItems.
The command method adds a command item to the end of the menu. When you use command, you must use the -label option to specify the text to be displayed in the menu. This code creates the same menu AddItems() example created:
$menub = $mw->Menubutton (-text => "File")->pack;
$menub->command(-label => "Open", -command => \&do_open);
$menub->command(-label => "Close", -command => \&do_close);
$menub->separator;
$menub->command(-label => "Exit", -command => sub { exit });
Creating a Checkbutton
The checkbutton method adds a checkbutton item to the end of the menu. Like the command method, you are required to use the -label option to specify the text string to display in the menu with the checkbutton. All other checkbutton item
options are the same as those listed in "Specifying Items for the Menu" earlier in this chapter. Here's an example:
$menub = $mw->Menubutton(-text => "Options");
$menub->checkbutton(-label => "Confirm Quit?",
                    -variable => \$confirm_quit);
checkbutton is really a menu widget method, but it also works on a menubutton widget. The same is true of radiobutton, separator, and cascade.
Creating a Radiobutton
The radiobutton method adds a radiobutton item to the end of the menu. You must specify the text to be displayed in the menu by using the -label option.
$menub->radiobutton(-label=>"Radio item");
Creating a Separator
The separator method adds a separator line tot he end of the menu. It does not take any arguments:
$menub->separator ();
Adding a Cascade Menu
The Cascade method adds a cascade item to the end of the menu. You must specify the text to be displayed by using the label option. Use $menub-> entryconfigure(-menu => $submenu) to assign the menu to be cascaded.
# assume we already created $menu_more
$menub->cascade (label => "More menu...");
$menub->entryconfigure ("More menu...", -menu => $menu_move);
Getting a Reference to the Menu Item
The menu mefhod returns a reference to the menu used within the menubutton widget. This allows us to create cascade entries with the actual menu as the parent of the cascaded menu; it also allows us access to all of the menu widget methods. For example, we could delete a menu item from our menu by using $menub->menu->delete(1), which would delete the second item in the menu. For more information on the menu widget methods, see "The Menu Widget" later in this chapter.
Complete Menubutton Examples
Menus are a more complicated widget than we've seen before because you don't always add items to them the same way. Sometimes you can use the simple -menuitems option, and other times you'll want to add to them dynamically. This section contains some full-length Perl scripts that create some useful menus.
Creating a Menubar
Here is the code that was used to create the window and menubar in Figure 11-4:
#!/usr/bin/perl -w
use Tk;
my $mw = MainWindow->new;
$mw->title("Menubutton");

$mw->Button(-text => "Exit",
            -command => sub { exit; })->pack(-side => "bottom");

my $f = $mw->Frame(-relief => 'ridge', -borderwidth => 2);
$f->pack(-side => 'top', -anchor => 'n', -expand => 1, -fill => 'x');

foreach (qw/File Edit Options Help/) {
  push (@menus, $f->Menubutton(-text => $_));
}

$menus[3]->pack(-side => 'right');
$menus[0]->pack(-side => 'left');
$menus[1]->pack(-side => 'left');
$menus[2]->pack(-side => 'left');

MainLoop;
First a frame was created across the top of the window and packed so it will resize itself dynamically when the window gets larger or smaller. Then the menubuttons were created and packed into the frame. Each of the menus has no items. We'll leave that as an exercise for the reader.
Dynamic Document List
In certain cases, you'll want to add and remove items from a menu dynamically. Many applications remember which documents you've most recently opened and keep them attached to the File menu for easier access later. This example does something similar, but I've simplified the problem-we'll just have a button that creates a new document name, and we'll display that document name in an entry
widget so we know which one we are editing. Using our menubutton and a few select methods from the menu widget, we can create a solution like this:
#!/usr/bin/perl -w
use Tk;
$mw = MainWindow->new;
$mw->title("Documents");

# Create a frame for our menubar across the top of the window
$f = $mw->Frame(-relief => 'ridge', -borderwidth => 2)
  ->pack(-side => 'top', -anchor => 'n', -expand => 1, -fill => 'x');

# Create the menubutton, with two items: New Doc and a separator
$filem = $f->Menubutton(-text => "File",
                        -tearoff => 0,
                        -menuitems => [ ["command" => "New Document",
                                         -command => \&new_document],
                                         "-"
                                      ]) ->pack(-side => 'left');
# We will open document 1 to begin with, and we want to limit the number
# of documents in our list to 0-9 (leaves 10 docs max in menu)
$doc_num = 1;
$doc_list_limit = 9;

# Create button that will do the same thing as the New Document menu item
$mw->Button(-text => "New Document",
            -command => \&new_document)->pack(-side => 'bottom',
                                             -anchor => 'e');
# The entry will display the current doc we are "editing".
$entry = $mw->Entry(-width => 80) ->pack(-expand => 1, -fill => 'both');

MainLoop;

# Creates the next doc in line, incs the doc counter
# Adds the new doc to the menu, and removes any docs from the
# menu that are over the limit (oldest out first)
sub new_document {
  my $name = "Document $doc_num";
  $doc_num++;

  push (@current, $name);
  $filem->command(-label => "$name",
                   -command => [ \&select_document, $name ]);

  &select_document ($name);

  if ($#current > $doc_list_limit) {
    $filem->menu->delete(2);
    shift (@current);
  }
}
 
sub select_document {
  my ($selected) = @_;

  $entry->delete(0, 'end');
  $entry->insert('end', "SELECTED DOCUMENT: $selected");
}
Figure 11-15 shows what our window looks like after we've created three documents:
0238-01.gif
Figure 11-15.
Example of Document History window
The Menu Widget
There are times when you won't want to use a menubutton widget. Perhaps you need to create some menus that will cascade from your menubutton. You still need to create the menus. You might also think of a way to use a menu that doesn't involve a button. For example, you could set up your application so the user right-clicks on a widget* and a related menu pops up, allowing the user to change configuration options.
It is also a good idea to be familiar with the methods for manipulating a menu, whether it's a menu widget by itself or the menu attached to a menubutton.
Creating the Basic Menu
To create a menu widget, invoke Menu from the desired parent of the menu:
$menu = $parent->Menu (options);
The menu widget is the only widget on which one of the geometry managers is not used directly. The menu is displayed to the user via a post directive:
$menu->post( ... );
Different arguments sent to post will determine how the menu is displayed. This method is discussed later in this chapter.
* Use "<Button-3>" with bind.
Menu Creation Options
As with any widget, there are options that affect how the menu widget looks and behaves. Many of the options for the menu widget were discussed in the menubutton widget portion of the chapter, so I'll only cover those that perform actions that aren't available with the menubutton widget or whose actions are different.
The following is a list of the options available for the menu widget:
-activebackground => color
Sets the color of the background behind the active menu item.
-activeborderwidth => amount
Sets the width of the edges of the active menu item's border.
-activeforeground => color
Sets the color of the text in the active menu item.
-background => color
Sets the color of the background of the entire menu.
-borderwidth => amount
Sets the width of the menu's edge.
-cursor => cursorname
Sets the cursor displayed when the mouse cursor is over the menu.
-disabledforeground => color
Sets the color of the text of any disabled menu items.
-font => font
Sets the of the text displayed in the menu.
-foreground => color
Sets the color of the text in the menu.
-menuitems => list
Defines a list of items to create in the menu.
-postcommand => callback
Sets the callback that is invoked before the menu is posted to the screen.
-relief => 'flat' | 'groove' | 'raised' | 'ridge' | 'sunken'
Sets the relief of the edges of the menu.
-selectcolor => color
Sets the color of the selection box in checkbutton or radiobutton items.
-takefocus => 0 | 1 | undef
Controls the ability to use the keyboard to traverse the menu. Default is 0.
-tearoff => 0 | 1
Determines whether or not the menu will contain the tear-off item as the first item. Default is 1.
Menu Style
The edges of the menu default to 'raised' with a -borderwidth of 2. This makes the menu look like a large button with multiple items of text listed in it. We can change the look of the menu edges by using the -relief option:
-relief => 'flat' | 'groove' | 'raised' | 'ridge' | 'sunken'
The menus in Figure 11-16 were created and then torn off so they are left on the screen. The actual menu edge is inside the window manager's decoration.
0240-01.gif
Figure 11-16.
Different relief options with menu widget
The width of the menu's edges (regardless of the -relief) are changed by using the -borderwidth option:
-borderwidth => amount
Changing the -borderwidth always makes the different relief types stand out more, as shown in Figure 11-17.
0240-02.gif
Figure 11-17.
Menus with different relief options and -borderwidth set to 4
The -activeborderwidth option affects the active menu item (the one with the mouse cursor over it):
-activeborderwidth => amount
Menu Fonts and Cursors
The font of the text displayed in the entire menu is controlled with the -font option:
-font => font
Figure 11-18 shows a menu with a different font, "lucidasans-14". Fonts that can be used for the value of -font were covered in Chapter 3.
0241-01.gif
Figure 11-18.
Menu with a different font
To change the cursor displayed when the mouse cursor is over a menu widget, use the -cursor option:
-cursor => cursorname
The default cursor for a menu widget is different than the window's default cursor. The default cursor for a menu is 'arrow', whereas the window cursor is an arrow that points the other way.
Calling a Subroutine Before Displaying the Menu
Before displaying the menu (via the post command or a menubutton), you can use the -postcommand option to specify a subroutine to call:
-postcommand => callback
The form for the callback is the same as the one used in a button widget (described in Chapter 2). One of the best uses of the -postcommand option is to update the state of each menu item if needed. Here is an example that uses a menubutton widget but uses -postcommand to perform an update of the menu before it is drawn:
# Create the menubutton
$menub = $mw->Menubutton(-text => "File", -tearoff => 0,
  -menuitems => [[ 'command' => "Open", -command => \&do_something],
                 [ 'command' => "Save" -command => \&do_something],
                 [ 'command' => "Close", -command => \&do_something],
                 "_",
                 [ 'command' => "Exit", -command => sub { exit }]]
  )->pack();
# A flag we use to see if the document has been saved yet.
$unsaved = 0;
# We have to wait until after we've created the menubutton to
# access the menu widget part of it:
$menub->menu()->configure(-postcommand => \&update_menu);
 
# This looks at some flags in our program and determines if the items
# should be updated or not
sub update_menu {
  if ($unsaved) {
    $menub->menu->entryconfigure(1, -state => "normal");
  } else {
    $menub->menu->entryconfigure(1, -state => "disabled");
  }
}
Specifying Menu Items
The -menuitems option allows you to create the menu and the menu items at the same time. The format for doing so is the same as the format for the menubutton's -menuitems option.
There is no AddItems method for a menu widget. The AddItems method is only available with the menubutton widget. You can use either the -menuitems option or the add method with a menu widget. add is described in the next section.
Menu Indexes
Like entry and text widgets, menu widgets have their own indexing scheme, as follows.
n
The items in a menu are numbered from 0 to n; 0 is the first item at the top of the menu, and n is the last item in the menu. (The tear-off item in a menu counts as index 0 if it is present. Use
-tearoff => 0 to turn it off.)
"active"
The menu item that is currently active (the mouse is over it and it is highlighted). If there are no menu items active, then "active" means the same as 'none'.
"end"
The last menu item in the menu. If there are no items in the menu, then 'end' means the same as "none".
"last"
Another way to say "end"
"none"
No item.
"@y"
The number is a y coordinate in the window. This form of index specification will resolve to the menu item closest to the y coordinate. "@0" means the same as 0.
"pattern"
The pattern is text to match the menu items against. The first menu item (starting with 0) it matches is used.
There aren't really that many menu widget methods. The most important methods are probably entryconfigure and delete because you'll use them more often than you'll use the others. Remember, if you are using a menubutton widget, you can invoke the menu widget method directly by using $menubutton->menu-> method().
Configuring the Menu Widget
The cget method returns the current value of an option. It only affects the options for the entire menu; there is an entrycget method that will return information about specific menu items. Both the configure and cget methods are discussed in detail in Appendix A.
Configuring Menu Items
The entrycget method queries a specific menu item and returns the information about that configuration option:
$menu->entrycget(index, -option);
The index determines which menu item entrycget affects. Any of the options that can be sent with the add method (covered in the following section) are valid.
The entryconfigure method returns or alters the configuration options of the menu item at index just as configure does for the entire menu widget:
$menu->entryconfigure (index, [-option, value, ...]);
You can specify no options to get the current configurations for all of the options at that index. You can specify a single option to get the value of only that option for that index. You can also specify multiple option/value pairs to set the values of those options for that index.
Adding Items
In addition to the -menuitems option, you can use the add method to add items to the end of a menu. The first argument to add is the type of menu item to be added. It should be one of the following: "command", "radiobutton", "checkbutton", "separator", or "cascade". Here is a usage statement:
$menu->add(type [ , options... ]);
The options that affect each menu item are the same as those for the -menuitems option: -activebackground, -activeforeground, -accelerator,
-background, -bitmap, -command, -font, -foreground, -image, -indicatoron, -label, -menu, -offvalue, -onvalue, -selectcolor, -selectimage, -state, -underline, -value, and -variable.
The results of the following two code snippets are identical:
# Snippet 1
# Using add for menu items
$menu = $mw->Menu;
$menu->add("command", -label => "Open",
           -command => \&open_file);
$menu->add("command", -label => "Close",
           -command => \&close_file);
#Snippet 2

# Sending a list intially using -menuitems option
$menu = $mw->Menu(-menuitems => [ ["command" => "Open",
                                    -command => \&open_file],
                                   ["command" => "Close",
                                    -command => \&close_file]
                                  ]);
Each additional call to add will add another item to the end of the menu. To add a menu item to somewhere other than the end of the menu, see the insert method (covered in the next section).
Instead of -text or -textvariable options, we use -label to indicate the text shown on the menu item. You should notice that we don't have a -labelvariable option. If you need to change the text shown in the menu item, you will need to use the entryconfigure method (discussed later in this chapter).
Inserting Menu Items
The insert method works exactly the same way the add method works, except the new menu item will be inserted right before the menu item at index. You cannot insert a menu item before the tear-off menu item because the tear-off must always be the first item in the menu:
$menu->insert(index, type [, options ... ]);
Here is an example:
$menu->insert("end", "radiobutton", -label=>"red");
Deleting Menu Items
To remove menu items from your menu, use the delete method:
$menu->delete(index);
# or..
$menu->delete(index1, index2);
You can delete one item by specifying only one index. You can delete more than one by specifying a range of indexes. Here are some examples:
$menu->delete ('last');   # deletes the last menu item
$menu->delete (0, 'end');# deletes every menu item (except tear off)
$menu->delete ("Open");   # deletes the item that matches "Open"
Invoking Menu Items
The invoke method will try to invoke the menu item at the specified index (as if you clicked on it with the mouse):
$menu->invoke (index);
The specific result of the invocation depends on what type of menu item is at index. The result from any -command callback associated with that index will be returned by the invoke method.
$menu->invoke ("red");
Determining Item Type
The type method returns a string that indicates the type of menu item located at index:
$type = $menu->type(index);
The string returned will be one of the following: "command", "radiobutton", "checkbutton", "cascade", "separator", or "tearoff".
$type = $menu->type(0); # look at index 0
Translating Index Values
The index method returns the numerical index of the menu item at index:
$menu->index (index);
The code $menu->index('end') returns 9 if there are 10 menu items in the menu. The code $menu->index("Open") returns the index number of the menu item that matches "Open".
Displaying a Menu
If you aren't using a menubutton widget to display your menu, you need a way to display it. You can use the post method or the Popup method.
The post method displays the menu for you, but the menu only goes away after you select a menu item or specifically call unpost. The Popup displays the menu only while the mouse button is depressed.
The post method requires x and y coordinates to tell it where to place the menu on the screen. Typically, you call it wherever the user clicked (unless you want it to display in the same place all the time). Here is an example that displays the menu when the user clicks the right mouse button in a listbox:
# Create a menu with two items for our example
$menu = $mw->Menu(-tearoff => 0,
                  -menuitems => [['command' => "A"],
                                 ['command' => "B"]]);
$1b = $mw->Listbox () ->pack () ;
# create a binding on the listbox that will display our menu
# when we click with the right mouse button
$lb->bind ("<Button-3>", [ \&display_menu (), Ev('X'), Ev('Y')]);
sub display_menu {
  my ($lb, $x, $y) = @_;
  $menu->post($x,$y);
}
I created a simple menu so we can get through the example quickly. I removed the tear-off item from the menu because I don't like tear-off menus all over the place (but some users do, so keep this in mind). The bind is where I mapped the right mouse button to display the menu. I used Ev("X") and Ev("Y") to send the coordinates of the location in which the user clicked (see Chapter 14 for more information about Ev("X") and Ev("Y")). The subroutine simply calls post with the correct arguments.
The menu will be displayed even when the user lets go of the mouse button. It will unpost itself automatically when the user selects a menu item.
Another way to display a menu is to use Popup.. This causes the menu to be displayed only while the user holds down the mouse button. To select a menu item, you must click down the mouse button, slide the cursor to the desired item, and then let go of the mouse button. The Popup method can be called with no arguments or with one or two options. The options that affect Popup are -popover and -popanchor. Calling Popup like this
$menu->Popup();
displays the menu at the very center of your entire screen. This isn't very useful, so I recommend that you at least use the -popover option. The -popover option will take either the string "cursor" or a widget reference. The menu will be centered under the cursor or centered over the widget; for example:
$menu->Popup(-popover => "cursor");  # Center menu under cursor
$menu->Popup(-popover => $button);   # Center menu over $button
$menu->Popup(-popover => $listbox);  #Center menu over $listbox
Notice that we are not using the syntax \$listbox. Because our scalars already contain a reference to a widget, we don't need to reference it again.
The second option, -popanchor, affects how the menu gets positioned relative to the -popover argument (or the entire screen if -popover isn't specified). The -popanchor option takes a string argument where the string is one of the following: "nw", "ne", "sw". or "se" For instance, if you would like to display the menu's upper-left corner where the user clicks, use this code:
$menu->Popup (-popover => "cursor", -popanchor => "nw");
This is how I like to create right-click menus that are associated with widgets. See the complete example in the ''Right-Click Menu Example."
Displaying a Cascading Menu
If your menu has a cascading menu associated with it, use postcascade to display it:
$menu->postcascade(index);
The postcascade method will unpost any other submenu and then post any cascade menu associated with the menu item located at index. If the menu item at index is not a cascade item type, then the only thing that happens is that any other submenus are unposted.
$menu->postcascade ("submenu");
Undisplaying a Menu
If you have displayed the menu on the screen using post, you can use unpost to remove it from the screen:
$menu->unpost();
This will unmap $menu from the window. If any cascaded menus of this menu are also displayed, they will be unmapped as well.
Getting the Position of an Item
The yposition method returns a decimal string that gives the y coordinate of the topmost pixel of the menu item at index:
$location = $menu->yposition(index);
Right-Click Menu Example
There are times you'll want to use a right-click menu, which is a menu that appears when you right-click on a particular widget or location in the application. A canvas is a perfect place to use a right-click menu; there are often so many different possible actions to take that associating different menus with different types of objects in the canvas is advantageous.
To create a right-click menu, simply create a menu widget, add the items to it as desired, and use the -command option to make the items perform useful tasks. To display the menu when the user right-clicks on the desired object, use the bind command:
$object->bind("<Button-3>", sub {$menu->Popup(-popover => 'cursor'); });
You can use a right-click menu with a listbox to allow users to delete or edit the currently selected item.
Optionmenu Widget
0248-01.gifThe optionmenu is a specific implementation of a menubutton widget. The difference between the two is that the optionmenu automatically sets the -indicatoron option to 1, removes the tear-off menu item, and handles the display of the menu in a slightly different way.
You can use an optionmenu when you want to give the user a choice between several different items but don't want to waste space with a listbox and scrollbar or with several radiobuttons. To add items, use the -options command instead of -menuitems or the other methods that allowed you to add to a menu or menubutton.
Creating and Configuring an Optionmenu
The optionmenu is created by using the Optionmenu method:
$optionmenu = $mw->Optionmenu( ... );
All the options that are available with a menubutton widget are also available for the optionmenu widget. The following options are specific to the optionmenu: -textvariable, -options, -variable, and -command.
Instead of using a -menuitems option or other methods to add items to an optionmenu, use the -options option. It takes an anonymous list that can contain either strings or other anonymous lists. The idea behind an optionmenu is to select one item from a list of items. The text displayed is the currently selected menu item. The -textvariable option determines where the displayed text is stored. There is also a -variable option, which you can use to store a value that is different than the one shown on the menu. Specify the displayed value and the stored value by using the -options option. If the displayed value is the same as the stored value, use a simple list:
-options => [1, 2, 3, 4, 5, 6], -textvariable => $number
To store a value other than the one shown, use this code:
-options => [["one",1], ["two",2], ["three",3],
             ["four",4], ["five",5], ["six",6]],
-textvariable => $displayed,
variable => $number
In this example, the written words are displayed in the menu (and are stored in $displayed), and the stored value (in $number) are the integers. The nondisplayed value can be any scalar value.
The -command option assigns a callback that will be executed when a selection has been made. The default arguments to the callback are the variables assigned with -textvariable and then -variable (if it exists). You can use callbacks to perform an action based on the item selected from the optionmenu.
Here's a complete script that will allow you to see most of the optionmenu's useful features:
#!/usr/bin/perl -w

use Tk;

$mw = MainWindow->new;
$mw->title("Optionmenu");

$display_var = "ten";
$mw->Optionmenu (-command =>
  sub { print "ARGS: @_\n"; print "in optionmenu\n" ;},
               -textvariable => \$display_var,
               -variable => \$stored_var,
               -options => [["ten", 10],
                             ["twenty",20],
                             ["thirty",30]]
               )->pack();
MainLoop;
It's good idea to also create a label widget so the user is aware of the optionmenu's purpose (shown in Figure 11-19).
0249-01.gif
Figure 11-19.
Optionmenu with a label widget to the left
The only methods available with the optionmenu are the cget and configure methods. The cget method returns information about an option in the optionmenu. The configure method can get or set option values for the optionmenu widget. Both cget and configure are covered in detail in Appendix A.
Fun Things to Try
• Create one menu that has two items: Disabled and Normal. When you rightclick on a widget, the menu will pop up. If you select 'Disabled', that widget will be disabled. Selecting 'Normal' reenables that widget.
• Create an application with two menubuttons. Have the items on the first add and delete different types of menu items to the second menu.
• Take all the fun things from previous chapters and add menus to them. Add at least a File menu, with an Exit item. Be inventive!