Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Macintosh Toolbox Essentials /
Chapter 3 - Menu Manager / Using the Menu Manager


Writing Your Own Menu Definition Procedure

The Menu Manager uses the menu definition procedure and menu bar definition function to display and perform basic operations on menus and the menu bar. The
menu definition procedure performs all the drawing of menu items within a menu
and performs all the actions that might differ between one type of menu and another. The menu bar definition function draws the menu bar and performs most of the
drawing activities related to the display of menus when the user moves the cursor between menus.

Apple provides a standard menu bar definition function, stored as a resource in the System file. The standard menu bar definition procedure is the 'MBDF' resource with resource ID 0. When you create your menus and menu bar, by default the Menu Manager uses the standard menu bar definition function to manage them. Although the Menu Manager lets you provide your own menu bar definition function, Apple recommends that you always use the standard menu bar definition function.

The Menu Manager uses the standard menu bar definition function to

Apple provides a standard menu definition procedure, stored as a resource in the System file. The standard menu definition procedure is the 'MDEF' resource with resource ID 0. The standard menu definition procedure handles three types of menus: pull-down, pop-up, and hierarchical; it also implements scrolling in menus. When you define your menus, you specify the menu definition procedure that the Menu Manager should use when managing them. You'll usually want to use the standard definition procedure for your application. However, if you need a feature not provided by the standard menu definition procedure (for example, if you want to include more graphics in your menus), you can write your own menu definition procedure.

The Menu Manager uses the standard menu definition procedure to

If you provide your own menu definition procedure, it should also perform these tasks. Your menu definition procedure should also support scrolling in menus and color in menus and provide support for Balloon Help.

If you provide your own menu definition procedure, store it in a resource of type 'MDEF' and include its resource ID in the description of each menu that uses your own menu definition procedure. If you create a menu using GetMenu (or GetNewMBar), the Menu Manager reads the menu definition procedure into memory and stores a handle to it in the menuProc field of the menu's menu record.

When your application uses GetMenu (or GetNewMBar) to create a new menu that uses your menu definition procedure, the Menu Manager creates a menu record for the menu and fills in the menuID, menuProc, enableFlags, and menuData fields according to the menu's resource description. The Menu Manager also reads in the data for each menu item and stores it as variable data at the end of the menu record. The menu definition procedure is responsible for interpreting the contents of the data. For example, the standard menu definition procedure interprets this data as described in "The Menu Resource" beginning on page 3-151. After reading in a resource description of a menu, the Menu Manager requests the menu definition procedure to calculate the size of
the menu and to store these values in the menuWidth and menuHeight fields of the menu's menu record.

Note that when drawing a menu, the Menu Manager first requests your menu definition procedure to calculate the dimensions (the menu rectangle) of the menu. Next the Menu Manager requests the menu bar definition function to draw the structure (shadow) of the menu and erase the contents of the menu to its background color. Then the Menu Manager requests your menu definition procedure to draw the items in the menu. As the user moves the cursor into and out of menu items, the Menu Manager requests your menu definition procedure to highlight and unhighlight items appropriately. Your menu definition procedure should also determine when to add scrolling indicators to a menu and scroll the menu appropriately when the cursor is in a scrolling item. Your menu definition is responsible for showing and removing any help balloons associated with a menu item.

When the Menu Manager requests your menu definition procedure to perform an action on a menu, it provides your procedure with a handle to its menu record. This allows your procedure to access the data in the menu record and to use any data in the variable data portion of the menu record to appropriately handle the menu items. However, your menu definition procedure should not assume that the A5 register is properly set up, so your procedure can't refer to any of the QuickDraw global variables.

The Menu Manager passes a value to your menu definition procedure in the message parameter that indicates the action your menu definition procedure should perform. The Menu Manager always passes a handle to the menu record of the menu that the operation should affect in the parameter theMenu. Depending on the requested action, the Menu Manager passes additional information in other parameters.

Listing 3-28 shows how you might declare a menu definition procedure.

Listing 3-28 A sample menu definition procedure

