User Interfaces


Point-And-Shoot Menus

John Matsche


John J. Matsche has a B.S and an M.B.A. from the University of Central Florida. His experience includes ten years of systems analysis on various platforms. His language experience includes C++, Pascual, Assembler, Algol, Cobol, and Basic. You can contact John at 4662 Millhaven Rd., Martinez, GA 30907.

In the beginning, programming a menu screen was easy. You presented a list of program options to the user. The user would select an option by entering the corresponding number and pressing the enter key. It was a simple programming task, usually requiring nothing more than a looping series of write statements followed by a read, then some sort of a branching statement.

When I bought my first copy of Lotus 1-2-3, I realized the inadequacies of this algorithm. The point-and-shoot menus that run along the top of my spreadsheet do more than provide a raw list of program options. These menus also describe each option. Once selected, it is easy to maneuver down the hierarchy, and even easier to back out. In short, the point-and-shoot menu system is more intuitive. More important, at that stage in my career, the point and shoot system looked more polished and professional.

The Lotus menu system has many variations. Basically, it consists of a list of words or short narrative that describes an option. The selected option, is highlighted in some fashion, usually by display in reverse video. When the user presses the right arrow key, the next option becomes the selected option. The left arrow key selects the previous option. The user points to an option by pressing the arrow keys, then invokes the option by pressing the enter key — hence the name "point and shoot." If the user presses the escape key, the menu backs out. No option is selected and control returns to some previous state.

I set out to develop an algorithm that would implement point-and-shoot menus as efficiently as possible. At the same time, I wanted it to be generic enough to work with any application program. C is an ideal programming language for this type of project. It is low-level enough to provide the speed necessary for quick screen displays. It is compact enough to have negligible impact on an application programs's size. For this project, I used Borland's Turbo C++ compiler running on an IBM-compatible 386 using MS-DOS v4.01.

Implementation

I make use of three video modes that I call normal, reverse, and highlight. These terms are left over from the days of monochrome displays, but are also applicable to color systems. Simply choose your own favorite color scheme for each mode. In this implementation, reverse video implies that both background and foreground colors are reversed from normal. Highlight video uses the intense foreground color of the normal video mode.

The program shown in Listing 1 contains the complete implementation. I added a main function to demonstrate the execution of the menu system. By removing main, you make the program a compilable module that can easily be linked into any application program. To complete the conversion from program to module, you should create a separate header file containing a prototype of the menu function. The remaining functions should stay relatively hidden from the application.

The #defines at the top of the listing provide logical names for the various keys that menu recognizes. Some of the keys can be read directly by the getch function. For example, the enter key will always generate an ASCII 13 (hex 0x0d). Other keys, such as the function keys and the arrows, do not generate a single character code when pressed. To recognize these keys, you must look at something called a scan code.

From a software point of view, think of a scan code as an extended return value for special keys. Whenever a special key such as a function or arrow key is pressed, getch returns an ASCII NUL (hex 0x00). The next call to getch returns the scan code for the pressed key. Note that this does not mean you must press the special key twice. It simply means that whenever you press a special key two characters are loaded into the keyboard buffer instead of just one. In fact, if you were to use getch as a pause mechanism, and you didn't test for scan codes, you might inadvertently pick up the scan code later on in another getch call.

As previously mentioned, main is a demonstration function that is not a part of the menu logic. It illustrates how easy the menu is to call. By looking at main, you see that menu requires only two parameters. The first points to the first element of an array of pointers to the menu items to be displayed. Note that in this implementation, each menu item begins with a different letter. This restriction allows you to select a menu item by pressing this letter on the keyboard. The array is terminated with a null pointer. You don't need to tell menu how many items are being passed to it. The second parameter to menu is a Boolean value (1 means true, 0 means false) that specifies whether or not a horizontal format is to be used.

The rest of the setup sets the desired colors and formats the screen. The primary loop in main displays which item the user chose and positions the cursor for the next menu display. The loop terminates when the user presses Q for quit.

Five functions carry out the menu's actual implementation: show, findletter, init, keypress, and menu. show displays one menu item on the screen. findletter searches the list of items for the letter pressed by the user. init sets up screen locations for displaying items, keypress handles all keyboard input, and menu controls the show.

menu initializes screen colors and locations, displays all of the items, gets user input, and loops until the user wants to exit. To initialize screen locations, call the function init. The gettextinfo function, included with Turbo C++, is called to provide menu with screen information as stored in the text_info type structure called info. This structure allows you to derive the current colors and cursor position for reference. Next, menu turns off the cursor to present a cleaner looking menu and calls show repeatedly to display each menu item.

