Copyright (c) Hyperion Entertainment and contributors.

GadTools Menus

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

Introduction

GadTools menus are easy to use. Armed only with access to a VisualInfo data structure, GadTools allows the application to easily create, layout and delete Intuition menus.

Normally, the greatest difficulty in creating menus is that a large number of structures must be filled out and linked. This is bothersome since much of the required information is orderly and is easier to do algorithmically than to do manually. GadTools handles this for you.

There are also many complexities in creating a sensible layout for menus. This includes some mechanical items such as handling various font sizes, automatic columnization of menus that are too tall and accounting for space for checkmarks and Amiga-key equivalents. There are also aesthetic considerations, such as how much spacing to provide, where sub-menus should be placed and so on.

GadTools menu functions support all the features that most applications will need. These include:

  • An easily constructed and legible description of the menus.
  • Font-sensitive layout.
  • Support for menus and sub-menus.
  • Sub-menu indicators (a >> symbol attached to items with sub-menus).
  • Separator bars for sectioning menus.
  • Command-key equivalents.
  • Checkmarked and mutually exclusive checkmarked menu items.
  • Graphical menu items.

With GadTools, it takes only one structure, the NewMenu structure, to specify the whole menu bar, For instance, here is how a typical menu strip containing two menus might be specified:

struct NewMenu mynewmenu[] =
    {
        { NM_TITLE, "Project",    0 , 0, 0, 0,},
        {  NM_ITEM, "Open...",   "O", 0, 0, 0,},
        {  NM_ITEM, "Save",      "S", 0, 0, 0,},
        {  NM_ITEM, NM_BARLABEL,  0 , 0, 0, 0,},
        {  NM_ITEM, "Print",      0 , 0, 0, 0,},
        {   NM_SUB, "Draft",      0 , 0, 0, 0,},
        {   NM_SUB, "NLQ",        0 , 0, 0, 0,},
        {  NM_ITEM, NM_BARLABEL,  0 , 0, 0, 0,},
        {  NM_ITEM, "Quit...",   "Q", 0, 0, 0,},
 
        { NM_TITLE, "Edit",       0 , 0, 0, 0,},
        {  NM_ITEM, "Cut",       "X", 0, 0, 0,},
        {  NM_ITEM, "Copy",      "C", 0, 0, 0,},
        {  NM_ITEM, "Paste",     "V", 0, 0, 0,},
        {  NM_ITEM, NM_BARLABEL,  0 , 0, 0, 0,},
        {  NM_ITEM, "Undo",      "Z", 0, 0, 0,},
 
        {   NM_END, NULL,         0 , 0, 0, 0,},
    };

This NewMenu specification would produce the two menus below:

Two Example Menus

The NewMenu arrays are designed to be read easily. The elements in the NewMenu array appear in the same order as they will appear on-screen. Unlike the lower-level menu structures described in Intuition Menus, there is no need to specify sub-menus first, then the menu items with their sub-menus, and finally the menu headers with their menu items. The indentation shown above also helps highlight the relationship between menus, menu items and sub-items.

New Look Menus

GadTools fully supports Intuition's NewLook menus. To make GadTools use the NewLook color scheme for a window's menus, an application needs to do two things. First, it needs to tell Intuition that it wants NewLook menu imagery for the window. It does this when opening the window by passing the {WA_NewLookMenus, TRUE} tag/value pair to OpenWindowTags(). Second, the application needs to remind GadTools that the window uses NewLook imagery for its menus. The application does this by passing the {GTMN_NewLookMenus, TRUE} tag/value pair to LayoutMenus().

The NewMenu Structure

The NewMenu structure used to specify GadTools menus is defined in <libraries/gadtools.h> as follows:

struct NewMenu
    {
    UBYTE nm_Type;
    STRPTR nm_Label;
    STRPTR nm_CommKey;
    UWORD nm_Flags;
    LONG nm_MutualExclude;
    APTR nm_UserData;
    };