PROCEDURE MyMDEF (message: Integer; theMenu: MenuHandle; 
                  VAR menuRect: Rect; hitPt: Point; 
                  VAR whichItem: Integer); 

{any support routines used by the main program of your MDEF }
{ go here}

BEGIN
   CASE message OF
      mDrawMsg: 
         MyDrawMenu(theMenu, menuRect);
      mChooseMsg: 
         MyChooseItem(theMenu, menuRect, hitPt, whichItem);
      mSizeMsg: 
         MySizeTheMenu(theMenu);
      mPopUpMsg: 
         MyCalcMenuRectForOpenPopUpBox(theMenu, hitPt, menuRect);
   END;
END;
The next sections describe in more detail how your menu definition procedure should respond when it receives the mDrawMsg, mChooseMsg, or mSizeMsg constant in the message parameter. For a complete description of the menu definition procedure and the parameters passed to your procedure by the Menu Manager, see "The Menu Definition Procedure" beginning on page 3-148.

Calculating the Dimensions of a Menu

Whenever the Menu Manager creates a menu or needs to calculate the size of a menu that is managed by your menu definition procedure, the Menu Manager calls your procedure and specifies the mSizeMsg constant in the message parameter, requesting that your procedure calculate the size of the menu.

Listing 3-29 on page 3-92 shows an application-defined support routine, MySizeTheMenu, used by the application's menu definition procedure. After
calculating the height and width of the menu's rectangle, the menu definition
procedure stores the values in the menuWidth and menuHeight fields of the
menu's menu record.

Listing 3-29 Calculating the size of a menu

PROCEDURE MySizeTheMenu(theMenu: MenuHandle);
VAR 
   itemDataPtr:   Ptr; 
   numItems:      Integer;
