Copyright (c) Hyperion Entertainment and contributors.
Difference between revisions of "Intuition Classic Menus"
Steven Solie (talk | contribs) |
Steven Solie (talk | contribs) |
||
Line 260: | Line 260: | ||
} |
} |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
− | |||
− | === 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 === |
=== Changing Menu Strips === |
Revision as of 19:17, 9 July 2015
Contents
- 1 About Menus
- 1.1 Setting Up Menus
- 1.2 Submitting and Removing Menu Strips
- 1.3 Simple Menu Example
- 1.4 Changing Menu Strips
- 1.5 Sharing Menu Strips
- 1.6 Menu Selection Messages
- 1.7 Menu Numbers
- 1.8 Help Key Processing in Menus
- 1.9 Menu Layout
- 1.10 About Menu Item Boxes
- 1.11 Attribute Items and the Checkmark
- 1.12 Toggle Selection
- 1.13 Mutual Exclusion
- 1.14 Managing the State of Checkmarks
- 1.15 Command Key Sequences
- 1.16 Enabling and Disabling Menus and Menu Items
- 1.17 Intercepting Normal Menu Operations
- 1.17.1 A Warning on the MENUSTATE Flag
- 1.17.2 Menu Verify
- 1.17.3 Menu Verify and Double-Menu Requesters
- 1.17.4 Shortcuts and IDCMP_MENUVERIFY
- 1.17.5 IDCMP_MENUVERIFY and Deadlock
- 1.17.6 Reasons to Use Menu Verify
- 1.17.7 Reasons Not to Use Menu Verify
- 1.17.8 Will I Ever Need to Cancel a Menu Operation?
- 2 Menu Data Structures
- 3 A Menu Example
- 4 Other Menu Macros
- 5 Function Reference
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.
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); } } }
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.
Menu Selection Messages
An input event is generated every time the user activates the menu system, either by pressing the mouse menu button or its keyboard equivalent (right Amiga Alt), or entering an appropriate command key sequence. The program receives a message of type IDCMP_MENUPICK detailing which menu items or sub-items were selected. Even if the user activates the menu system without selecting a menu item or sub-item, an event is generated.
Multi-Selection of Menu Items
Each activation of the menu system generates only a single event. The user may select none, one or many items using any of the selection techniques described above; still, only one event is sent.
The program finds out whether or not multiple items have been chosen by examining the field called NextSelect in the MenuItem structure. The selected items are chained together through this field. This list is only valid until the user next activates the menu system, as the items are chained together through the actual MenuItem structures used in the menu system. If the user reselects an item, the NextSelect field of that item will be overwritten.
In processing the menu events, the application should first take the appropriate action for the item selected by the user, then check the NextSelect field. If the number there is equal to the constant MENUNULL, there is no next selection. However, if it is not equal to MENUNULL, the user has selected another option after this one. The program should process the next item as well, by checking its NextSelect field, until it finds a NextSelect equal to MENUNULL.
The following code fragment shows the correct way to process a menu event:
struct IntuiMessage *msg; struct Menu *menuStrip; uint16 menuNumber = msg->Code; while (menuNumber != MENUNULL) { struct MenuItem *item = IIntuition->ItemAddress(menuStrip, menuNumber); /* process this item */ menuNumber = item->NextSelect; }
Intuition specifies which item or sub-item was selected in the IDCMP_MENUPICK event by using a shorthand code known as a menu number. Programs can locate the MenuItem structure that corresponds to a given menu number by using the ItemAddress() function. This function translates a menu number into a pointer to the MenuItem structure represented by the menu number.
struct MenuItem *ItemAddress( struct Menu *menuStrip, ULONG menuNumber );
This allows the application to gain access to the MenuItem structure and to correctly process multi-select events. Again, when the user performs multiple selection, the program will receive only one message of class IDCMP_MENUPICK. For the program to behave correctly, it must pay attention to the NextSelect field of the MenuItem, which will lead to the other menu selections.
There may be some cases in an application's logical flow where the selection of a menu item voids any further menu processing. For instance, after processing a "quit" menu selection, the application will, in general, ignore all further menu selections.
Menu Numbers
The menu numbers Intuition provides in the IDCMP_MENUPICK messages, describe the ordinal position of the menu in the linked list of menus, the ordinal position of the menu item beneath that menu, and (if applicable) the ordinal position of the sub-item beneath that menu item. Ordinal means the successive number of the linked items, in this case, starting from 0.
To determine which menus and menu items (sub-items are special cases of menu items) were selected, use the following macros:
MENUNUM(num) | Extracts the ordinal menu number from num. |
ITEMNUM(num) | Extracts the ordinal item number from num. |
SUBNUM(num) | Extracts the ordinal sub-item number from num. |
MENUNULL is the constant describing "no menu selection made." Likewise, NOMENU, NOITEM, and NOSUB describe the conditions "no menu chosen," "no item chosen" and "no sub-item chosen."
For example:
if (menuNumber == MENUNULL) /* no menu selection was made */ ; else { /* if there were no sub-items attached to that item, ** SubNumber will equal NOSUB. */ menuNum = MENUNUM(menuNumber); itemNum = ITEMNUM(menuNumber); subNum = SUBNUM(menuNumber); }
The menu number received by the program is always set to either MENUNULL or a valid menu selection. If the menu number represents a valid selection, it will always have at least a menu number and a menu item number. Users can never select the menu text itself, but they always select at least an item within a menu. Therefore, the program always gets at least the menu selected and the menu item selected. If the menu item selected has a sub-item, a sub-item number will also be received.
Just as it is not possible to select an entry in the menu bar, it is not possible to select a menu item that has attached sub-items. The user must select one of the options in the sub-item list before the program hears about the action as a valid menu selection.
Help Is Available |
---|
The restrictions on what can be selected do not apply to IDCMP_MENUHELP messages. Using menu help, a user can select any component of a menu, including the menu header itself. |
How Menu Numbers Really Work
The following is a description of how menu numbers really work. It should clarify why there are some restrictions on the number of menu components Intuition allows. Programs should not rely on the information given here to access menu number information directly though. Always use the Intuition menu macros to handle menu numbers.
For convenience you should use the menus supplied. For example, to extract the item number from the menu number, call the macro ITEMNUM(menu_number); to construct a menu number, call the macro FULLMENUNUM(menu, item, sub). See the section at the end of this chapter for a more complete description of the menu number macros.
Menu numbers are 16-bit numbers with 5 bits used for the menu number, 6 bits used for the menu item number, and 5 bits used for the sub-item number. The three numbers only have meaning when used together to determine the position of the item or sub-item selected.
The value "all bits on" means that no selection of this particular component was made. MENUNULL actually equals "no selection of any of the components was made" so MENUNULL always equals "all bits of all components on."
For example, suppose that the program gets back the menu number (in hexadecimal) 0x0CA0. In binary that equals:
Again, the application should not examine these numbers directly. Use the macros described above to ensure proper menu handling.
Help Key Processing in Menus
If the window is opened with the WA_MenuHelp tag, then user selection of the help key while menus are displayed will be detected.
When the user presses the Help key while using the menu system, the menu selection is terminated and an IDCMP_MENUHELP event is sent. The IDCMP_MENUHELP event is sent in place of the IDCMP_MENUPICK event, not in addition to it. IDCMP_MENUHELP never come as multi-select items, and the event terminates the menu processing session.
The routine that handles the IDCMP_MENUHELP events must be very careful; it can receive menu information that is impossible under IDCMP_MENUPICK. IDCMP_MENUHELP events may be sent for any menu, item or sub-item in the menu strip, regardless of its state or position in the list. The program may receive events for items that are disabled or ghosted. IDCMP_MENUHELP events may send the menu header number alone, or the menu and item numbers, or all three components regardless of the items linked to the selected menu or item. This is done because it is reasonable for a user to request help in a disabled item or a menu header. If the user requests menu help on a disabled menu item or sub-item, try to explain to the user why that item is disabled and what steps are necessary to enable it. For instance, pressing help while a menu header is highlighted will trigger an IDCMP_MENUHELP event with a code that has a valid number for the menu, then NOITEM and NOSUB (IDCMP_MENUPICK would receive MENUNULL in this case.)
The application should not take the action indicated by the IDCMP_MENUHELP event, it should provide the user with a description of the use or status of that menu. The application should never step through the NextSelect chain when it receives a IDCMP_MENUHELP event.
Menu Layout
The Amiga allows great flexibility in the specification of fonts for the display. Default fonts are chosen by the user to suit their particular requirements and display resolution. The application should, where possible, use one of the preferred fonts.
If the application did not open its own screen and completely specify the font for that screen, it must perform dynamic menu layout. This is because the Menu structure does not specify font. The menu header always uses the screen font and the program should update the size and position of these items at runtime to reflect the font.
The font for menu items may be specified in the MenuItem structure, allowing the programmer to hard code values for the font, size and position of these items. This is not recommended. A specific font, while ideal on one system, may be less than ideal on another display type. Use the preferred font wherever possible.
If the application does its own menu layout, it must use great care to handle the font in the menu strip and the font in each item or sub-item. The code should also keep items from running off the edges of the screen.
See the description of ItemFill in the section "MenuItem Structure" below for information on the positioning of multiple IntuiText or Image structures within the menu item.
Applications should use the GadTools library menu layout routines whenever possible, rather than performing their own layout. See GadTools Library for more details.
About Menu Item Boxes
The item box is the rectangle containing the menu items or sub-items.
The size and location of the item or sub-item boxes is not directly described by the application. Instead, the size is indirectly described by the placement of items and sub-items. When presented with a menu strip, Intuition first calculates the minimum size box required to hold the items. It then adjusts the box to ensure the menu display conforms to certain design philosophy constraints for items and sub-items.
The item box must start no further right than the leftmost position of the menu header's select box. It must end no further left than the rightmost position of the menu header's select box. The top edge of each item box must overlap the screen's title bar by one line. Each sub-item box must overlap its item's select box somewhere.
Attribute Items and the Checkmark
Attribute items are items that have two states: selected and unselected. In the menu system, these items are often represented as items with checkmarks. If the checkmark is visible, then the item (or attribute) is selected. Otherwise, the attribute is not selected.
Checkmarked items (attributes) may be toggle selected or mutually exclusive. Selecting a toggle selected item toggles its state; if it was selected, it will become unselected; and if it was unselected, it will become selected. Selecting a mutually exclusive item puts it in the selected state, while possibly clearing one or more other items, where it remains until it is cleared by the selection of some other item.
A menu item is specified as a checkmark item by setting the CHECKIT flag in the Flags variable of the item's MenuItem structure.
The program can initialize the state of the checkmark (checked or not) by presetting the item's CHECKED flag. If this flag is set when the menu strip is submitted to Intuition, then the item is considered selected and the checkmark will be drawn.
The program can use the default Intuition checkmark or provide a custom checkmark for the menus. To use a custom checkmark, the application must provide a pointer to the image with the WA_Checkmark tag when the window is opened. See Intuition Windows for details about supplying a custom checkmark.
The application must provide sufficient blank space at the left edge of the select box for the checkmark imagery. Constants are provided to standardize the space reserved in the menu for the checkmark. LOWCHECKWIDTH gives the amount of space required for checkmarks on low resolution screens and CHECKWIDTH gives space for all other screens.
These constants specify the space required by the default checkmarks (with a bit of space for aesthetic purposes). If the image would normally be placed such that the LeftEdge of the image without the checkmark is 5, the image should start at 5 + CHECKWIDTH if CHECKIT is set. Also, the select box must be made CHECKWIDTH wider than it would be without the checkmark. It is generally accepted on the Amiga that only checkmarked items should be indented by the size of the checkmark, other items are left justified within their column.
Toggle Selection
Some of the checkmarked menu items may be of the toggle select type. Each time the user accesses such an item, it changes state, selected or unselected. To make an attribute item toggle select, set both the CHECKIT and the MENUTOGGLE flags for that menu item. Of course, the CHECKED flag may be preset to the desired initial state.
Mutual Exclusion
Mutual exclusion allows the selection of an item to cause other items to become unselected.
For example, for a list of menu items describing the available sizes for a font, the selection of any size could unselect all other sizes. Use the MutualExclude variable in the MenuItem structure to specify other menu items to be excluded when the user selects an item. Exclusion also depends upon the CHECKED and CHECKIT flags of the MenuItem, as explained below.
If CHECKED is not set, then this item is available to be selected. If the user selects this item, the CHECKED flag is set and the checkmark will be drawn to the left of the item.
If the item selected has bits set in the MutualExclude field, the CHECKED flag is examined in the excluded items. If any item is currently CHECKED, its checkmark is erased, and its CHECKED flag is cleared.
Mutual exclusion pertains only to items that have the CHECKIT flag set. Attempting to exclude items that do not have the CHECKIT flag set has no effect.
Keep track of deselected items |
---|
It is up to the program to track internally which excluded items have been deselected. See the section "Enabling and Disabling Menus and Menu Items" below for more information. |
In the MutualExclude field, bit 0 refers to the first item in the item list, bit 1 to the second, bit 2 to the third, and so on.
In the text style example described above, selecting plain excludes any other style. The MutualExclude fields of the four items would look like this:
Plain | 0xFFFE |
Bold | 0x0001 |
Italic | 0x0001 |
Underline | 0x0001 |
"Plain" is the first item on the list. It excludes all items except the first one. All of the other items exclude only the first item, so that bold, underlined text may be selected, while bold, plain text may not.
Managing the State of Checkmarks
To correctly handle checkmarked menu items, from time to time the application will need to read the CHECKED bit of its CHECKIT MenuItems. It is not adequate to infer which items are checked by tracking what their state must have become. There are several reasons for this (although it's not important to understand the details; just the implication):
- Using multi-selection of menus, the user can toggle the state of a MENUTOGGLE item several times, yet the application will receive only a single IDCMP_MENUPICK event, and that item will only appear once in the NextSelect chain.
- When the user selects a mutually exclusive menu item, the IDCMP_MENUPICK event refers to that item, but Intuition doesn't notify your application of other items that may have been deselected through mutual exclusion.
- For complex multi-selection operations, the NextSelect chain will not be in select-order (a side-effect of the fact that the same MenuItem cannot appear twice in the same NextSelect chain combined with the fix to the orphaning problems mentioned above). With certain mutual exclusion arrangements, it is impossible to predict the state of the checkmarks.
- If the user begins multi-selection in the menus and hits several checkmarked items, but then presses the help key, the application will receive an IDCMP_MENUHELP message. No IDCMP_MENUPICK message will have been sent. Thus, some checkmark changes could have gone unnoticed by the application.
It is legal to examine the CHECKED state of a MenuItem while that MenuItem is still attached to a window. It is unnecessary to first call ClearMenuStrip().
Command Key Sequences
A command key sequence is an event generated when the user holds down one of the Amiga keys (the ones with the fancy A) and presses one of the normal alphanumeric keys at the same time. There are two different command or Amiga keys, commonly known as the left Amiga key and the right Amiga key.
Menu command key sequences are combinations of the right Amiga key with any alphanumeric character, and may be used by any program. These sequences must be accessed through the menu system. Command key sequences using the left Amiga key cannot be associated with menu items.
Menu command key sequences, like the menus themselves, are only available for a window while that window is active. Each window may control these keys by setting keyboard shortcuts in the menu item structures which make up the window's menu strip.
If the user presses a command key sequence that is associated with one of the menu items, Intuition will send the program an event that is identical to the event generated by selecting that menu item with the mouse. Many users would rather keep their hands on the keyboard than use the mouse to make a menu selection when accessing often repeated selections. Menu command key sequences allow the program to provide shortcuts to the user who prefers keyboard control.
A command key sequence is associated with a menu item by setting the COMMSEQ flag in the Flags variable of the MenuItem structure and by placing the ASCII character (upper or lower case) that is to be associated with the sequence into the Command variable of the MenuItem structure.
Command keys are not case sensitive and they do not repeat. Command keys are processed through the keymap so that they will continue to work even if the key value is remapped to another position. International key values are supported as long as they are accessible without using the Alt key (right-Amiga-Alt maps to the right mouse button on the mouse).
When items have alternate key sequences, the menu boxes show a special Amiga key glyph rendered roughly one character span plus a few pixels from the right edge of the menu select box. The command key used with the Amiga key is displayed immediately to the right of the Amiga key image, at the rightmost edge of the menu select box (see figure).
Space must be provided at the right edge of the select box for the Amiga key imagery and for the actual command character. Leave COMMWIDTH pixels on high resolution screens, and LOWCOMMWIDTH pixels on low resolution screens. The character's width may be calculated with the graphics library TextLength() call. In general, each column of items should leave enough room for the widest command character plus the width of the Amiga key imagery.
Enabling and Disabling Menus and Menu Items
Disabling menu items makes them unavailable for selection by the user.
Disabled menus and menu items are displayed in a ghosted fashion; that is, their imagery is overlaid with a faint pattern of dots, making it less distinct.
Enabling or disabling a menu or menu item is always a safe procedure, whether or not the user is currently using the menus. Of course, by the time you have disabled the item, the user may have already selected it. Thus, the program may receive a IDCMP_MENUPICK message for that item, even though it considers the item disabled. The program should be prepared to handle this case and ignore items that it knows are already disabled. This implies that the program must track internally which items are enabled and which are disabled.
The OffMenu() and OnMenu() functions may be used to enable or disable items while a menu strip is attached to the window.
VOID OffMenu( struct Window *window, ULONG menuNumber ); VOID OnMenu( struct Window *window, ULONG menuNumber );
These routines check if the user is currently using the menus and whether the menus need to be redrawn to reflect the new states. If the menus are currently in use, these routines wait for the user to finish before proceeding.
If the item component referenced by menuNumber equals NOITEM, the entire menu will be disabled or enabled. If the item component equates to an actual component number, then that item will be disabled or enabled. Use the macros defined below for the construction of menu numbers from their component parts.
The program can enable or disable whole menus, just the menu items, or just single sub-items.
- To enable or disable a whole menu, set the item component of the menu number to NOITEM. This will enable or disable all items and any sub-items for that menu.
- To enable or disable a single item and all sub-items attached to that item, set the item component of the menu number to the item's ordinal number. If the item has a sub-item list, set the sub-item component of the menu number to NOSUB. If the item has no sub-item list, the sub-item component of the menu number is ignored.
- To enable or disable a single sub-item, set the item and sub-item components appropriately.
It is also legal to remove the menu strip from each window that it is attached to (with ClearMenuStrip() ) change the ITEMENABLED or MENUENABLED flag of one or more Menu or MenuItem structures and add the menu back using ResetMenuStrip() or SetMenuStrip().
Intercepting Normal Menu Operations
IDCMP_MENUVERIFY gives the program the opportunity to react before menu operations take place and, optionally, to cancel menu operations. Menus may be completely disabled by removing the menu strip with a call to ClearMenuStrip().
A Warning on the MENUSTATE Flag
The MENUSTATE flag is set by Intuition in Window.Flags when the menus of that window are in use. Beware: in typical event driven programming, such a state variable is not on the same timetable as the application's input message handling, and should not be used to draw profound conclusions in any program. Use IDCMP_MENUVERIFY to synchronize with the menu handling.
Menu Verify
Menu verify is one of the Intuition verification capabilities that allow an application to ensure that it is prepared for some action taken by Intuition before that action takes place. Through menu verify, Intuition allows all windows in a screen to verify that they are prepared for menu operations before the operations begin. In general, use menu verify if the program is doing something special to the display of a custom screen, and needs to be sure the operation has completed before menus are rendered.
Menu verification works using the IDCMP mechanism. For an Intuition screen, when the user triggers Intuition into drawing the menu, Intuition sends an IDCMP_MENUVERIFY message to every window on that screen that asked to hear about menu verify events. Intuition will not display the menus until it gets back all the IDCMP_MENUVERIFY messages.
Any window can access the menu verify feature by setting the IDCMP_MENUVERIFY flag with the WA_IDCMP tag when opening the window. When menus are activated in a screen which contains at least one window with IDCMP_MENUVERIFY set, menu operations will not proceed until all windows with the menu verify flag set reply to the notification or until the last message times out. The specific menu verify protocol is described below.
It is vital that the application know when menu operations terminate, for only then does it have control of the screen again.
It is not recommended that your programs try and interpret the cause of a menu cancellation. The reasoning for the cancellation is not really relevant. Trying to interpret the cause of a menu cancellation is a bad idea. The only important fact is that the "menu processing" state has terminated and the application can resume normal operations.
Any of the following message combinations signify the cancellation of the "menu processing" state:
- IDCMP_MOUSEBUTTONS (IntuiMessage.Code is equal to MENUUP) only
- IDCMP_MENUPICK (IntuiMessage.Code is equal to MENUNULL) only
- IDCMP_MOUSEBUTTONS (IntuiMessage.Code is equal to MENUUP) then IDCMP_MENUPICK (IntuiMessage.Code is equal to MENUNULL)
- IDCMP_MENUPICK (IntuiMessage.Code is equal to MENUNULL) then IDCMP_MOUSEBUTTONS (IntuiMessage.Code is equal to MENUUP)
Since the active window can cancel menu operations, the application may need to determine if its window is the active window. The application can do this by examining the IntuiMessage.Code field of the menu verify message. If that field is equal to MENUWAITING the window is not active. However, if that field is equal to MENUHOT the window is active and can cancel the menu operation by changing the Code field of the menu verify message to MENUCANCEL. If the application cancels the menu operation, Intuition will send the active window an IDCMP_MOUSEBUTTONS event about the right mouse button.
The following are possible cases signifying that the "menu processing" state is over because the user successfully pulled down and released the menu (there was no cancellation):
- IDCMP_MENUPICK (IntuiMessage.Code is equal to the menu code)
- (spurious) IDCMP_MOUSEBUTTONS (IntuiMessage.Code is equal to MENUUP) then IDCMP_MENUPICK (IntuiMessage.Code is equal to the menu code)
Note that the menu code could be MENUNULL if no item was selected. IDCMP_MENUHELP events have the same meaning as the IDCMP_MENUPICK messages when it comes to terminating the "menu processing" state.
Active Window
The active window is given special menu verify treatment. It receives the menu verify message before any other window and has the option of canceling menu operations altogether. This could be used, for instance, to examine where the user has positioned the mouse when the right button was pressed. For example, the application may choose to allow normal menu operations to proceed only when the pointer is in the menu bar area. When the pointer is below the menu bar, then the application can choose to interpret the menu verify message as a right button event for some non-menu purpose.
If it wishes menu operations to proceed, the active window should reply to the IDCMP_MENUVERIFY message without changing any values. To cancel the menu operation, change the code field of the message to MENUCANCEL before replying to the message as described above.
The system takes no action on screen until the active window either replies to the menu verify event or the event times out. If the active window replies to the event in time and does not cancel the menu operation, Intuition will then move the screen title bar layer to the front, display the menu strip and notify all inactive menu verify windows of the operation. Layers will not be locked and the actual menus will not be swapped in until all these inactive windows reply to the message or time out.
Inactive Windows
When menu operations terminate, inactive windows will always receive IDCMP_MOUSEBUTTONS message with code equal to MENUUP.
Inactive windows may not cancel a menu operation.
If an inactive window receives an IDCMP_MENUVERIFY message, it will always receive an IDCMP_MOUSEBUTTONS message with code equal to MENUUP when the menu operations are completed.
Menu Verify and Double-Menu Requesters
The processing described above becomes more complicated when double-menu requester processing is introduced. If an application chooses to use a double-menu requester in a window with IDCMP_MENUVERIFY set, it should be aware that odd message combinations are possible. For instance, it is possible to receive only an IDCMP_MENUVERIFY event with no following IDCMP_MOUSEBUTTONS event or IDCMP_MENUPICK event. Applications should avoid using double menu requesters if possible.
The double menu requester is considered to be inconsistant with the Amiga's user interface, so using them in any capacity is strongly discouraged. Using them in conjunction with menu verify is even more strongly discouraged because they significantly complicate processing menu verify events. For this reason, never use double menu requester and menu verify together.
Shortcuts and IDCMP_MENUVERIFY
The idea behind IDCMP_MENUVERIFY is to synchronize the program with Intuition's menu handling sessions. The motive was to allow a program to arbitrate access to a custom screen's bitmap, so that Intuition would not render menus before the application was prepared for them.
Some programs use IDCMP_MENUVERIFY to permit them to intercept the right mouse button for their own purposes. Other programs use it to delay menu operations while they recover from unusual events such as illegible colors of the screen or double buffering and related ViewPort operations.
Menu shortcut keystrokes, for compatibility, also respect IDCMP_MENUVERIFY. They are always paired with an IDCMP_MENUPICK message so that the program knows the menu operation is over. This is true even if the menu event is cancelled.
IDCMP_MENUVERIFY and Deadlock
The program may call ModifyIDCMP() to turn IDCMP_MENUVERIFY and the other VERIFY IDCMP options off. It is important that this be done each and every time that the application is directly or indirectly waiting for Intuition, since Intuition may be waiting for the application, but not watching the window message port for IDCMP_MENUVERIFY events. The program cannot wait for a gadget or mouse event without checking also for any IDCMP_MENUVERIFY event messages that may require program response.
The most common problem area is System Requesters (AutoRequest() and EasyRequest()). Before AutoRequest() and EasyRequest() return control to the application, Intuition must be free to run and accept a response from the user. If the user presses the menu button, Intuition will wait for the program to reply to the IDCMP_MENUVERIFY event and a deadlock results.
Therefore, it is extremely important to use ModifyIDCMP() to turn off all verify messages before calling AutoRequest(), EasyRequest() or, directly or indirectly, AmigaDOS, since many error conditions in the DOS require user input in the form of an EasyRequest(). Indirect DOS calls include OpenLibrary(), OpenDevice(), and OpenDiskFont().
All windows that have the IDCMP_MENUVERIFY bit set must respond to Intuition within a set time period, or the menu operation will time out and the menu action will be canceled.
IDCMP_MENUVERIFY Timeout |
---|
Intuition has a time out on menu verify operations. If the active window's application takes too long to respond to the IDCMP_MENUVERIFY message, Intuition will cancel the menu operation. This feature helps avoid some deadlocks that can occur while Intuition is waiting on the active window. This feature applies only to the active window; Intuition will wait indefinitely for the IDCMP_MENUVERIFY message from an inactive window. |
Reasons to Use Menu Verify
The primary purpose of the menu verify feature is to arbitrate access to screen resources. There are really only two screen resources that an application might need to use directly, the palette and the bitmap.
Some applications need as many pens as possible, so they change the color values of the pens that Intuition uses to render the menus. This can present a problem if the application happens to alter the menu pen colors so that the menu's foreground color is difficult (if not impossible) to see against the background color. The menu items will be unreadable to the user.
To prevent this problem, the application has to restore the original pen colors when the user pulls down the menu. The application can do this using menu verify. When the user hits the menu button, Intuition sends the application a menu verify message. When the application receives this message, it restores the menu colors and then returns the IDCMP_MENUVERIFY message so Intuition can properly render the menus. When the menu operation is over, Intuition will send a "conclusion" IDCMP message. The "conclusion" message is covered below.
Another reason to use menu verify is to arbitrate rendering access to an Intuition Screen's bitmap. While Intuition is displaying the menus, the application must not render directly to the screen's bitmap because the application could draw over the menus. The application must wait until the menu operation is over to resume rendering.
This raises some important questions. Why render into a screen's bitmap (Screen-> RastPort.BitMap)? Shouldn't I render into a window instead?
The quick answer is: "render into a window's rastport instead of rendering directly into a screen's bitmap." Rendering to a window's rastport offers several advantages over rendering directly to the screen's bitmap. The window offers the application efficient clipping, making the application simpler because it doesn't need to worry about rendering into rectangular bounds. The window also supports a layered display, so the application can ignore the consequences of arbitrating access to the screen's bitmap.
There are some cases where, arguably, it might be advantageous for an application to render directly into a screen's bitmap. Rendering into a screen's bitmap versus rendering to a window's rastport offers a slight improvement in performance. Part of this improvement is due to the lack of clipping on a screen's bitmap, which the window provides, so the performance improvement is questionable if you still need to perform clipping.
One case where an application has to render into a screen's bitmap is when graphics functions that operate below the layers level. For example blitter objects (BOBs) operate below the layers level. If the application is operating below the layers level, it can't render to a windowed display.
Reasons Not to Use Menu Verify
One feature of Intuition menus is that the user can toggle a menu item between a selected "checked" state and an unselected "unchecked" state. Intuition keeps track of the state, so the application only has to read the state from the MenuItem structure. In some cases, the state of the menu item can be affected by something besides the user performing menu operations, so the application has to explicitly change the state of the menu item.
Some applications use the menu verify feature as a mechanism for updating the state of the menu items. When such an application receives the IDCMP_MENUVERIFY message, it updates the checked flag of the menu items and then returns the menu verify message. Some applications also use this method to manage the enabled and disabled state of a menu item.
Although this method works, it should not be necessary to interrupt the menu event. Almost all applications should be able to keep their menu states current using ResetMenuStrip(). It is also possible to use SetMenuStrip(), but since ResetMenuStrip() is considerably faster than SetMenuStrip(), ResetMenuStrip() is better. See the Autodoc for ResetMenuStrip() for more details.
Some people may think one potential use for menu verify is to assign an alternative meaning to the right mouse button. For example, when the pointer is not within the title bar of a screen, some paint packages use the right mouse button to paint using the background color. Using menu verify, it is possible for a application to intercept the menu verify event, checking to see if the pointer is within the title bar. If it isn't, the application cancels the menu event. After cancelling the menu event, Intuition will send a right button down event, which the application can interpret.
This mechanism may work, but it is not very efficient. There is a better way to do this using WFLG_RMBTRAP. This flag is from the Flags field in the Window structure. When set, this flag disables menu operations for the window. When the user hits the menu button, Intuition sends the window IDCMP_MOUSEBUTTONS events instead of menu events.
To use WFLG_RMBTRAP as an alternative to menu verify, the application tracks mouse movement using IDCMP_MOUSEMOVE. While the pointer is outside of the screen's title bar, the application makes sure the WFLG_RMBTRAP flag is set. If the user hits the right mouse button while WFLG_RMBTRAP is set, Intuition sends the application a mouse button event. While the pointer is inside the screen's title bar, the application makes sure the WFLG_RMBTRAP flag is clear. If the user hits the right mouse button while WFLG_RMBTRAP is clear, Intuition generates a normal menu event for the window.
WFLG_RMBTRAP is an exception to most fields in Intuition structures because it is legal for an application to directly modify this flag. Note that this change must take place as an atomic operation so that Exec cannot perform a task switch in the middle of the change. If you are unsure your compiler will do this, use a Forbid()/Permit() pair to prevent a task switch.
There are cases where an application is too busy to handle any menu event so it needs to prevent menu operations for a while. This is another case where menu verify is inappropriate. A better way to block menus is to remove the menu with the Intuition function ClearMenuStrip() and restore it later with ResetMenuStrip(). Another potentially useful way to block menu operations is using a requester.
Will I Ever Need to Cancel a Menu Operation?
When the active window receives an IDCMP_MENUVERIFY message, it has the option of cancelling the menu operation. Although this feature may have some possible uses, for the moment there is no recommended use for it.
Menu Data Structures
The specifications for the menu structures are given below. Menus are the headers that show in the menu bar, and MenuItems are the items and sub-items that can be chosen by the user.
Menu Structure
Here is the specification for a Menu structure:
struct Menu { struct Menu *NextMenu; WORD LeftEdge, TopEdge; WORD Width, Height; UWORD Flags; BYTE *MenuName; struct MenuItem *FirstItem; WORD JazzX, JazzY, BeatX, BeatY; };
The variables in the Menu structure have the following meanings:
- NextMenu
- This variable points to the next Menu header in the list. The last Menu in the list should have a NextMenu value of NULL.
- LeftEdge, TopEdge, Width, Height
- These fields describe the select box of the header. Currently, any values supplied for TopEdge and Height are ignored by Intuition, which uses instead the screen's TopBorder for the TopEdge and the height of the screen’s title bar for the Height.
- LeftEdge is relative to the LeftEdge of the screen plus the screen's left border width, so if LeftEdge is 0, Intuition puts this header at the leftmost allowable position.
- Flags
- The flag space is shared by the program and Intuition. The flags are:
MENUENABLED This flag is for Intuition's use and indicates whether or not this Menu is currently enabled. Set this flag before submitting the menu strip to Intuition. If this flag is not set, the menu header and all menu items below it will be disabled, and the user will be able to view, but not select any of the items. After submitting the strip to Intuition, the disabled or enabled status may be changed by calling OnMenu() or OffMenu(). MIDRAWN This flag indicates whether or not this menu's items are currently displayed to the user.
- MenuName
- This is a pointer to a NULL terminated character string that is printed on the screen's title bar starting at the LeftEdge of this menu's select box and at the TopEdge just below the screen title bar's top border. The text is rendered in the screen's font.
- FirstItem
- This points to the first MenuItem structure in the linked list of this menu's items.
- JazzX, JazzY, BeatX, BeatY
- For internal use only.
MenuItem Structure
The MenuItem structure is used for both items and sub-items. There is no internal difference between items and sub-items, other than how they are linked into the menu strip. Items are linked directly to Menu structures through the FirstItem field, sub-items are linked to MenuItem structures through the SubItem field.
Here is the specification:
struct MenuItem { struct MenuItem *NextItem; WORD LeftEdge, TopEdge; WORD Width, Height; UWORD Flags; LONG MutualExclude; APTR ItemFill; APTR SelectFill; BYTE Command; struct MenuItem *SubItem; UWORD NextSelect; };
The fields have the following meanings:
- NextItem
- This field is a pointer to the next item in the list. The last item in the list should have a NextItem value of NULL.
- LeftEdge, TopEdge, Width, Height
- These fields describe the select box of the MenuItem. The LeftEdge is relative to the LeftEdge of the Menu. The TopEdge is relative to the topmost position Intuition allows. TopEdge is based on the way the user has the system configured: which font, which resolution, and so on. Use 0 for the topmost position.
- Flags
- The flag space is shared by the program and Intuition. See "MenuItem Flags" below for a description of the flag bits.
- MutualExclude
- Use these bits to describe which of the other items, if any, are mutually excluded by this one. This long word refers to the items in the same menu as this one. A maximum of 32 items may be described by this variable, and they must be the first 32 items in the menu. This does not mean that there cannot be more than 32 items in any given menu, just that only the first 32 can be mutually excluded.
- ItemFill
- This points to the data used in rendering this MenuItem. It can point to either an instance of an IntuiText structure with text for this MenuItem or an instance of an Image structure with image data. The program tells Intuition the type of data pointed to by this variable by setting or clearing the MenuItem flag bit ITEMTEXT. (See "MenuItem Flags" below for more details about ITEMTEXT.)
- Note that the IntuiText or Image data need not be simple imagery, either of them may consist of multiple objects of the same type chained together as described in the chapter "Intuition Images, Line Drawing and Text". By chaining multiple IntuiText structures, the application may "fine tune" the positioning of text within each item. This is especially important for proportional fonts, where the width of the individual characters is not constant. This also allows items to have part of the text left justified and part right justified.
- SelectFill
- If HIGHIMAGE is set in the Flags variable as the MenuItem highlighting mode, Intuition substitutes this alternate image or text for the original rendering described by ItemFill. The type of this structure must be the same as ItemFill. SelectFill can point to either an Image or an IntuiText, where the type is determined by the setting of the ITEMTEXT flag.
- Command
- This variable is storage for a single alphanumeric character to be used as the command key substitute for this menu item. The command key sequence will be rendered in the menu display to the right of the item's select area, with a fancy, reverse-video A, followed by the command character. Case is ignored.
- If the flag COMMSEQ is set, the user can hold down the right Amiga key on the keyboard (to mimic using the right mouse menu button) and press the indicated key as a shortcut for using the mouse to select this item.
- SubItem
- If this item has a sub-item list, this variable should point to the MenuItem structure describing the first sub-item in the list.
There Is No Such Thing as a Sub-sub-item |
---|
A sub-item cannot have a sub-item attached to it. If this MenuItem structure is not an item, this variable is ignored. |
- NextSelect
- This field is filled in by Intuition when this MenuItem is selected by the user. After an item has been selected by the user, the program should process the request and then check the NextSelect field. If the NextSelect field is equal to MENUNULL, no other items were selected; otherwise, there is another item to process. See "Menu Numbers and Menu Selection Messages" above for more information about user selections.
MenuItem Flags
Here are the flags that can be set by the application in the Flags field of the MenuItem structure:
- CHECKIT
- Set this flag to inform Intuition that this item is a checkmark item and should be preceded by a checkmark if the flag CHECKED is set.
- CHECKED
- For an item with the CHECKIT flag set, set this bit to specify that the checkmark is displayed. After the menu strip is submitted to Intuition, it will maintain the CHECKED bit based on effects from other items' mutual exclusions, or, for MENUTOGGLE items, from user accesses to this item.
- ITEMTEXT
- Set this flag if the representation of the item pointed to by the ItemFill field and, possibly, by SelectFill is text and points to an IntuiText structure. Clear this bit if the item is graphic and points to an Image structure.
- COMMSEQ
- If this flag is set, this item has an equivalent command key sequence set in the Command field of the MenuItem structure.
- MENUTOGGLE
- This flag is used in conjunction with the CHECKIT flag. If MENUTOGGLE is set, a checkmark that is turned on may be turned off by selecting the item. This allows the user to toggle between the checked and non-checked states by repeatedly selecting the item.
- ITEMENABLED
- This flag describes whether or not this item is currently enabled. If an item is not enabled, its image will be ghosted and the user will not be able to select it. If this item has sub-items, all of the sub-items are disabled when the item is disabled.
- Set this flag before submitting the menu strip to Intuition. Once the menu strip has been submitted to Intuition, enable or disable items by calling OnMenu() or OffMenu().
- HIGHFLAGS
- An item can be highlighted when the user positions the pointer over the item. These bits describe what type of highlighting will be used, if any. One of the following bits must be set, according to the type of highlighting desired:
HIGHCOMP This complements all of the bits contained by this item's select box. HIGHBOX This draws a box outside this item's select box. HIGHIMAGE This displays alternate imagery referenced in SelectFill. For alternate text, make sure that ITEMTEXT is set, and that the SelectFill field points to an IntuiText structure. For alternate image, ITEMTEXT must be cleared, and the SelectFill field must point to an Image structure. HIGHNONE This specifies no highlighting.
The following two flags are used by Intuition:
- ISDRAWN
- Intuition sets this flag when this item's sub-items are currently displayed to the user and clears it when they are not.
- HIGHITEM
- Intuition sets this flag when this item is highlighted and clears it when the item is not highlighted.
A Menu Example
This example shows how to implement menus. The menu code is simply part of the processing for Intuition messages as shown in the IDCMP example in Intuition Input and Output Methods. The example implements extended selection for menus, adaptation to fonts of different sizes, mutual exclusion and checkmarks.
If possible, applications should use the menu layout routines available in the GadTools library, rather than doing the job themselves as this example does. See GadTools Library for more information.
/* ** menulayout.c - Example showing how to do menu layout in general. This example ** also illustrates handling menu events, including IDCMP_MENUHELP events. ** ** Note that handling arbitrary fonts is fairly complex. Applications should ** use the simpler menu layout routines found in the GadTools library. */ #include <exec/types.h> #include <intuition/intuition.h> #include <intuition/intuitionbase.h> #include <graphics/gfxbase.h> #include <dos/dos.h> #include <proto/exec.h> #include <proto/graphics.h> #include <proto/intuition.h> #include <stdlib.h> #include <string.h> /* Our function prototypes */ BOOL processMenus(uint16 selection, BOOL done); BOOL handleIDCMP(struct Window *win); uint16 MaxLength(struct RastPort *textRPort, struct MenuItem *first_item, uint16 char_size); VOID setITextAttr(struct IntuiText *first_IText, struct TextAttr *textAttr); VOID adjustItems(struct RastPort *textRPort, struct MenuItem *first_item, struct TextAttr *textAttr, uint16 char_size, uint16 height, uint16 level, uint16 left_edge); BOOL adjustMenus(struct Menu *first_menu, struct TextAttr *textAttr); LONG doWindow(void); /* Settings Item IntuiText */ struct IntuiText SettText[] = { {0,1,JAM2,2, 1, NULL, "Sound...", NULL }, {0,1,JAM2,CHECKWIDTH,1, NULL, " Auto Save", NULL }, {0,1,JAM2,CHECKWIDTH,1, NULL, " Have Your Cake", NULL }, {0,1,JAM2,CHECKWIDTH,1, NULL, " Eat It Too", NULL } }; struct MenuItem SettItem[] = { { /* "Sound..." */ &SettItem[1], 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP, 0, (APTR)&SettText[0], NULL, NULL, NULL, MENUNULL }, { /* "Auto Save" (toggle-select, initially selected) */ &SettItem[2], 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP|CHECKIT|MENUTOGGLE|CHECKED, 0, (APTR)&SettText[1], NULL, NULL, NULL, MENUNULL }, { /* "Have Your Cake" (initially selected, excludes "Eat It Too") */ &SettItem[3], 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP|CHECKIT|CHECKED, 8, (APTR)&SettText[2], NULL, NULL, NULL, MENUNULL }, { /* "Eat It Too" (excludes "Have Your Cake") */ NULL, 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP|CHECKIT, 4, (APTR)&SettText[3], NULL, NULL, NULL, MENUNULL } }; /* Edit Menu Item IntuiText */ struct IntuiText EditText[] = { {0,1,JAM2,2,1, NULL, "Cut", NULL }, {0,1,JAM2,2,1, NULL, "Copy", NULL }, {0,1,JAM2,2,1, NULL, "Paste", NULL }, {0,1,JAM2,2,1, NULL, "Erase", NULL }, {0,1,JAM2,2,1, NULL, "Undo", NULL } }; /* Edit Menu Items */ struct MenuItem EditItem[] = { { /* "Cut" (key-equivalent: 'X') */ &EditItem[1], 0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&EditText[0], NULL, 'X', NULL, MENUNULL }, { /* "Copy" (key-equivalent: 'C') */ &EditItem[2], 0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&EditText[1], NULL, 'C', NULL, MENUNULL }, { /* "Paste" (key-equivalent: 'V') */ &EditItem[3], 0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&EditText[2], NULL, 'V', NULL, MENUNULL }, { /* "Erase" (disabled) */ &EditItem[4], 0, 0, 0, 0, ITEMTEXT|HIGHCOMP, 0, (APTR)&EditText[3], NULL, NULL, NULL, MENUNULL }, { /* "Undo" MenuItem (key-equivalent: 'Z') */ NULL, 0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&EditText[4], NULL, 'Z', NULL, MENUNULL } }; /* IntuiText for the Print Sub-Items */ struct IntuiText PrtText[] = { {0,1, JAM2,2,1, NULL, "NLQ", NULL }, {0,1, JAM2,2,1, NULL, "Draft", NULL } }; /* Print Sub-Items */ struct MenuItem PrtItem[] = { { /* "NLQ" */ &PrtItem[1], 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP, 0, (APTR)&PrtText[0], NULL, NULL, NULL, MENUNULL }, { /* "Draft" */ NULL, 0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP, 0, (APTR)&PrtText[1], NULL, NULL, NULL, MENUNULL } }; /* Uses the >> character to indicate a sub-menu item. ** This is \273 Octal, 0xBB Hex or Alt-0 from the Keyboard. ** ** NOTE that standard menus place this character at the right margin of the menu box. ** This may be done by using a second IntuiText structure for the single character, ** linking this IntuiText to the first one, and positioning the IntuiText so that the ** character appears at the right margin. GadTools library will provide the correct behavior. */ /* Project Menu Item IntuiText */ struct IntuiText ProjText[] = { {0,1, JAM2,2,1, NULL, "New", NULL }, {0,1, JAM2,2,1, NULL, "Open...", NULL }, {0,1, JAM2,2,1, NULL, "Save", NULL }, {0,1, JAM2,2,1, NULL, "Save As...", NULL }, {0,1, JAM2,2,1, NULL, "Print \273", NULL }, {0,1, JAM2,2,1, NULL, "About...", NULL }, {0,1, JAM2,2,1, NULL, "Quit", NULL } }; /* Project Menu Items */ struct MenuItem ProjItem[] = { { /* "New" (key-equivalent: 'N' */ &ProjItem[1],0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[0], NULL, 'N', NULL, MENUNULL }, { /* "Open..." (key-equivalent: 'O') */ &ProjItem[2],0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[1], NULL, 'O', NULL, MENUNULL }, { /* "Save" (key-equivalent: 'S') */ &ProjItem[3],0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[2], NULL, 'S', NULL, MENUNULL }, { /* "Save As..." (key-equivalent: 'A') */ &ProjItem[4],0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[3], NULL, 'A', NULL, MENUNULL }, { /* "Print" (has sub-menu) */ &ProjItem[5],0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[4], NULL, NULL, &PrtItem[0], MENUNULL }, { /* "About..." */ &ProjItem[6],0, 0, 0, 0, ITEMTEXT|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[5], NULL, NULL, NULL, MENUNULL }, { /* "Quit" (key-equivalent: 'Q' */ NULL, 0, 0, 0, 0, ITEMTEXT|COMMSEQ|ITEMENABLED|HIGHCOMP, 0, (APTR)&ProjText[6], NULL, 'Q', NULL, MENUNULL } }; /* Menu Titles */ struct Menu Menus[] = { {&Menus[1], 0, 0, 63, 0, MENUENABLED, "Project", &ProjItem[0]}, {&Menus[2], 70, 0, 39, 0, MENUENABLED, "Edit", &EditItem[0]}, {NULL, 120, 0, 88, 0, MENUENABLED, "Settings", &SettItem[0]}, }; /* A pointer to the first menu for easy reference */ struct Menu *FirstMenu = &Menus[0]; /* Window Text for Explanation of Program */ struct IntuiText WinText[] = { {0, 0, JAM2, 0, 0, NULL, "How to do a Menu", NULL}, {0, 0, JAM2, 0, 0, NULL, "(with Style)", &WinText[0]} }; /* Globals */ struct IntuitionIFace *IIntuition = NULL; struct GraphicsIFace *IGraphics = NULL; /* open all of the required libraries. */ int main(int argc, char **argv) { LONG returnValue; /* This gets set to RETURN_OK if everything goes well. */ returnValue = RETURN_FAIL; /* Open the Intuition Library */ struct Library *IntuitionBase = IExec->OpenLibrary("intuition.library", 50); IIntuition = (struct IntuitionIFace*)IExec->GetInterface(IntuitionBase, "main", 1, NULL); struct Library *GfxBase = IExec->OpenLibrary("graphics.library", 50); IGraphics = (struct GraphicsIFace*)IExec->GetInterface(GfxBase, "main", 1, NULL); if (IIntuition && IGraphics) { returnValue = doWindow(); } IExec->DropInterface((struct Interface*)IGraphics); IExec->CloseLibrary(GfxBase); IExec->DropInterface((struct Interface*)IIntuition); IExec->CloseLibrary(IntuitionBase); return returnValue; } /* Open a window with some properly positioned text. Layout and set ** the menus, then process any events received. Cleanup when done. */ LONG doWindow() { struct Window *window; struct Screen *screen; struct DrawInfo *drawinfo; ULONG signalmask, signals; ULONG win_width, alt_width, win_height; LONG returnValue = RETURN_FAIL; BOOL done = FALSE; if (screen = IIntuition->LockPubScreen(NULL)) { if (drawinfo = IIntuition->GetScreenDrawInfo(screen)) { /* get the colors for the window text */ WinText[0].FrontPen = WinText[1].FrontPen = drawinfo->dri_Pens[TEXTPEN]; WinText[0].BackPen = WinText[1].BackPen = drawinfo->dri_Pens[BACKGROUNDPEN]; /* use the screen's font for the text */ WinText[0].ITextFont = WinText[1].ITextFont = screen->Font; /* calculate window size */ win_width = 100 + IntuiTextLength(&(WinText[0])); alt_width = 100 + IntuiTextLength(&(WinText[1])); if (win_width < alt_width) win_width = alt_width; win_height = 1 + screen->WBorTop + screen->WBorBottom + (screen->Font->ta_YSize * 5); /* calculate the correct positions for the text in the window */ WinText[0].LeftEdge = (win_width - IIntuition->IntuiTextLength(&(WinText[0]))) >> 1; WinText[0].TopEdge = 1 + screen->WBorTop + (2 * screen->Font->ta_YSize); WinText[1].LeftEdge = (win_width - IIntuition->IntuiTextLength(&(WinText[1]))) >> 1; WinText[1].TopEdge = WinText[0].TopEdge + screen->Font->ta_YSize; /* Open the window */ window = IIntuition->OpenWindowTags(NULL, WA_PubScreen, screen, WA_IDCMP, IDCMP_MENUPICK | IDCMP_CLOSEWINDOW | IDCMP_MENUHELP, WA_Flags, WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_CLOSEGADGET | WFLG_ACTIVATE | WFLG_NOCAREREFRESH, WA_Left, 10, WA_Top, screen->BarHeight + 1, WA_Width, win_width, WA_Height, win_height, WA_Title, "Menu Example", WA_MenuHelp, TRUE, TAG_END); if (window) { returnValue = RETURN_OK; /* program initialized ok */ /* Give a brief explanation of the program */ IIntuition->PrintIText(window->RPort,&WinText[1],0,0); /* Adjust the menu to conform to the font (TextAttr) */ adjustMenus(FirstMenu, window->WScreen->Font); /* attach the menu to the window */ IIntuition->SetMenuStrip(window, FirstMenu); /* Set up the signals that you want to hear about ... */ signalmask = 1L << window->UserPort->mp_SigBit; /* And wait to hear from your signals */ while (!done) { signals = IExec->Wait(signalmask); if (signals & signalmask) done = handleIDCMP(window); }; /* clean up everything used here */ IIntuition->ClearMenuStrip(window); IIntuition->CloseWindow(window); } IIntuition->FreeScreenDrawInfo(screen,drawinfo); } IIntuition->UnlockPubScreen(NULL,screen); } return(returnValue); } /* print out what menu was selected. Properly handle the IDCMP_MENUHELP ** events. Set done to TRUE if quit is selected. */ BOOL processMenus(uint16 selection, BOOL done) { uint16 flags; uint16 menuNum, itemNum, subNum; menuNum = MENUNUM(selection); itemNum = ITEMNUM(selection); subNum = SUBNUM(selection); /* when processing IDCMP_MENUHELP, you are not guaranteed ** to get a menu item. */ if (itemNum != NOITEM) { flags = ((struct MenuItem *)IIntuition->ItemAddress(FirstMenu,(LONG)selection))->Flags; if (flags & CHECKED) IDOS->Printf("(Checked) "); } switch (menuNum) { case 0: /* Project Menu */ switch (itemNum) { case NOITEM: IDOS->Printf("Project Menu\n"); break; case 0: IDOS->Printf("New\n"); break; case 1: IDOS->Printf("Open\n"); break; case 2: IDOS->Printf("Save\n"); break; case 3: IDOS->Printf("Save As\n"); break; case 4: IDOS->Printf("Print "); switch (subNum) { case NOSUB: IDOS->Printf("Item\n"); break; case 0: IDOS->Printf("NLQ\n"); break; case 1: IDOS->Printf("Draft\n"); break; } break; case 5: IDOS->Printf("About\n"); break; case 6: IDOS->Printf("Quit\n"); done = TRUE; break; } break; case 1: /* Edit Menu */ switch (itemNum) { case NOITEM: IDOS->Printf("Edit Menu\n"); break; case 0: IDOS->Printf("Cut\n"); break; case 1: IDOS->Printf("Copy\n"); break; case 2: IDOS->Printf("Paste\n"); break; case 3: IDOS->Printf("Erase\n"); break; case 4: IDOS->Printf("Undo\n"); break; } break; case 2: /* Settings Menu */ switch (itemNum) { case NOITEM: IDOS->Printf("Settings Menu\n"); break; case 0: IDOS->Printf("Sound\n"); break; case 1: IDOS->Printf("Auto Save\n"); break; case 2: IDOS->Printf("Have Your Cake\n"); break; case 3: IDOS->Printf("Eat It Too\n"); break; } break; case NOMENU: /* No menu selected, can happen with IDCMP_MENUHELP */ IDOS->Printf("no menu\n"); break; } return(done); } /* Handle the IDCMP messages. Set done to TRUE if quit or closewindow is selected. */ BOOL handleIDCMP(struct Window *win) { BOOL done; uint16 code, selection; struct IntuiMessage *message = NULL; ULONG class; done = FALSE; /* Examine pending messages */ while (message = (struct IntuiMessage *)IExec->GetMsg(win->UserPort)) { class = message->Class; code = message->Code; /* When we're through with a message, reply */ IExec->ReplyMsg((struct Message *)message); /* See what events occurred */ switch (class) { case IDCMP_CLOSEWINDOW: done = TRUE; break; case IDCMP_MENUHELP: /* ** The routine that handles the menus for IDCMP_MENUHELP must be very careful ** it can receive menu information that is impossible under IDCMP_MENUPICK. ** For instance, the code value on a IDCMP_MENUHELP may have a valid number ** for the menu, then NOITEM and NOSUB. IDCMP_MENUPICK would get MENUNULL ** in this case. IDCMP_MENUHELP never come as multi-select items, and the ** event terminates the menu processing session. ** ** Note that I do not keep the return value from the processMenus() routine here--the ** application should not quit if the user selects "help" over the quit menu item. */ IDOS->Printf("IDCMP_MENUHELP: Help on "); processMenus(code,done); break; case IDCMP_MENUPICK: for ( selection = code; selection != MENUNULL; selection = (IIntuition->ItemAddress(FirstMenu,(LONG)selection))->NextSelect) { IDOS->Printf("IDCMP_MENUPICK: Selected "); done = processMenus(selection,done); } break; } } return(done); } /* Steps thru each item to determine the maximum width of the strip */ uint16 MaxLength(struct RastPort *textRPort, struct MenuItem *first_item, uint16 char_size) { uint16 maxLength; uint16 total_textlen; struct MenuItem *cur_item; struct IntuiText *itext; uint16 extra_width; uint16 maxCommCharWidth; uint16 commCharWidth; extra_width = char_size; /* used as padding for each item. */ /* Find the maximum length of a command character, if any. ** If found, it will be added to the extra_width field. */ maxCommCharWidth = 0; for (cur_item = first_item; cur_item != NULL; cur_item = cur_item->NextItem) { if (cur_item->Flags & COMMSEQ) { commCharWidth = IGraphics->TextLength(textRPort,&(cur_item->Command),1); if (commCharWidth > maxCommCharWidth) maxCommCharWidth = commCharWidth; } } /* if we found a command sequence, add it to the extra required space. Add ** space for the Amiga key glyph plus space for the command character. Note ** this only works for HIRES screens, for LORES, use LOWCOMMWIDTH. */ if (maxCommCharWidth > 0) extra_width += maxCommCharWidth + COMMWIDTH; /* Find the maximum length of the menu items, given the extra width calculated above. */ maxLength = 0; for (cur_item = first_item; cur_item != NULL; cur_item = cur_item->NextItem) { itext = (struct IntuiText *)cur_item->ItemFill; total_textlen = extra_width + itext->LeftEdge + IGraphics->TextLength(textRPort, itext->IText, strlen(itext->IText)); /* returns the greater of the two */ if (total_textlen > maxLength) maxLength = total_textlen; } return(maxLength); } /* Set all IntuiText in a chain (they are linked through the NextText ** field) to the same font. */ VOID setITextAttr(struct IntuiText *first_IText, struct TextAttr *textAttr) { struct IntuiText *cur_IText; for (cur_IText = first_IText; cur_IText != NULL; cur_IText = cur_IText->NextText) cur_IText->ITextFont = textAttr; } /* Adjust the MenuItems and SubItems */ VOID adjustItems(struct RastPort *textRPort, struct MenuItem *first_item, struct TextAttr *textAttr, uint16 char_size, uint16 height, uint16 level, uint16 left_edge) { uint16 item_num; struct MenuItem *cur_item; uint16 strip_width, subitem_edge; if (first_item == NULL) return; /* The width of this strip is the maximum length of its members. */ strip_width = MaxLength(textRPort, first_item, char_size); /* Position the items. */ for (cur_item = first_item, item_num = 0; cur_item != NULL; cur_item = cur_item->NextItem, item_num++) { cur_item->TopEdge = (item_num * height) - level; cur_item->LeftEdge = left_edge; cur_item->Width = strip_width; cur_item->Height = height; /* place the sub_item 3/4 of the way over on the item. */ subitem_edge = strip_width - (strip_width >> 2); setITextAttr((struct IntuiText *)cur_item->ItemFill, textAttr); adjustItems(textRPort,cur_item->SubItem,textAttr,char_size,height,1,subitem_edge); } } /* The following routines adjust an entire menu system to conform to the specified fonts' width and ** height. Allows for Proportional Fonts. This is necessary for a clean look regardless of what the ** users preference in Fonts may be. Using these routines, you don't need to specify TopEdge, ** LeftEdge, Width or Height in the MenuItem structures. ** ** NOTE that this routine does not work for menus with images, but assumes that all menu items are ** rendered with IntuiText. ** ** This set of routines does NOT check/correct if the menu runs off ** the screen due to large fonts, too many items, lo-res screen. */ BOOL adjustMenus(struct Menu *first_menu, struct TextAttr *textAttr) { struct RastPort textrp = {0}; /* Temporary RastPort */ struct Menu *cur_menu; struct TextFont *font; /* Font to use */ uint16 start, char_size, height; BOOL returnValue = FALSE; /* open the font */ if (font = IGraphics->OpenFont(textAttr)) { IGraphics->SetFont(&textrp, font); /* Put font into temporary RastPort */ char_size = IGraphics->TextLength(&textrp, "n", 1); /* Get the Width of the Font */ /* To prevent crowding of the Amiga key when using COMMSEQ, don't allow the items to be less ** than 8 pixels high. Also, add an extra pixel for inter-line spacing. */ if (font->tf_YSize > 8) height = 1 + font->tf_YSize; else height = 1 + 8; start = 2; /* Set Starting Pixel */ /* Step thru the menu structure and adjust it */ for (cur_menu = first_menu; cur_menu != NULL; cur_menu = cur_menu->NextMenu) { cur_menu->LeftEdge = start; cur_menu->Width = char_size + IGraphics->TextLength(&textrp, cur_menu->MenuName, strlen(cur_menu->MenuName)); adjustItems(&textrp, cur_menu->FirstItem, textAttr, char_size, height, 0, 0); start += cur_menu->Width + char_size + char_size; } IGraphics->CloseFont(font); /* Close the Font */ returnValue = TRUE; } return(returnValue); }
Other Menu Macros
The MENUNEM(), ITEMNUM() and SUBNUM() macros let an application break a menu number down into its component parts: the specific menu number, the item number and the sub-item number. (See the section on "Menu Numbers" earlier in this chapter for details.) Intuition also supplies macros that allow an application to construct a menu number given its components:
- SHIFTMENU(n)
- Create a properly masked and shifted specific menu number.
- SHIFTITEM(n)
- Create a properly masked and shifted item number.
- SHIFTSUB(n)
- Create a properly masked and shifted sub-item number.
- FULLMENUNUM( menu, item, sub )
- Create a complete composite menu number from its components.
Function Reference
The following are brief descriptions of the Intuition functions that relate to the use of Intuition menus. See the SDK for details on each function call.
Function | Description |
---|---|
SetMenuStrip() | Set a menu for an open window. |
ClearMenuStrip() | Clear the menu of an open window. |
ResetMenuStrip() | Set a pre-calculated menu for an open window. |
ItemAddress() | Find the address of a menu item from its position. |
OffMenu() | Disable a menu in a menu strip. |
OnMenu() | Enable a menu in a menu strip. |