nm_Type
The first field, nm_Type, defines what this particular NewMenu describes. The defined types provide an unambiguous and convenient representation of the application's menus.
NM_TITLE Used to signify a textual menu heading. Each NM_TITLE signifies the start of a new menu within the menu strip.
NM_ITEM or IM_ITEM Used to signify a textual (NM_ITEM) or graphical (IM_ITEM) menu item. Each NM_ITEM or IM_ITEM becomes a menu item in the current menu.
NM_SUB or IM_SUB Used to signify a textual (NM_SUB) or graphical (IM_SUB) menu sub-item. All the consecutive NM_SUBs and IM_SUBs that follow a menu item (NM_ITEM or IM_ITEM) compose that item's sub-menu. A subsequent NM_ITEM or IM_ITEM would indicate the start of the next item in the original menu, while a subsequent NM_TITLE would begin the next menu.
NM_END Used to signify the end of the NewMenu structure array. The last element of the array must have NM_END as its type.
nm_Label
NM_TITLE, NM_ITEM and NM_SUB are used for textual menu headers, menu items and sub-items respectively, in which case nm_Label points to the string to be used. This string is not copied, but rather a pointer to it is kept. Therefore the string must remain valid for the active life of the menu.
Menus don't have to use text, GadTools also supports graphical menu items and sub-items (graphical menu headers are not possible since they are not supported by Intuition). Simply use IM_ITEM and IM_SUB instead and point nm_Label at a valid Image structure. The Image structure can contain just about any graphic image (see Intuition Images, Line Drawing and Text for more on this).
Sometimes it is a good idea to put a separator between sets of menu items or sub-items. The application may want to separate drastic menu items such as "Quit" or "Delete" from more mundane ones. Another good idea is to group related checkmarked items by using separator bars.
GadTools will provide a separator bar if the special constant NM_BARLABEL is supplied for the nm_Label field of an NM_ITEM or NM_SUB.
A single character string used as the Amiga-key equivalent for the menu item or sub-item.
Menu headers cannot have command keys. Note that assigning a command-key equivalent to a menu item that has sub-items is meaningless and should be avoided.
nm_CommKey
The nm_CommKey field is a pointer to a string and not a character itself. This was done in part because routines to support different languages typically return strings, not characters. The first character of the string is actually copied into the resulting MenuItem structure.
The nm_Flags field of the NewMenu structure corresponds roughly to the Flags field of the Intuition's lower-level Menu and MenuItem structures.
For programmer convenience the sense of the Intuition MENUENABLED and ITEMENABLED flags are inverted. When using GadTools, menus, menu items and sub-items are enabled by default.
NM_MENUDISABLED To specify a disabled menu, set the NM_MENUDISABLED flag in this field.
NM_ITEMDISABLED To disable an item or sub-item, set the NM_ITEMDISABLED flag.
The Intuition flag bits COMMSEQ (indication of a command-key equivalent), ITEMTEXT (indication of a textual or graphical item) and HIGHFLAGS (method of highlighting) will be automatically set depending on other attributes of the menus. Do not set these values in nm_Flags.
nm_Flags
The nm_Flags field is also used to specify checkmarked menu items. To get a checkmark that the user can toggle, set the CHECKIT and MENUTOGGLE flags in the nm_Flags field. Also set the CHECKED flag if the item or sub-item is to start in the checked state.
nm_MutualExclude
For specifying mutual exclusion of checkmarked items. All the items or sub-items that are part of a mutually exclusive set should have the CHECKIT flag set.
This field is a bit-wise representation of the items (or sub-items), in the same menu or sub-menu, that are excluded by this item (or sub-item). In the simple case of mutual exclusion, where each choice excludes all others, set nm_MutualExclude to ~(1<<item number) or ~1, ~2, ~4, ~8, etc. Separator bars count as items and should be included in the position calculation. See Intuition Menus for more details on menu mutual exclusion.
nm_UserData
The NewMenu structure also has a user data field. This data is stored with the Intuition Menu or MenuItem structures that GadTools creates.
To extract or change the user data fields of menus and menu items, respectively, use the macros GTMENU_USERDATA(menu) and GTMENUITEM_USERDATA(menuitem) defined in <libraries/gadtools.h>.
The application may place index numbers in this field and perform a switch statement on them, instead of using the Intuition menu numbers. The advantage of this is that the numbers chosen remain valid even if the menus are rearranged, while the Intuition menu numbers would change when the menus are rearranged.
Alternately, an efficient technique for menu handling is to create a handler function for each menu item and put a pointer to that function in the corresponding item's UserData field. When the program receives a IDCMP_MENUPICK message it may call the selected item's function through this field.

GadTools Menus Example

The functions used to set up and control GadTools menus are discussed in the next section. Before looking at these functions in detail, it may be helpful to look at a brief example.

/* gadtoolsmenu.c
** Example showing the basic usage of the menu system with a window.
** Menu layout is done with GadTools, as is recommended for applications.
*/
 
#include <exec/types.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <libraries/gadtools.h>
 
#include <proto/exec.h>
#include <proto/gadtools.h>
#include <proto/intuition.h>
 
struct Library *GadToolsBase = NULL;
struct GadToolsIFace *IGadTools = NULL;
 
struct Library *IntuitionBase = NULL;
struct IntuitionIFace *IIntuition = NULL;
 
