Copyright (c) Hyperion Entertainment and contributors.
Intuition Menus
Contents
Intuition Menus
Menus are command and option lists associated with an application window that the user can bring into view at any time. These lists provide the user with a simple way to access features of the application without having to remember or enter complex character-based command strings.
The Intuition menu system handles all of the menu display without intervention from the application. The program simply submits an initialized list of data structures to Intuition and waits for menu events.
This section shows how to set up menus that allow the user to choose from your program's commands and options.
About Menus
Intuition's menu system provides applications with a convenient way to group together and display the commands and options available to the user. In most cases menus consist of a fixed list of text choices however this is not a requirement. Items in the menu list may be either graphic images or text, and the two types can be freely used together. The number of items in a menu can be changed if necessary.
Types of Menu Choices
Menu choices represent either actions or attributes. Actions are analogous to verbs. An action is executed and then forgotten. Actions include such things as saving and printing files, calculating values and displaying information on the program.
Attributes are analogous to adjectives. An attribute stays in effect until canceled. Attributes include such things as pen type, color, draw mode and numeric format.
For instance, in a word processor, menus could be used to control the following types of features:
- File loading and saving (action).
- Editing functions (action).
- Formatting preferences (attributes).
- Printing functions (action).
- Current font and style (attributes).
Menus can be set up such that some attribute items are mutually exclusive (selecting an attribute cancels the effects of one or more other attributes). For example, a drawing or graphics package may only allow one color to be active at a time; selecting a color cancels the previous active color.
The program can also allow a number of attributes to be in effect at the same time. A common example of this appears in most word processing programs, where the text style may be bold, italic or underlined. Selecting bold does not rule out italic or underlined, in fact, all three may be active at the same time.
The Menu System
To activate the menu system, the user presses the menu button (the right mouse button). This displays the menu bar in the screen's title area. The menu bar displays a list of topics (called menus) that have menu items associated with them (see figure). The menu bar and menu items only remain visible while the menu button is held down.
When the mouse pointer is moved onto one of the menus in the menu bar, a list of menu items appears below the menu. The user can move the pointer within the list of menu items while holding down the menu button. A menu item will highlight when the pointer is over it and, if the item has a sub-item list, that list will be displayed.
The specific menu that is displayed belongs to the active window. Changing the active window will change the menu bar and the choices available to the user.
Unlike some other systems, the Amiga has no "standard menu" that appears in every menu bar. In fact, a window need not have any menus at all, thus holding down the mouse menu button does not guarantee the appearance of a menu bar. Although there is no "standard menu", the AmigaOS development team does have a well-defined set of standards for menu design. These standards are covered in the Amiga User Interface Style Guide.
Selecting Menu Items
To select a single menu item, the user releases the menu button when the pointer is over the desired item. Intuition can notify your program whenever the user makes a menu selection by sending an IDCMP message to your window's UserPort. Your application is then responsible for carrying out the action associated with the menu item selected. Action items lead to actions taken by the program while attribute items set values in the program for later reference.
Menu selection is restricted to the most subordinate item. Top level menus are never selected. A menu item can be selected as long as it has no sub-items, and a sub-item may always be selected. (Of course, disabled menu items and sub-items cannot be selected.)
Intuition menus allow the user to select multiple items by:
- Pressing and releasing the select button (left mouse button) without releasing the menu button. This selects the item and keeps the menus active so that other items may be selected.
- Holding down both mouse buttons and sliding the pointer over several items. This is called drag selecting. All items highlighted while dragging are selected.
Drag selection, single selection with the select button and releasing the mouse button over an item can all be combined in a single operation. Any technique used to select a menu item is also available to select a menu sub-item.
Menu Item Imagery
Menu items can be graphic images or text. There is no conceptual difference between menus that display text and menus that display images, in fact, the two techniques may be used together. The examples in this chapter use text based menus to avoid the extra code required to define images.
When the user positions the pointer over an item, the item can be highlighted through a variety of techniques. These techniques include a highlighted box around the selected item, complementing the entire item and replacing the item with an alternate image or alternate text.
Attribute items can have an image rendered next to them, usually a checkmark, to indicate whether they are in effect or not. The checkmark is positioned to the left of the item. If the checkmark is present, the attribute is on. If not, the attribute is off.
On the right side of menu items, command key alternatives may be displayed. Command key alternatives allow the user to make menu selections with the keyboard instead of the mouse. This is done by holding down the right Amiga key and then pressing the single character command key alternative listed next to the menu item. Command key alternatives appear as a reverse video, fancy "A", followed by the single character command key.
Menu items or whole menus may be enabled or disabled. Disabling an item prevents the user from selecting it. Disabled items are ghosted (overwritten with a pattern of dots making the image less distinct) in order to distinguish them from enabled items.
Menu help allows the application to be notified when the user presses the help key at the same time the menu system is activated. This allows applications to provide a help feature for every item in its menus. Menu help may be requested on any level of a menu.
Menu Limitations
Menus are not layered so they lock the screen while they are displayed. While the screen is locked, applications cannot render graphics into that screen; any rendering will be suspended until the menus are no longer displayed.
Menus can only display a limited number of choices. Each window may have up to 31 menus, each menu may have up to 63 items, and each item may have up to 31 sub-items.
Menus always appear at the top of the screen and cannot be repositioned or sized by the user. Moving the pointer to the menu bar may be inconvenient or time consuming for the user. (This is why it is generally a good idea to provide keyboard alternatives for menu items.) If some application has a function that the user will be performing repeatedly, it may be better to use a series of gadgets in the window (or a separate window) rather than a menu item.
Alternatives to Menus
You may want to use a requester or a window as an alternative to menus. A requester can function as a "super menu" using gadgets to provide the commands and options of a menu but with fewer restrictions on their placement, size and layout. See Intuition Requesters for more information.
A window, also, could be substituted for a menu where an application has special requirements. Unlike menus, windows allow layered operations so that commands and options can be presented without forcing all other window output in the active screen to halt.
Windows may be sized, positioned and depth arranged. This positioning flexibility allows the user to make other parts of the screen and other windows visible while they are entering data or selecting operations. The ability to access or view other data may be important in the user's choice of actions or attributes. See Intuition Windows for more details.
Setting Up Menus
The application does not have to worry about handling the menu display. The menus are simply submitted to Intuition and the application waits for Intuition to send messages about the selection of menu items. These messages, along with the data in the menu structures, give the application all the information required for the processing of the user actions.
Menus should be set up with the GadTools library. Since GadTools makes menu set up easier and handles much of the detail work of menu processing (including adjusting to the current font selection), it should be used whenever possible.
Under older versions of the OS, GadTools was not available. To set up menus that work with these older systems, you use the Menu and MenuItem structures. In general, for each menu in the menu bar, you declare one instance of the Menu structure. For each item or sub-item within a menu, you declare one instance of the MenuItem structure. Text-based menus like the kind used in this chapter require an additional IntuiText structure for each menu, menu item and sub-item. All these structures are defined in <intuition/intuition.h>.
The data structures used for menus are linked together to form a list known as a menu strip. For all the details of how the structures are linked and for listings of Menu and MenuItem, see the "Menu Structures" section later in this chapter.
Submitting and Removing Menu Strips
Once the application has set up the proper menu structures, linked them into a list and attached the list to a window, the menu system completely handles the menu display. The menu strip is submitted to Intuition and attached to the window by calling the function SetMenuStrip().
BOOL SetMenuStrip( struct Window *window, struct Menu *menu );
SetMenuStrip() always returns TRUE. This function can also be used to attach a single menu strip to multiple windows by calling SetMenuStrip() for each window (see below).
Any menu strip attached to a window must be removed before the window is closed. To remove the menu strip, call ClearMenuStrip().
VOID ClearMenuStrip( struct Window *window );
The menu example below demonstrates how to use these functions with a simple menu strip.
Simple Menu Example
Menu concepts are explained in great detail later in this chapter; for now though it may be helpful to look at an example. Here is a very simple example of how to use the Intuition menu system. The example shows how to set up a menu strip consisting of a single menu with five menu items. The third menu item in the menu has two sub-items.
The example works with all versions of the Amiga OS however it assumes that the Workbench screen is set up with the the Topaz 8 ROM font. If the font is different, the example will exit immediately since the layout of the menus depends on having a monospaced font with 8*8 pixel characters.
/* ** simplemenu.c: how to use the menu system with a window */ #include <exec/types.h> #include <exec/memory.h> #include <graphics/text.h> #include <intuition/intuition.h> #include <intuition/intuitionbase.h> #include <proto/exec.h> #include <proto/graphics.h> #include <proto/intuition.h> #include <string.h> /* These values are based on the ROM font Topaz8. Adjust these */ /* values to correctly handle the screen's current font. */ #define MENWIDTH (56+8) /* int32est menu item name * font width */ /* + 8 pixels for trim */ #define MENHEIGHT (10) /* Font height + 2 pixels */ struct GraphicsIFace *IGraphics = NULL; struct IntuitionIFace *IIntuition = NULL; /* To keep this example simple, we'll hard-code the font used for menu */ /* items. Algorithmic layout can be used to handle arbitrary fonts. */ /* GadTools provides font-sensitive menu layout. */ /* Note that we still must handle fonts for the menu headers. */ struct TextAttr Topaz80 = { "topaz.font", 8, 0, 0 }; struct IntuiText menuIText[] = { { 0, 1, JAM2, 0, 1, &Topaz80, "Open...", NULL }, { 0, 1, JAM2, 0, 1, &Topaz80, "Save", NULL }, { 0, 1, JAM2, 0, 1, &Topaz80, "Print \273", NULL }, { 0, 1, JAM2, 0, 1, &Topaz80, "Draft", NULL }, { 0, 1, JAM2, 0, 1, &Topaz80, "NLQ", NULL }, { 0, 1, JAM2, 0, 1, &Topaz80, "Quit", NULL } }; struct MenuItem submenu1[] = { { /* Draft */ &submenu1[1], MENWIDTH-2, -2 , MENWIDTH, MENHEIGHT, ITEMTEXT | MENUTOGGLE | ITEMENABLED | HIGHCOMP, 0, (APTR)&menuIText[3], NULL, NULL, NULL, NULL }, { /* NLQ */ NULL, MENWIDTH-2, MENHEIGHT-2, MENWIDTH, MENHEIGHT, ITEMTEXT | MENUTOGGLE | ITEMENABLED | HIGHCOMP, 0, (APTR)&menuIText[4], NULL, NULL, NULL, NULL } }; struct MenuItem menu1[] = { { /* Open... */ &menu1[1], 0, 0, MENWIDTH, MENHEIGHT, ITEMTEXT | MENUTOGGLE | ITEMENABLED | HIGHCOMP, 0, (APTR)&menuIText[0], NULL, NULL, NULL, NULL }, { /* Save */ &menu1[2], 0, MENHEIGHT , MENWIDTH, MENHEIGHT, ITEMTEXT | MENUTOGGLE | ITEMENABLED | HIGHCOMP, 0, (APTR)&menuIText[1], NULL, NULL, NULL, NULL }, { /* Print */ &menu1[3], 0, 2*MENHEIGHT , MENWIDTH, MENHEIGHT, ITEMTEXT | MENUTOGGLE | ITEMENABLED | HIGHCOMP, 0, (APTR)&menuIText[2], NULL, NULL, &submenu1[0] , NULL }, { /* Quit */ NULL, 0, 3*MENHEIGHT , MENWIDTH, MENHEIGHT, ITEMTEXT | MENUTOGGLE | ITEMENABLED | HIGHCOMP, 0, (APTR)&menuIText[5], NULL, NULL, NULL, NULL }, }; /* We only use a single menu, but the code is generalizable to */ /* more than one menu. */ #define NUM_MENUS 1 STRPTR menutitle[NUM_MENUS] = { "Project" }; struct Menu menustrip[NUM_MENUS] = { { NULL, /* Next Menu */ 0, 0, /* LeftEdge, TopEdge, */ 0, MENHEIGHT, /* Width, Height, */ MENUENABLED, /* Flags */ NULL, /* Title */ &menu1[0] /* First item */ } }; struct NewWindow mynewWindow = { 40,40, 300,100, 0,1, IDCMP_CLOSEWINDOW | IDCMP_MENUPICK, WFLG_DRAGBAR | WFLG_ACTIVATE | WFLG_CLOSEGADGET, NULL,NULL, "Menu Test Window", NULL,NULL,0,0,0,0,WBENCHSCREEN }; /* our function prototypes */ VOID handleWindow(struct Window *win, struct Menu *menuStrip); /* Main routine. */ /* */ int main(int argc, char **argv) { struct Window *win=NULL; uint16 left, m; /* Open the Graphics Library */ struct Library *GfxBase = IExec->OpenLibrary("graphics.library", 0); IGraphics = (struct GraphicsIFace*)IExec->GetInterface(GfxBase, "main", 1, NULL); struct Library *IntuitionBase = IExec->OpenLibrary("intuition.library", 0); IIntuition = (struct IntuitionIFace*)IExec->GetInterface(IntuitionBase, "main", 1, NULL); if (IGraphics && IIntuition) { if ( win = IIntuition->OpenWindow(&mynewWindow) ) { left = 2; for (m = 0; m < NUM_MENUS; m++) { menustrip[m].LeftEdge = left; menustrip[m].MenuName = menutitle[m]; menustrip[m].Width = TextLength(&win->WScreen->RastPort, menutitle[m], strlen(menutitle[m])) + 8; left += menustrip[m].Width; } if (IIntuition->SetMenuStrip(win, menustrip)) { handleWindow(win, menustrip); IIntuition->ClearMenuStrip(win); } IIntuition->CloseWindow(win); } } IExec->DropInterface((struct Interface*)IIntuition); IExec->CloseLibrary(IntuitionBase); IExec->DropInterface((struct Interface*)IGraphics); IExec->CloseLibrary(GfxBase); return 0; } /* ** Wait for the user to select the close gadget. */ VOID handleWindow(struct Window *win, struct Menu *menuStrip) { struct IntuiMessage *msg; BOOL done; uint32 class; uint16 menuNumber; uint16 menuNum; uint16 itemNum; uint16 subNum; struct MenuItem *item; done = FALSE; while (FALSE == done) { /* we only have one signal bit, so we do not have to check which ** bit broke the Wait(). */ IExec->Wait(1L << win->UserPort->mp_SigBit); while ( (FALSE == done) && (msg = (struct IntuiMessage *)IExec->GetMsg(win->UserPort))) { class = msg->Class; if(class == IDCMP_MENUPICK) menuNumber = msg->Code; switch (class) { case IDCMP_CLOSEWINDOW: done = TRUE; break; case IDCMP_MENUPICK: while ((menuNumber != MENUNULL) && (!done)) { item = IIntuition->ItemAddress(menuStrip, menuNumber); /* process this item ** if there were no sub-items attached to that item, ** SubNumber will equal NOSUB. */ menuNum = MENUNUM(menuNumber); itemNum = ITEMNUM(menuNumber); subNum = SUBNUM(menuNumber); /* Note that we are printing all values, even things ** like NOMENU, NOITEM and NOSUB. An application should ** check for these cases. */ IDOS->Printf("IDCMP_MENUPICK: menu %ld, item %ld, sub %ld\n", menuNum, itemNum, subNum); /* This one is the quit menu selection... ** stop if we get it, and don't process any more. */ if ((menuNum == 0) && (itemNum == 4)) done = TRUE; menuNumber = item->NextSelect; } break; } IExec->ReplyMsg((struct Message *)msg); } } }
Disabling Menu Operations
If an application does not use menus at all, it may set the WFLG_RMBTRAP flag, which allows the program to trap right mouse button events for its own use.
By setting the WFLG_RMBTRAP flag with the WA_Flags tag when the window is opened, the program indicates that it does not want any menu operations at all for the window. Whenever the user presses the right button while this window is active, the program will receive right button events as normal IDCMP_MOUSEBUTTONS events.
Changing Menu Strips
Direct changes to a menu strip attached to a window may be made only after the menu strip has been removed from the window. Use the ClearMenuStrip() function to remove the menu strip. It may be added back to the window after the changes are complete.
Major changes include such things as adding or removing menus, items and sub-items; changing text or image data; and changing the placement of the data. These changes require the system to completely re-layout the menus.
An additional function, ResetMenuStrip(), is available to let the application make small changes to the menus without the overhead of SetMenuStrip(). Only two things in the menu strip may be changed before a call to ResetMenuStrip(), they are: changing the CHECKED flag to turn checkmarks on or off, and changing the ITEMENABLED flag to enable/disable menus, items or sub-items.
BOOL ResetMenuStrip( struct Window *window, struct Menu *menu );
ResetMenuStrip() is called in place of SetMenuStrip(), and may only be called on menus that were previously initialized with a call to SetMenuStrip(). As with SetMenuStrip(), the menu strip must be removed from the window before calling ResetMenuStrip(). Note that the window used in the ResetMenuStrip() call does not have to be the same window to which the menu was previously attached. The window, however, must be on a screen of the same mode to prevent the need for recalculating the layout of the menu.
If the application wishes to attach a different menu strip to a window that already has an existing menu strip, the application must call ClearMenuStrip() before calling SetMenuStrip() with the new menu strip.
The flow of events for menu operations should be:
OpenWindowTagList()
.
SetMenuStrip()
.
Zero or more iterations of ClearMenuStrip() and SetMenuStrip() or ResetMenuStrip().
ClearMenuStrip()
.
CloseWindow()
.
Sharing Menu Strips
A single menu strip may be attached to multiple windows in an application by calling SetMenuStrip() for each window. All of the windows must be on the same screen for this to work. Since menus are always associated with the active window on a given screen, and since only one window may be active on a screen at a time, only one window may display the shared menu strip at any given time.
When multiple windows share a single menu strip, they will all "see" the same state of the menus, that is, changes made to the menu strip from one window will still exist when a new window is activated. If the application wishes to share menu strips but to have a different flag and enabled status for each window, the program may watch IDCMP_ACTIVEWINDOW for the windows and modify the menu strip to match the active window's requirements at that point. In addition, the application must also set IDCMP_MENUVERIFY to insure that the user can't access the menus of a newly activated window before the application can process the IDCMP_ACTIVEWINDOW message.
ResetMenuStrip() may also be used to set the menus for the multiple windows as long as SetMenuStrip() is used first to attach the menu strip to any one window and no major changes are made to the menu strip before the calls to ResetMenuStrip() on subsequent windows.