BEGIN
   HLock(Handle(theMenu));
   WITH theMenu^^ DO
   BEGIN    {menuData points to title of menu and additional item data}
      itemDataPtr := @menuData;
      {skip past the menu title}
      itemDataPtr := POINTER(ORD4(itemDataPtr)+ itemDataPtr^ +1);
   END;
   numItems := CountMItems(theMenu);
   {calculate the height of the menu--each item's height can vary }
   { according to whether the item has an icon or a script code defined. }
   { The height of the menu should not exceed the height of the }
   { screen minus the menu bar height. }
   { Store the height in the menu's menu record}
   theMenu^^.menuHeight := MyCalcMenuHeight(itemDataPtr, numItems);

   {calculate the width of the menu (the width of the longest item): }
   { for each item calculate the width as }
   { width = iconWidth + markWidth + textWidth + subMenuWidth }
   {         + cmdKeyComboWidth }
   { If an item doesn't have a characteristic, use 0 as the width of }
   { that characteristic. }
   { To calculate the width of item's text, must consider script code and }
   { width of the font. }
   { The width of the menu should not exceed the right or left }
   { boundaries of the screen. }
   { Store the width in the menu's menu record}
   theMenu^^.menuWidth := MyCalcMenuWidth(itemDataPtr, numItems);
   HUnLock(Handle(theMenu));
END;

Drawing Menu Items in a Menu

Whenever the user presses the mouse button while the cursor is in the menu title of a menu managed by your menu definition procedure, the Menu Manager calls the menu bar definition function to highlight the menu title, draw the structure of the menu, and erase the contents of the menu to its background color. The Menu Manager then calls your menu definition procedure and specifies the mDrawMsg constant in the message parameter, requesting that your procedure draw the menu items. When your menu definition procedure receives this constant, it should draw the menu items of the menu specified by the parameter theMenu inside the rectangle specified by the menuRect parameter. The Menu Manager sets the current graphics port to the Window Manager port before calling your menu definition procedure. Your menu definition procedure can determine how to draw the menu items by examining the data in the menu record.

If your menu definition procedure supports color menus, your procedure should
check the application's menu color information table for the colors to use to draw
each item. If the application's menu color information table contains a color entry for
an item, draw the item using that color. If the table does not contain an item entry for
a particular item, use the default item color defined in the menu title entry. If a menu title entry doesn't exist, use the default item color defined in the menu bar entry. If the menu bar entry doesn't exist, draw the item using black on white.

If your menu definition procedure supports scrolling menus, it should insert scrolling indicators if necessary when drawing the menu items.

Listing 3-30 shows an application-defined support routine, MyDrawMenu, used by the application's menu definition procedure. The MyDrawMenu procedure draws each item in the menu, according to the item's defined characteristics. Disabled items should be drawn using the colors returned by the GetGray function. Pass the RGB color of the item's background in the bkgnd parameter to the GetGray function; pass the RGB color of the item's enabled text in the fgnd parameter. The GetGray function returns TRUE if there's an available color between the two specified colors and returns in the fgnd parameter the color in which you should draw the item.

Listing 3-30 Drawing menu items

PROCEDURE MyDrawMenu(theMenu: MenuHandle; menuRect: Rect);
VAR 
   numItems:      Integer;
   itemRect:      Rect;
   item:          Integer;
   currentOffset: LongInt;
   nextOffset:    LongInt;
BEGIN
   numItems := CountMItems(theMenu);
   currentOffset := 0;
   nextOffset := 0;
   FOR item := 1 TO numItems DO
   BEGIN
   {calculate the enclosing rectangle for this item}
      itemRect := MyCalcItemRect(item, menuRect, currentOffset, nextOffset);
   {draw the item--index into the item-specific data from the menu record }
   { to get the characteristics of this menu item and draw the item }
   { according to its defined characteristics. For example, draw the item's }
   { text in its defined style & font of its defined script, draw any icon, }
   { mark, submenu indication, or keyboard equivalent, and draw each }
   { characteristic of the item according to its color entry in the menu's }
   { menu color information table. }
   { Draw disabled items in gray--use the GetGray function to return the }
   { appropriate color. Also draw dividers using the gray color }
   { returned by GetGray}
      MyDrawTheItem(item, itemRect, menuRect, currentOffset);
   END;
   {if your menu supports scrolling, insert scrolling indicators if needed}
   MyInsertScrollingArrows(menuRect);
END;

Determining Whether the Cursor Is in an Enabled Menu Item

Whenever the user drags the cursor into or out of a menu item of a displayed menu managed by your menu definition procedure, the Menu Manager calls your procedure and specifies the mChooseMsg constant in the message parameter, requesting that
your procedure determine whether the cursor is in a menu item and that your procedure highlight or unhighlight the menu item as appropriate. When your menu definition procedure receives this constant, it should use the menu rectangle specified in the menuRect parameter, the mouse location specified in the hitPt parameter, and the item number specified in the whichItem parameter to determine the proper action
to take.

To see whether the user chose an enabled item, your menu definition procedure should determine whether the specified mouse location is inside the rectangle specified by the menuRect parameter, and, if so, it should check whether the menu is enabled. If the menu is enabled, your menu definition procedure should determine whether the mouse location specified in the hitPt parameter is in an enabled menu item.

If the mouse location is in an enabled menu item, your menu definition procedure should unhighlight the item specified by the whichItem parameter, highlight the new item, and return the new item number in whichItem.

If the mouse location isn't in an enabled menu item, your menu definition procedure should unhighlight the item specified by the whichItem parameter and return 0 in
the whichItem parameter.

When your menu definition procedure draws a menu item in its highlighted state in a color menu, it should reverse the background color and the item color and then draw the menu item. When your menu definition procedure needs to return a menu item to its normal (unhighlighted) state, it should reset the background color and item color of that menu item and draw the menu item.

If your menu definition procedure supports scrolling menus, it should scroll the menu when the user moves the cursor into the area of the indicator, or when the cursor is directly above or below the menu. If the user can scroll the menu up (by dragging the cursor past the last item to view more items), place a downward-pointing triangular indicator in place of the last item in the menu. If the user can scroll the menu down
(by dragging the cursor past the first item to view the items originally at the top of
the menu), place an upward-pointing triangular indicator in place of the first item
in the menu.

For all menus, your menu definition procedure should set the global variable MenuDisable appropriately each time a new item is highlighted. Set MenuDisable to the menu ID and item number of the last menu item chosen, whether or not it's disabled. The MenuChoice function uses the value in MenuDisable to determine if a chosen menu item is disabled.

Listing 3-31 shows an application-defined support routine, MyChooseItem, used by the application's menu definition procedure. This routine determines which item, if any, the point specified by the hitPt parameter is in. If the item is in an enabled menu item that is different from the previous item, the MyChooseItem procedure unhighlights the old item and highlights the new item. However, the MyChooseItem procedure does not highlight the new item if the item is in a divider or disabled item.

The procedure also removes any help balloons as appropriate and, if Balloon Help is turned on, displays any help balloon of the new item (for any item other than a divider or scrolling indicator). The MyChooseItem procedure returns the item number of the new item in the whichItem parameter or returns 0 if no item is chosen. Although not shown in the listing, if the item is a disabled item, the procedure returns 0 in the whichItem parameter and sets the MenuDisable global variable to the menu ID and item number of the disabled item.

Listing 3-31 Choosing menu items

PROCEDURE MyChooseItem (theMenu: MenuHandle; menuRect: Rect; hitPt: Point;
                        VAR whichItem: Integer);
VAR 
   oldWhichItem:        Integer;
   MenuChoicePtr:       ^LongInt;
   numItems, item, max: Integer;
   itemChosen:          Integer;
   inScroll:            Integer;
   currentOffset:       LongInt;
   nextOffset:          LongInt;
BEGIN
   oldWhichItem := whichItem;
   whichItem := 0;
   itemChosen := 0;
   MenuChoicePtr := POINTER(kLowMemMenuDisable); 
   numItems := CountMItems(theMenu);
   {find out whether the hitPt is in an item's rectangle, and if so, }
   { determine which item}
   item := 1;
   max := numItems + 1;
   currentOffset := 0;
   nextOffset := 0;
   REPEAT
      itemRect := MyCalcItemRect(item, menuRect, currentOffset, nextOffset);
      IF PtInRect(hitPt, itemRect) THEN {hitPt is in this item}
         itemChosen := item;
      item := item + 1;
   UNTIL (item = MAX) OR (itemChosen <> 0);
   IF itemChosen = 0 THEN
   BEGIN {the mouse isn't in any item of this menu;unhighlight previous item}
      MyNotInMenu(menuRect, oldWhichItem);   
   END
   ELSE  
   BEGIN {the mouse is in this menu item. }
         { First see if a previous item was highlighted}
      IF ((oldWhichItem <> 0) AND (oldWhichItem <> itemChosen)) THEN
      BEGIN
         {a previous item was highlighted--unhighlight it}
         itemRect := MyCalcOldItemRect(oldWhichItem, menuRect);
         IF HMGetBalloons THEN {if Balloon Help is on then }
            HMRemoveBalloon;{ remove any balloon that might be showing}
         MyHighlightItem(itemRect, oldWhichItem, FALSE);
      END;
      IF HMGetBalloons and MyIsItemDivider(itemChosen) THEN 
         {Balloon Help is on and item is divider}
         HMRemoveBalloon;{remove any balloon that might be showing}
      IF MyIsItemEnabled(itemChosen) THEN
      BEGIN
         {the item is enabled, so highlight the item the cursor is in}
         itemRect := MyCalcNewItemRect(itemChosen, menuRect, currentOffset);
         {the highlighting routine must also support scrolling correctly }
         { (if the cursor is in a scrolling item, don't highlight the item)}
         inScroll := MyIsScrollItem(itemChosen);
         MyHighlightItem(itemRect, itemChosen, inScroll);
         IF HMGetBalloons AND inScroll THEN 
            HMRemoveBalloon   {remove any balloon that might be showing}
         ELSE
         BEGIN {display help balloon for this item, if any}
            IF HMGetBalloons THEN


            BEGIN
               IF StillDown THEN {mouse button is still down in this item}
                  {this routine sets up the needed parameters and then }
                  { calls HMShowMenuBalloon}
                  MyShowMenuBalloon(itemChosen, itemRect);
            END;
         END;
      END;
   END;
END;

Previous Book Contents Book Index Next

© Apple Computer, Inc.
11 JUL 1996