17. Menus Library

The menus library provides a nice extension to basic curses, through which you
can create menus. It provides a set of functions to create menus. But they have
to be customized to give a nicer look, with colors etc. Let's get into the
details.

A menu is a screen display that assists the user to choose some subset of a
given set of items. To put it simple, a menu is a collection of items from which
one or more items can be chosen. Some readers might not be aware of multiple
item selection capability. Menu library provides functionality to write menus
from which the user can chose more than one item as the preferred choice. This
is dealt with in a later section. Now it is time for some rudiments.

17.1. The Basics

To create menus, you first create items, and then post the menu to the display.
After that, all the processing of user responses is done in an elegant function
menu_driver() which is the work horse of any menu program.

The general flow of control of a menu program looks like this.

Initialize curses

Create items using new_item(). You can specify a name and description for the
items.

Create the menu with new_menu() by specifying the items to be attached with.

Post the menu with menu_post() and refresh the screen.

Process the user requests with a loop and do necessary updates to menu with
menu_driver.

Unpost the menu with menu_unpost()

Free the memory allocated to menu by free_menu()

Free the memory allocated to the items with free_item()

End curses

Let's see a program which prints a simple menu and updates the current selection
with up, down arrows.

17.2. Compiling With the Menu Library

To use menu library functions, you have to include menu.h and to link the
program with menu library the flag -lmenu should be added along with -lncurses
in that order.

This program demonstrates the basic concepts involved in creating a menu using
menus library. First we create the items using new_item() and then attach them
to the menu with new_menu() function. After posting the menu and refreshing the
screen, the main processing loop starts. It reads user input and takes
corresponding action. The function menu_driver() is the main work horse of the
menu system. The second parameter to this function tells what's to be done with
the menu. According to the parameter, menu_driver() does the corresponding task.
The value can be either a menu navigational request, an ascii character, or a
KEY_MOUSE special key associated with a mouse event.

The menu_driver accepts following navigational requests.

REQ_LEFT_ITEM Move left to an item.
REQ_RIGHT_ITEM Move right to an item.
REQ_UP_ITEM Move up to an item.
REQ_DOWN_ITEM Move down to an item.
REQ_SCR_ULINE Scroll up a line.
REQ_SCR_DLINE Scroll down a line.
REQ_SCR_DPAGE Scroll down a page.
REQ_SCR_UPAGE Scroll up a page.
REQ_FIRST_ITEM Move to the first item.
REQ_LAST_ITEM Move to the last item.
REQ_NEXT_ITEM Move to the next item.
REQ_PREV_ITEM Move to the previous item.
REQ_TOGGLE_ITEM Select/deselect an item.
REQ_CLEAR_PATTERN Clear the menu pattern buffer.
REQ_BACK_PATTERN Delete the previous character from the pattern buffer.
REQ_NEXT_MATCH Move to the next item matching the pattern match.
REQ_PREV_MATCH Move to the previous item matching the pattern match.

Don't get overwhelmed by the number of options. We will see them slowly one
after another. The options of interest in this example are REQ_UP_ITEM and
REQ_DOWN_ITEM. These two options when passed to menu_driver, menu driver
updates the current item to one item up or down respectively.

17.3. Menu Driver: The work horse of the menu system

As you have seen in the above example, menu_driver plays an important role in
updating the menu. It is very important to understand various options it takes
and what they do. As explained above, the second parameter to menu_driver() can
be either a navigational request, a printable character or a KEY_MOUSE key.
Let's dissect the different navigational requests.

REQ_LEFT_ITEM and REQ_RIGHT_ITEM

A Menu can be displayed with multiple columns for more than one item. This can
be done by using the menu_format()function.
When a multi columnar menu is displayed these requests cause the menu driver to
move the current selection to left or right.

REQ_UP_ITEM and REQ_DOWN_ITEM

These two options you have seen in the above example. These options when given,
makes the menu_driver to move the current selection to an item up or down.

REQ_SCR_* options

The four options REQ_SCR_ULINE, REQ_SCR_DLINE, REQ_SCR_DPAGE, REQ_SCR_UPAGE are
related to scrolling. If all the items in the menu cannot be displayed in the
menu sub window, then the menu is scrollable. These requests can be given to the
menu_driver to do the scrolling either one line up, down or one page down or up
respectively.

REQ_FIRST_ITEM, REQ_LAST_ITEM, REQ_NEXT_ITEM and
REQ_PREV_ITEM

These requests are self explanatory.

REQ_TOGGLE_ITEM

This request when given, toggles the present selection. This option is to be
used only in a multi valued menu. So to use this request the option O_ONEVALUE
must be off. This option can be made off or on with set_menu_opts().

Pattern Requests

Every menu has an associated pattern buffer, which is used to find the nearest
match to the ascii characters entered by the user. Whenever ascii characters are
given to menu_driver, it puts in to the pattern buffer. It also tries to find
the nearest match to the pattern in the items list and moves current selection
to that item. The request REQ_CLEAR_PATTERN clears the pattern buffer. The
request REQ_BACK_PATTERN deletes the previous character in the pattern buffer.
In case the pattern matches more than one item then the matched items can be
cycled through REQ_NEXT_MATCH and REQ_PREV_MATCH which move the current
selection to the next and previous matches respectively.

Mouse Requests

In case of KEY_MOUSE requests, according to the mouse position an action is
taken accordingly. The action to be taken is explained in the man page as,