struct NewMenu mynewmenu[] =
    {
        { NM_TITLE, "Project",    0 , 0, 0, 0,},
        {  NM_ITEM, "Open...",   "O", 0, 0, 0,},
        {  NM_ITEM, "Save",      "S", 0, 0, 0,},
        {  NM_ITEM, NM_BARLABEL,  0 , 0, 0, 0,},
        {  NM_ITEM, "Print",      0 , 0, 0, 0,},
        {   NM_SUB, "Draft",      0 , 0, 0, 0,},
        {   NM_SUB, "NLQ",        0 , 0, 0, 0,},
        {  NM_ITEM, NM_BARLABEL,  0 , 0, 0, 0,},
        {  NM_ITEM, "Quit...",   "Q", 0, 0, 0,},
 
        { NM_TITLE, "Edit",       0 , 0, 0, 0,},
        {  NM_ITEM, "Cut",       "X", 0, 0, 0,},
        {  NM_ITEM, "Copy",      "C", 0, 0, 0,},
        {  NM_ITEM, "Paste",     "V", 0, 0, 0,},
        {  NM_ITEM, NM_BARLABEL,  0 , 0, 0, 0,},
        {  NM_ITEM, "Undo",      "Z", 0, 0, 0,},
 
        {   NM_END, NULL,         0 , 0, 0, 0,},
    };
 
 
/*
** Watch the menus and wait for the user to select the close gadget
** or quit from the menus.
*/
VOID handle_window_events(struct Window *win, struct Menu *menuStrip)
{
struct IntuiMessage *msg;
BOOL done;
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) &&
            (NULL != (msg = (struct IntuiMessage *)IExec->GetMsg(win->UserPort))))
        {
        switch (msg->Class)
            {
            case IDCMP_CLOSEWINDOW:
                done = TRUE;
                break;
            case IDCMP_MENUPICK:
                menuNumber = msg->Code;
                while ((menuNumber != MENUNULL) && (!done))
                    {
                    item = IIntuition->ItemAddress(menuStrip, menuNumber);
 
                    /* process the item here! */
                    menuNum = MENUNUM(menuNumber);
                    itemNum = ITEMNUM(menuNumber);
                    subNum  = SUBNUM(menuNumber);
 
                    /* stop if quit is selected. */
                    if ((menuNum == 0) && (itemNum == 5))
                        done = TRUE;
 
                    menuNumber = item->NextSelect;
                    }
                break;
            }
        IExec->ReplyMsg((struct Message *)msg);
        }
    }
}
 
 
/*
** Open all of the required libraries and set-up the menus.
*/
int main(int argc, char *argv[])
{
struct Window *win;
APTR *my_VisualInfo;
struct Menu *menuStrip;
 
IntuitionBase = IExec->OpenLibrary("intuition.library", 50);
IIntuition = (struct IntuitionIFace*)IExec->GetInterface(IntuitionBase, "main", 1, NULL);
 
GadToolsBase = IExec->OpenLibrary("gadtools.library", 50);
IGadTools = (struct GadToolsIFace*)IExec->GetInterface(GadToolsBase, "main", 1, NULL);
 
if (IIntuition != NULL && IGadTools != NULL)
    {
        if (NULL != (win = IIntuition->OpenWindowTags(NULL,
                            WA_Width,  400,       WA_Activate,    TRUE,
                            WA_Height, 100,       WA_CloseGadget, TRUE,
                            WA_Title,  "Menu Test Window",
                            WA_IDCMP,  IDCMP_CLOSEWINDOW | IDCMP_MENUPICK,
                            TAG_END)))
            {
            if (NULL != (my_VisualInfo = IGadTools->GetVisualInfo(win->WScreen, TAG_END)))
                {
                if (NULL != (menuStrip = IGadTools->CreateMenus(mynewmenu, TAG_END)))
                    {
                    if (IGadTools->LayoutMenus(menuStrip, my_VisualInfo, TAG_END))
                        {
                        if (IIntuition->SetMenuStrip(win, menuStrip))
                            {
                            handle_window_events(win,menuStrip);
 
                            IIntuition->ClearMenuStrip(win);
                            }
                        IGadTools->FreeMenus(menuStrip);
                        }
                    }
                IGadTools->FreeVisualInfo(my_VisualInfo);
                }
            IIntuition->CloseWindow(win);
            }
        }
    }
IExec->DropInterface((struct Interface*)IGadTools);
IExec->CloseLibrary(GadToolsBase);
 
IExec->DropInterface((struct Interface*)IIntuition);
IExec->CloseLibrary(IntuitionBase);
 
return 0;
}