The main loop of menu displays the currently selected menu item in reverse video, then waits for the user to press a key. Once a key is pressed, the function tests for a scan code. menu then displays the current item in normal video, effectively de-selecting it from the user's point of view. Finally, the function calls keypress to act on the selected key. The value returned by keypress determines whether or not the loop is repeated.

The last task menu must complete before exiting is to turn the cursor back on and return the user's selection to the calling function.

Screen locations, as stated earlier, are set up by init and used by show. Setting up the locations depends on which format, vertical or horizontal, is specified. For vertical formats, screen location is simply based on the starting row plus the current index. For horizontal formats, the position is calculated from the starting column location plus the length of the previous menu items. Because the horizontal format involves more calculation, it is only done once in init, then it is stored in an array.

show adds the starting row and column information, contained in the x and y variables, to an offset based on the chosen format (horizontal or vertical). It sets the video mode to highlight by setting the high bit of the foreground color attribute. It then displays the first character, returns video mode to normal, and displays the rest of the menu item.

keypress has been set up separately to isolate the logic necessary to handle the user's input, so you can easily add new key handling ability to the function's repertoire. keypress is responsible for calculating which menu item will be made current based on which key the user pressed. It does this using an index value (variable i) that can vary from zero to the maximum number of items less one. The function returns --1 as an index value when the user has pressed the enter key. menu then knows to quit and return whichever option was previously current to the calling function. Note that keypress returns an index value instead of the menu item letter. keypress was implemented to return an index value so show will easily know where to display the next menu item.

The last function to discuss is findletter. It is called from keypress whenever the user presses a key that is not specifically handled. findletter loops through the menu items looking for a match between the key pressed and the first character of the menu item. If found, it changes the current menu item index and passes it to the caller as a return value.

Collectively, these functions provide a simple and clean looking point-and-shoot menu system that is flexible enough to handle most applications' needs. At the same time, it is simple enough not to bog down the programmer in detail. There are, obviously, many enhancements that can be made to this system.

Enhancements

The basic point-and-shoot menu theme has many variations. For example, you can display a more descriptive explanation of each menu item on a separate line when it is selected. To do this you might want to pass to menu another array of pointers (call this the description array) which correspond on a one-for-one basis to the original array (list of menu items). If the first menu item is "Load," then the first element of the description array might be "Load a file from disk." show would display this description on some predesignated line. Many applications reserve the bottom line of the screen display for this purpose.

You could enhance this implementation by creating a more sophisticated approach to color selection. Even though the menu example described in this article is a stand-alone module, it would normally be incorporated as part of an overall module to handle screen formatting. Several other utility functions for windowing and formatting would be included that also require a way to specify color selection. One popular way to designate color selection is to define a color palette, implemented as an array of allowable color combinations for foreground and background. You might select normal, highlight, and reverse colors with a call like color(3,2,5) in which each parameter represents a specific index within the color palette.

This particular implementation of point-and-shoot menus relies on the user to provide a Quit option as one of the menu items. Many applications recognize the escape key as the key to press to back out of a menu without selecting anything. To add this capability to the example, modify keypress to return a special value for escape to menu. A value of --2 should be used since --1 designates the enter key. (Remember that keypress returns an index value, not a character.) In turn, menu returns some prearranged value such as ASCII 27, so the calling application can recognize a back-out request and act accordingly.

Many applications these days are demanding mouse support. The actual implementation would depend on the mouse support library that you use. A detailed discussion of mouse support is beyond the scope of this article, but its impact on the menu system is easy to describe.

Adding mouse support would involve replacing the getch logic used for detecting characters and scan codes with a separate function. This function would return the same character value or scan code based on the user's selection. The function would probably detect what the user selected using some sort of loop that continuously tests the keyboard and mouse devices for input. You need modify no other function.

This example of point-and-shoot menus was implemented in ANSI C to enhance its portability. Note that it's not strictly ANSI. For example, gettextinfo is available only for IBM-compatible systems. However, other systems do have other functions for performing similar tasks.

Implementing a point-and-shoot menu system with the object-oriented flavor of Turbo C++ has many advantages also. One of the greatest advantages is the ability of a function to inherit behavior. For example, if you want to enhance the behavior of function keypress (called method keypress in OOP) to recognize the escape key, you create a new function (method) that contains only the escape key handling logic. Everything else is already provided.

Summary

You can approach programming problems or ideas in a variety of ways. I've attempted to describe one generalized approach and several possible enhancements that I've had great success with in my own application programs.

The overall design goal of this menu system is to isolate into separate functions, as much as possible, discreet parts of the process such as keyboard handling, location calculations, and screen displays. This modular approach facilitates making future enhancements without completely rewriting the process every time.