If the second argument is the KEY_MOUSE special key, the
associated mouse event is translated into one of the above
pre-defined requests. Currently only clicks in the user
window (e.g. inside the menu display area or the decora­
tion window) are handled. If you click above the display
region of the menu, a REQ_SCR_ULINE is generated, if you
doubleclick a REQ_SCR_UPAGE is generated and if you
tripleclick a REQ_FIRST_ITEM is generated. If you click
below the display region of the menu, a REQ_SCR_DLINE is
generated, if you doubleclick a REQ_SCR_DPAGE is generated
and if you tripleclick a REQ_LAST_ITEM is generated. If
you click at an item inside the display area of the menu,
the menu cursor is positioned to that item.

Each of the above requests will be explained in the following lines with several
examples whenever appropriate.

17.4. Menu Windows

Every menu created is associated with a window and a sub window. The menu window
displays any title or border associated with the menu. The menu sub window
displays the menu items currently available for selection. But we didn't specify
any window or sub window in the simple example. When a window is not specified,
stdscr is taken as the main window, and then menu system calculates the sub
window size required for the display of items. Then items are displayed in the
calculated sub window. So let's play with these windows and display a menu with
a border and a title.

This example creates a menu with a title, border, a fancy line separating title
and the items. As you can see, in order to attach a window to a menu the
function set_menu_win() has to be used. Then we attach the sub window also. This
displays the items in the sub window. You can also set the mark string which
gets displayed to the left of the selected item with set_menu_mark().

17.5. Scrolling Menus

If the sub window given for a window is not big enough to show all the items,
then the menu will be scrollable. When you are on the last item in the present
list, if you send REQ_DOWN_ITEM, it gets translated into REQ_SCR_DLINE and the
menu scrolls by one item. You can manually give REQ_SCR_ operations to do
scrolling. Let's see how it can be done.

This program is self-explanatory. In this example the number of choices has been
increased to ten, which is larger than our sub window size which can hold 6
items. This message has to be explicitly conveyed to the menu system with the
function set_menu_format(). In here we specify the number of rows and columns we
want to be displayed for a single page. We can specify any number of items to be
shown, in the rows variables, if it is less than the height of the sub window.
If the key pressed by the user is a PAGE UP or PAGE DOWN, the menu is scrolled a
page due to the requests (REQ_SCR_DPAGE and REQ_SCR_UPAGE) given to
menu_driver().

17.6. Multi Columnar Menus

In the above example you have seen how to use the function set_menu_format(). I
didn't mention what the cols variable (third parameter) does. Well, If your sub
window is wide enough, you can opt to display more than one item per row. This
can be specified in the cols variable. To make things simpler, the following
example doesn't show descriptions for the items.

Watch the function call to set_menu_format(). It specifies the number of columns
to be 3, thus displaying 3 items per row. We have also switched off the showing
descriptions with the function menu_opts_off(). There are couple of functions
set_menu_opts(), menu_opts_on() and menu_opts() which can be used to manipulate
menu options. The following menu options can be specified.

O_ONEVALUE
Only one item can be selected for this menu.
O_SHOWDESC
Display the item descriptions when the menu is
posted.
O_ROWMAJOR
Display the menu in row-major order.
O_IGNORECASE
Ignore the case when pattern-matching.
O_SHOWMATCH
Move the cursor to within the item name while pat­
tern-matching.
O_NONCYCLIC
Don't wrap around next-item and previous-item,
requests to the other end of the menu.

All options are on by default. You can switch specific attributes on or off with
menu_opts_on() and menu_opts_off() functions. You can also use set_menu_opts()
to directly specify the options. The argument to this function should be a OR ed
value of some of those above constants. The function menu_opts() can be used to
find out a menu's present options.

17.7. Multi Valued Menus

You might be wondering what if you switch off the option O_ONEVALUE. Then the
menu becomes multi-valued. That means you can select more than one item. This
brings us to the request REQ_TOGGLE_ITEM. Let's see it in action.

Whew, A lot of new functions. Let's take them one after another. Firstly, the
REQ_TOGGLE_ITEM. In a multi-valued menu, the user should be allowed to select
or un select more than one item. The request REQ_TOGGLE_ITEM toggles the present
selection. In this case when space is pressed REQ_TOGGLE_ITEM request is sent to
menu_driver to achieve the result.

Now when the user presses <ENTER> we show the items he presently selected.
First we find out the items associated with the menu using the function
menu_items(). Then we loop through the items to find out if the item is selected
or not. The function item_value() returns TRUE if an item is selected. The
function item_count() returns the number of items in the menu. The item name can
be found with item_name(). You can also find the description associated with an
item using item_description().

17.8. Menu Options

Well, by this time you must be itching for some difference in your menu, with
lots of functionality. I know. You want Colors !!!. You want to create nice
menus similar to those text mode dos games. The functions
set_menu_fore() and set_menu_back() can be used to change the attribute of the
selected item and unselected item. The names are misleading. They don't change
menu's foreground or background which would have been useless.

The function set_menu_grey() can be used to set the display attribute for the
non-selectable items in the menu. This brings us to the interesting option for
an item the one and only O_SELECTABLE. We can turn it off by the function
item_opts_off() and after that that item is not selectable. It's like a grayed
item in those fancy windows menus. Let's put these concepts in practice with
this example

17.9. The useful User Pointer

We can associate a user pointer with each item in the menu. It works the same
way as user pointer in panels. It's not touched by menu system. You can store
any thing you like in that. I usually use it to store the function to be
executed when the menu option is chosen (It's selected and may be the user
pressed <ENTER>);