MDEFS, Part 2
Volume Number: 10
Issue Number: 7
Column Tag: Getting Started
Related Info: Menu Manager Custom Menus
MDEFS, Part 2
Custom menus give you more control over behavior and appearance
By Dave Mark, MacTech Magazine Regular Contributing Author
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
Last month’s column presented two separate programs. The first implemented a
menu definition procedure (a.k.a. MDEF), and the second was an application whose sole
purpose was to take the MDEF for a test drive. In this month’s column, we’ll start off
with a little bit of background on menu definition procedures, followed by a
walk-through of both programs.
The Menu Definition Procedure
A normal Macintosh application features a menu bar made up of individual menus.
At initialization time you’ll set up the menu bar (perhaps basing it on an MBAR
resource). If a command-key keyDown or autoKey occurs, you’ll pass the character
code to the Menu Manager function MenuKey(). MenuKey() maps the character code
into its corresponding menu and item, returning the two-byte menu and item codes
packed into a four-byte long.
When you detect a mouseDown in the menu bar, you’ll call the Menu Manager
function MenuSelect(). MenuSelect() tracks the movement of the mouse until the
mouse button is released. As the mouse passes over each menu title in the menu bar,
MenuSelect() calls the menu definition procedure specified by that menu.
A menu definition procedure (more commonly known by its resource name,
MDEF) is a chunk of code that brings a menu to life. An MDEF is designed to respond to
different requests from the Menu Manager. The first request asks the MDEF to
calculate the bounding rectangle for the menu. This is usually based on the width of the
menu’s largest item and the number of items in the menu. Once it has the menu’s
bounding rectangle, the Menu Manager can figure out exactly what pixels will be
covered by the menu once it is drawn. Once it knows this, the Menu Manager copies
those pixels to another location in memory so it can blast them back in as soon as the
menu disappears.
Once it knows the menu’s bounding rectangle, the Menu Manager makes a second
request, asking the MDEF to draw the menu inside the bounding rectangle. The MDEF
mechanism gives your application incredible flexibility. If you like, you can make
your menu look exactly like the standard Macintosh menu, drawing each item using
12-point Chicago and including the appropriate appointments, like command-key
equivalents, check boxes and the like, and even an icon or two.
On the other hand, you can construct a menu that goes where no menu has gone
before. Your menu can be made up of pictures, icons, text, anything you like. You can
draw in black and white, gray-scale, or even living color. If you can draw it in a
window, you can draw it in your menu.
Once the menu is drawn, the Menu Manager makes a third request, passing a
mouse location to the MDEF. The MDEF uses the mouse location to determine which item
should be highlighted. The MDEF unhighlights any previously highlighted item,
highlights the new item, and returns the number of the newly highlighted item.
The flexibility of the MDEF lets you create all kinds of interesting menus. For
example, you might create a menu with items in multiple columns. Figure 1 shows a
menu that supports color and keeps its items in multiple rows and columns.
Figure 1 This MDEF lets you select from a
two-dimensional palette of colors.
Writing an MDEF
Writing a menu definition procedure is not quite the same as writing an
application. First off, you’ll need to tell the compiler that you want to create a code
resource. With THINK C, you’ll do this in the Set Project Type... dialog (Figure 2).
Clicking the Code Resource radio button tells THINK C you want to create a code
resource instead of an application. Since you are creating a resource and not an
application file, you can ask THINK C to save the resource in a resource file. Setting
the File Type to rsrc and the Creator to RSED will launch ResEdit when you
double-click on the output file. Use a File Type of RSRC and a Creator of Doug if you
want to launch Resorcerer when you double-click on the output file.
Figure 2 The Set Project Type... dialog,
showing settings for an MDEF.
The Type, Name, and ID fields tell THINK C what resource type to create, and what
name and ID to give the new resource. In our case, we want to create an ‘MDEF’
resource with a name of “PICT” and an ID of 128. When you select Build Code
Resource... from THINK C’s Project menu, once your code compiles you’ll be prompted
for the name of a resource file in which to save your MDEF. This dialog box will
feature a Merge checkbox, that let’s you add the resource to an existing resource file
instead of creating a new one. To do this, type the name of an existing file in the file
name field.
Now that your project is set up, let’s talk about the source code that actually
implements the MDEF. Your source code will have a single entry point that follows this
function prototype:
/* 1 */
pascal void MenuDef( short message, MenuHandle menu,
Rect menuRectPtr, Point hitPt, short itemPtr );
The pascal keyword tells you that this code will be called by a Toolbox routine.
The first parameter tells you what the Menu Manager wants you to do during this call.
The four possible messages are:
• mSizeMsg - Calculate the menu’s width and height.
• mDrawMsg - Draw the menu.
• mChooseMsg - Figure out which item was selected, deselect any highlighted items
if necessary, and highlight the appropriate item.
• mPopUpMsg - Calculate the bounding rectangle in the case where the MDEF is
used to implement a popup menu.
The second parameter is a MenuHandle, a handle to the menu which specified this
MDEF in the first place. When message is either mDrawMsg or mChooseMsg, the third
parameter specifies the menu’s bounding rectangle in global coordinates. It’s your job
to make sure that the menu fits on the screen. If their are too many items for the menu
to fit, you should create a scrolling menu with up (s) and down (t) scroll indicators as
needed.
When the message is mPopUpMsg, the Menu Manager is asking the MDEF to
calculate the rectangle, in global coordinates, in which the menu should appear.
Typically, you’ll use the fourth parameter, hitPt, to determine the position of the
menu. If appropriate, place the menu so that the default (or last selected) item is at
hitPt, so that the item is selected as soon as the menu appears. Of course, if this puts
part of the menu off the screen, you’ll have to make allowances.
By the way, code resources (such as the MDEF, LDEF, and WDEF) can’t have
global variables. Fortunately, we can get by without them.
When the message is mSizeMsg, the Menu Manager is asking the MDEF to
calculate the menu’s height and width and place those values in the MenuRecord’s
menuHeight and menuWidth fields.
As already mentioned, the fourth parameter contains the mouse position, in
global coordinates, for use when the message is either mChooseMsg or mPopUpMsg. On
mChooseMsg, you’ll use hitPt to figure out which item should be selected. If the mouse
was in a scroll indicator, you should scroll the menu one item in the appropriate
direction (you’ll probably want to take advantage of ScrollRect()). On mPopUpMsg,
hitPt contains the upper-left corner of the popup label.
The last parameter, itemPtr, points to a short containing the last item selected,
or 0 if no item was selected yet. You’ll use this information when message is
mChooseMsg to determine whether an item is currently highlighted and, if the
highlighted item isn’t the same as the newly selected item, to unhighlight the old item.
Creating an MDEF for the PowerPC requires a little bit of extra work (and goes
beyond the scope of this article). To learn how to deal with executable resources on a
PowerPC, you’ll need to read Inside Macintosh: PowerPC System Software. You’ll want
to focus on the Mixed Mode Manager and the Code Fragment Manager.
Walking Through MDEF.c
OK, here’s the MDEF source code. As you’ve already seen, this MDEF creates a
menu built completely out of PICT resources (Figure 3). It doesn’t handle scrolling
and it doesn’t handle the mPopUpMsg message. Your mission, should you choose to
accept it, is to add them yourself.
kTopMargin is the number of pixels of blank space placed on top and below each
PICT in the menu. kLeftMargin is the number of pixels placed to the left and right of
each PICT.
/* 2 */
#define kTopMargin 1
#define kLeftMargin 2
Here’s the function prototypes:
/* 3 */
void DoSizeMessage( MenuHandle menu, Rect *menuRectPtr );
void DoDrawMessage( MenuHandle menu, Rect *menuRectPtr );
void DoChooseMessage( MenuHandle menu, Rect *menuRectPtr,
Point hitPt, short * whichItemPtr );
void InvertItem( short itemNumber, short itemHeight, Rect
*menuRectPtr );
void DrawCenteredPict( PicHandle pic, Rect *rectPtr );
void CalcitemHeightAndWidth( short basePICTid, short numPICTs,
short *widthPtr, short *heightPtr );
void GetNumPICTs( MenuHandle menu, short *baseIDPtr,
short *numPICTsPtr );
main() is declared according to the function prototype described at the beginning
of the column.
/* 4 */
main
pascal void main( short message,
MenuHandle menu,
Rect *menuRectPtr,
Point hitPt,
short * whichItemPtr )
{
Each of the messages (remember, we don’t handle mPopUpMsg) is handled by its
own routine.
/* 5 */
switch( message )
{
case mDrawMsg:
DoDrawMessage( menu, menuRectPtr );
break;
case mChooseMsg:
DoChooseMessage( menu, menuRectPtr, hitPt, whichItemPtr );
break;
case mSizeMsg:
DoSizeMessage( menu, menuRectPtr );
break;
}
}
DoSizeMessage() starts with a call to GetNumPICTs(). GetNumPICTs() looks for
a ‘long’ resource with the same resource ID as the current menu. The ‘long’ resource
is not a standard Mac resource. It consists of 2 sets of 2 bytes. The first 2 bytes of the
resource specify the resource ID of the first PICT to display in this menu. The second 2
bytes tell you how many PICT’s to display. If the base resource ID is 128 and the
number of PICT’s is 3, the menu will display PICT resources 128, 129, and 130.
/* 6 */
DoSizeMessage
void DoSizeMessage( MenuHandle menu,