Functions for GadTools Menus

In this section the basic GadTools menu functions are presented. See the listing above for an example of how to use these functions.

Creating Menus

The CreateMenus() function takes an array of NewMenus and creates a set of initialized and linked Intuition Menu, MenuItem, Image and IntuiText structures, that need only to be formatted before being used. Like the other tag-based functions, there is a CreateMenusA() call that takes a pointer to an array of TagItems and a CreateMenus() version that expects to find its tags on the stack.

struct Menu *CreateMenusA( struct NewMenu *newmenu, struct TagItem *taglist );
struct Menu *CreateMenus( struct NewMenu *newmenu, Tag tag1, ... );

The first argument to these functions, newmenu, is a pointer to an array of NewMenu structures as described earlier. The tag arguments can be any of the following items:

GTMN_FrontPen (ULONG)
The pen number to use for menu text and separator bars. The default is zero.
GTMN_FullMenu (BOOL)
This tag instructs CreateMenus() to fail if the supplied NewMenu structure does not describe a complete Menu structure. This is useful if the application does not have direct control over the NewMenu description, for example if it has user-configurable menus. The default is FALSE.
GTMN_SecondaryError (ULONG *)
This tag allows CreateMenus() to return some secondary error codes. Supply a pointer to a NULL-initialized ULONG, which will receive an appropriate error code as follows:
GTMENU_INVALID Invalid menu specification. For instance, a sub-item directly following a menu-title or an incomplete menu. CreateMenus() failed in this case, returning NULL.
GTMENU_NOMEM Failed for lack of memory. CreateMenus() returned NULL.
GTMENU_TRIMMED The number of menus, items or sub-items exceeded the maximum number allowed so the menu was trimmed. In this case, CreateMenus() does not fail but returns a pointer to the trimmed Menu structure.
NULL If no error was detected.

CreateMenus() returns a pointer to the first Menu structure created, while all the MenuItem structures and any other Menu structures are attached through the appropriate pointers. If the NewMenu structure begins with an entry of type NM_ITEM or IM_ITEM, then CreateMenus() will return a pointer to the first MenuItem created, since there will be no first Menu structure. If the creation fails, usually due to a lack of memory, CreateMenus() will return NULL.

GadTools will not create any menus, menu items or sub-items in excess of the maximum number allowed by Intuition. Up to 31 menus may be defined, each menu with up to 63 items, each item with up to 31 sub-items. See Intuition Menus for more information on menus and their limitations. If the NewMenu array describes a menu that is too big, CreateMenus() will return a trimmed version. GTMN_SecondaryError can be used to learn when this happens.

Menus need to be added to the window with Intuition's SetMenuStrip() function. Before doing this, they must be formatted with a call to LayoutMenus().

Layout of the Menus

The Menu and MenuItem structures returned by CreateMenus() contain no size or positional information. This information is added in a separate layout step, using LayoutMenus(). As with the other tag-based functions, the program may call either LayoutMenus() or LayoutMenusA().

BOOL LayoutMenusA( struct Menu *firstmenu, APTR vi, struct TagItem *taglist );
BOOL LayoutMenus( struct Menu *firstmenu, APTR vi, Tag tag1, ... );

Set firstmenu to a pointer to a Menu structure returned by a previous call to CreateMenus(). The vi argument is a a VisualInfo handle obtained from GetVisualInfo(). See the documentation of GadTools gadgets below for more about this call. For the tag arguments, tag1 or taglist, LayoutMenus() recognizes the following tags:

GTMN_TextAttr
A pointer to an openable font (TextAttr structure) to be used for the menu item and sub-item text. The default is to use the screen's font.
GTMN_NewLookMenus
A boolean value indicating whether you want the 3D menu look or not. Defaults to FALSE for compatibility.

LayoutMenus() fills in all the size, font and position information for the menu strip. LayoutMenus() returns TRUE if successful and FALSE if it fails. The usual reason for failure is that the font supplied cannot be opened.

LayoutMenus() takes care of calculating the width, height and position of each individual menu item and sub-item, as well as the positioning of all menus and sub-menus. In the event that a menu would be too tall for the screen, it is broken up into multiple columns. Additionally, whole menus may be shifted left from their normal position to ensure that they fit on screen. If a large menu is combined with a large font, it is possible, even with columnization and shifting, to create a menu too big for the screen. GadTools does not currently trim off excess menus, items or sub-items, but relies on Intuition to clip menus at the edges of the screen.

It is perfectly acceptable to change the menu layout by calling ClearMenuStrip() to remove the menus, then LayoutMenus() to make the change and then SetMenuStrip() to display the new layout. Do this when changing the menu's font (this can be handled by a tag to LayoutMenus()), or when updating the menu's text (to a different language, for instance). Run-time language switching in menus will be discussed later.

