Section 10 Home page Section 12

11. Menus

The menu bar is a convenient way of interacting with our programs. A menu bar is defined within an RSC file, which is a file containing all the resources, such as menu bars and dialogs, used by our program. The RSC file is created by an external program: there are several available, but I use Resource Master 3.65. When started, our program must load in its RSC file, and access the required information to show menu bars and dialogs.

Menus, in GEM, typically appear at the top of the screen. The menu bar can contain several menus. Each menu has a title, and can contain a number of menu items. Each menu item may have an associated key command. Also, menu items may be disabled (meaning they cannot be selected), or have a check mark showing. Menus can also contain separator items, which are horizontal lines to divide the menu into groups. The image below illustrates these elements:

images/menu.png

Menus also provide access to the desk accessories. The convention is that the first menu item, on the left, refers to the program name, and contains a single item, which opens a dialog showing information about the program. This first menu item is followed by a separator, and then a list of desk accessories (or "Clients" in Mint).

Interactions with the menu are simple: when a menu item is clicked on with the mouse, a message is sent to the program indicating that the menu has been used. This message contains information about the menu item that was clicked.

Keyboard shortcuts are also an integral part of using menus. In order to handle these, we need to discuss more about event handling, which we do in the following section.

11.1. The RSC and rsh files

Before our program can use a menu, we need to build the menu in a resource construction program (such as Resource Master) and save it as a RSC file. Our program also needs a way of associating the clicked on menu item with the information sent in the message event. This information is contained in a set of symbol definitions, saved by the resource constructon program in a .rsh file (or equivalent).

Tip
It is standard practice to name our .RSC file using the program name. Do not use your program name for a .C source file. For example, if your program is sokoban, we will have sokoban.rsc. Do not name one of your source files sokoban.c. ResourceMaster, for example, will export a .c file if you choose to build a desk accessory, and this will overwrite any source files of the same name. For safety, only use your program name as the .PRG name in your project file, and as the .RSC name.

As we create each menu item, we associate with that menu item a symbol, used to name it. When we export the C header file, we get a file containing symbol definitions for each menu item. For Resource Master, the output looks something like the following. Note that the symbol ABOUT attached to the About menu item has been prefixed with the symbol for the main menu itself.

 /*  Resource C-Header-file v1.95 for ResourceMaster v2.06&up by ARDISOFT  */

#define MAIN_MENU 0  /* menu                                    <1> */
#define MAIN_MENU_ABOUT 9  /* STRING in tree MAIN_MENU          <2> */
  1. The symbol for the main menu itself.

  2. The symbol for the About item on the main menu.

Before we can use the RSC file, we need to include the .rsh file in our source code. We do this by adding a line in "windows.h", for example:

#include "version7.rsh"

11.2. Showing a Menu

Before we can show the menu, our program must open the .RSC file. We must check the file has opened successfully before proceeding. Having done this, we can locate the menu bar and show it. Once our program has finished, we should remove the menu bar. Our start_program function is accordingly modified, as follows:

void start_program (void) {
        /* DECLARE LOCAL VARIABLES */

        if (!rsrc_load ("VERSION7.rsc")) {                      // <1>
                form_alert (1, "[1][version7 .rsc file missing!][OK]");
        } else {
                OBJECT * menu_addr;                             // <2>

                /* 1. install the menu bar */
                rsrc_gaddr (R_TREE, MAIN_MENU, &menu_addr);     // <3>
                menu_bar (menu_addr, true);                     // <4>

                /* 2. OPEN WINDOWS ETC */

                /* 3. process events for our window */
                event_loop (menu_addr, &wd1);                   // <5>

                /* 4. remove the menu bar */
                menu_bar (menu_addr, false);                    // <6>

                /* 5. CLOSE WINDOWS AND CLEAN UP */
        }
}
  1. Attempt to load the RSC file. If it fails to load, display a dialog, and then abort the program.

  2. Create a local variable to hold the address of the menu.

  3. Retrieve the menu from the RSC data. We provide the name of the menu MAIN_MENU, and the variable to store the menu’s address.

  4. Display the menu bar for our application.

  5. menu_addr is also passed to the event_loop.

  6. Remove the menu bar, which frees up its resources.

Note
rsrc_load likes the filename in capital letters.

11.3. Responding to Menu Events

Menu events are passed to our program using the MN_SELECTED event type. msg_buf[4] contains the reference to the actual menu selected. These references are defined in the .rsh file.

Within the event_loop function switch function, we add the following case to respond to menu events:

        case MN_SELECTED: /* menu selection */
                do_menu (wd, msg_buf[4]);                       // <1>
                /* return menu to normal */
                menu_tnormal (menu_addr, msg_buf[3], true);     // <2>
                break;
  1. Call out to do_menu with the selected menu item.

  2. Once the menu event has been dealt with, return the menu display to normal.

Notice how GEM highlights the selected menu item whilst the event is carried out. Our program is responsible for returning the item to normal once it has finished.

The do_menu function simply dispatches to a function to deal with each of the possible menu items.

void do_menu (struct win_data * wd, int menu_item) {
        switch (menu_item) {

                case MAIN_MENU_ABOUT:           // <1>
                        do_about ();
                        break;
        }
}
  1. The case statement uses the name for the menu item as defined in the RSC file.

The do_about function displays a simple information dialog - we discuss dialogs in a later section.

How about the QUIT option? When the user selects quit, we don’t have to do anything except exit the event loop. This is easily done in the while condition:

        } while (!(msg_buf[0] == MN_SELECTED && msg_buf[4] == MAIN_MENU_QUIT)); // <1>
  1. Loop terminates when it has a MN_SELECTED event which is Quit.

11.4. Controlling the Menu Items

An individual menu item can be enabled or disabled. A disabled menu item cannot be selected, and is shown greyed out on the menu bar. Whether a menu item is enabled or not is controlled using:

menu_ienable (menu_addr, OBJECT, flag);

where

  • menu_addr is the address of the menu;

  • OBJECT is the reference to the menu item; and

  • flag is true to enable or false to disable the menu item.

Each menu item can also show an optional check (or tick) mark beside it. The code to control this takes the same parameters as menu_ienable:

menu_icheck (menu_addr, OBJECT, flag);

Finally, it is possible to change the text showing on a menu item, with the call:

menu_text (menu_addr, OBJECT, str); // <1>
  1. str must be a statically allocated string. Remember to keep two spaces at the start of the string.

The sample program does not have a good reason to use checked menu items or to disable items, so there is a menu provided just to try out these options. One menu item controls whether the other is enabled or not, and shows a check mark if it is enabled. For good measure, the altered menu item also has its text changed. We store the state for this as a global variable, menu_state; in a real application the state will likely depend on the top-most window’s contents, and so be stored within win_data.

The action is handled directly in do_menu:

        case MAIN_MENU_SWITCH:
                menu_state = !menu_state;                               // <1>
                menu_icheck (menu_addr, MAIN_MENU_SWITCH, menu_state);  // <2>
                menu_ienable (menu_addr, MAIN_MENU_DUMMY, menu_state);  // <3>
                menu_text (menu_addr, MAIN_MENU_DUMMY,                  // <4>
                           (menu_state ? "  Enabled" : "   Disabled"));
                break;
  1. Invert the state.

  2. Set the status of the SWITCH menu item’s check sign.

  3. Set the enabled/disabled status of the DUMMY menu item.

  4. Change the text on the DUMMY menu item.