Layout for Individual Menus

LayoutMenuItems() performs the same function as LayoutMenus(), but only affects the menu items and sub-items of a single menu instead of the whole menu strip. Ordinarily, there is no need to call this function after having called LayoutMenus(). This function is useful for adding menu items to an extensible menu, such as the Workbench "Tools" menu.

For example, a single MenuItem can be created by calling CreateMenus() with a two-entry NewMenu array whose first entry is of type NM_ITEM and whose second is of type NM_END. The menu strip may then be removed and this new item linked to the end of an extensible menu by placing its address in the NextItem field of the last MenuItem in the menu. LayoutMenuItems() can then be used to to recalculate the layout of just the items in the extensible menu and, finally, the menu strip can be reattached to the window.

BOOL LayoutMenuItemsA( struct MenuItem *firstitem, APTR vi, struct TagItem *taglist );
BOOL LayoutMenuItems( struct MenuItem *firstitem, APTR vi, Tag tag1, ... );

Set firstitem to a pointer to the first MenuItem in the linked list of MenuItems that make up the Menu. (See Intuition Menus for more about these structures.) Set vi to the address of a VisualInfo handle obtained from GetVisualInfo(). The tag arguments, tag1 or taglist, may be set as follows:

GTMN_TextAttr
A pointer to an openable font (TextAttr structure) to be used for the menu item and sub-item text. The default is to use the screen’s font.
GTMN_Menu
Use this tag to provide a pointer to the Menu structure whose FirstItem is passed as the first parameter to this function. This tag should always be used.

LayoutMenuItems() returns TRUE if it succeeds and FALSE otherwise.

Freeing Menus

The FreeMenus() function frees all the memory allocated by the corresponding call to CreateMenus().

VOID FreeMenus( struct Menu *menu );

Its one argument is the Menu or MenuItem pointer that was returned by CreateMenus(). It is safe to call FreeMenus() with a NULL parameter, the function will then return immediately.

GadTools Menus and IntuiMessages

If the window uses GadTools menus and GadTools gadgets, then use the GT_GetIMsg() and GT_ReplyIMsg() functions described below (or GT_FilterIMsg() and GT_PostFilterIMsg(), if applicable). However, if the window has GadTools menus, but no GadTools gadgets, it is acceptable to use GetMsg() and ReplyMsg() in the usual manner.

Additionally, no context need be created with CreateContext() if no GadTools gadgets are used. For more about these functions, see the section on "Other GadTools Functions" below.

Restrictions on GadTools Menus

GadTools menus are regular Intuition menus. Once the menus have been laid out, the program may do anything with them, including attaching them or removing them from windows, enabling or disabling items, checking or unchecking checkmarked menu items, etc. See the documentation for SetMenuStrip(), ClearMenuStrip(), ResetMenuStrip(), OnMenu() and OffMenu() in Intuition Menus for full details.

If a GadTools-created menu strip is not currently attached to any window, the program may change the text in the menu headers (Menu->MenuName), the command-key equivalents (MenuItem->Command) or the text or imagery of menu items and sub-items, which can be reached as:

((struct IntuiText *)MenuItem->ItemFill)->IText

or

((struct Image *)MenuItem->ItemFill)

The application may also link in or unlink menus, menu items or sub-items. However, do not add sub-items to a menu item that was not created with sub-items and do not remove all the sub-items from an item that was created with some.

Any of these changes may be made, provided the program subsequently calls LayoutMenus() or LayoutMenuItems() as appropriate. Then, reattach the menu strip using SetMenuStrip().

Some of these manipulations require walking the menu strip using the usual Intuition-specified linkages. Beginning with the first Menu structure, simply follow its FirstItem pointer to get to the first MenuItem. The MenuItem->SubItem pointer will lead to the sub-menus. MenuItems are connected via the MenuItem->NextItem field. Successive menus are linked together with the Menu->NextMenu pointer. Again, see Intuition Menus for details.

Language-Sensitive Menus

Allowing the application to switch the language displayed in the menus, can be done quite easily. Simply detach the menu strip and replace the strings in the IntuiText structures as described above. It may be convenient to store some kind of index number in the Menu and MenuItem UserData which can be used to retrieve the appropriate string for the desired language. After all the strings have been installed, call LayoutMenus() and SetMenuStrip().

If the application has the localized strings when the menus are being created, it simply places the pointers to the strings and command shortcuts into the appropriate fields of the NewMenu structure. The menus may then be processed in the normal way.