Extend Modal Dialog
Volume Number: 6
Issue Number: 4
Column Tag: C Workshop
Extending Modal Dialogs 
By Paul Potts, Wooster, OH
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
The Gauntlet is Thrown Down
A few months ago I was developing a program that used a modal dialog. I showed
the results to my roommate. He ran the program and brought up the dialog, but was
then interrupted. When he got back, the screen saver had come on. He moved the
mouse to refresh the screen, and then scrutinized my display.
“Which button is default?” he asked .
“That one. The program drew the outline, but it got erased.”
“It should be there.”
“Oh, come on! The Finder doesn’t even keep the outline around its default buttons
when you use a screen saver!”
“If it’s in Inside Macintosh, it should be there. Go fix it. Here, make me a copy
first.” He popped in a blank disk. Nothing happened.
“How come it didn’t ask if I want to initialize the disk?”
“That doesn’t work when a modal dialog is active,” I explained lamely.
He then pressed the “Q” key. Nothing happened. “There’s a dialog button called
Quit -- why didn’t it quit?” he asked. “What kind of user-friendly program is this,
anyway?”
The Gauntlet is Picked Up
My roommate was right. We are used to putting up with the limitations of modal
dialogs, but ModalDialog can be extended using filter procedures. The parameters of
ModalDialog can be declared in C as follows:
/* 1 */
void ModalDialog (filterProc, itemHit)
ProcPtr filterProc;
int *itemHit;
The address of your filter procedure gets passed by casting it to a procPtr. If you
don’t want to use a filterProc, you pass NIL as your first parameter. If you do use a
filterProc, Modal dialog then gets each event for you, using a mask which excludes disk
events, and sends them to your filterProc. It is then up to your filterProc to decide
what to do with them. ModalDialog expects your filterProc to be declared as follows:
/* 2 */
pascal Boolean FProc(Dialog, Event, itemHit)
DialogPtr theDialog;
EventRecord *theEvent;
int *itemHit;
A dialog filterProc is actually a function that returns a Boolean value. This value
should be TRUE if you want ModalDialog to exit, and FALSE otherwise. ModalDialog
passes your filterProc a pointer to the current event. You then can do what you want
with it. If your function returns FALSE, ModalDialog will handle the event after you.
Thus, you can get a crack at each event even before ModalDialog does. Your filterProc
can then do any of the following:
1) Handle the event, then send it to ModalDialog
2) Handle the event and tell ModalDialog to exit
3) Change the event and return to ModalDialog
4) Read the event queue itself and act on it.
My filterProc uses the first technique to handle updateEvents before ModalDialog
gets them. This allows me to redraw the default box around a button and then tell
ModalDialog to do its own updating. I use the second technique to exit ModalDialog if my
filterProc receives a key event that it understands. I use the third technique to handle
the key by changing the event from a keypress to a click in the default button.
You don’t actually have to do this for and keypresses, since
ModalDialog handles key events, but I wanted to illustrate the technique of altering an
event.
Scott Knaster, in Macintosh Programming Secrets, suggests that command-key
equivalents be provided for buttons in modal dialog boxes whenever possible.
Command-key equivalents aren’t always necessary, however. Some popular
applications, such as Microsoft Word, provide single-key equivalents for button
clicks, if no editable text fields are present in the dialog, such as in the Save Changes
Before Closing dialog. I suggest that commnad-key equivalents be used in modal dialogs
with editable text fields, and single-key equivalents (or both single-key and
command-key equivalents) be used when no editable text fields are present. My code
illustrates both techniques, but your application should use only one. Even though my
modal dialog has an editable text field, I allows the user to press a 1, 2, or 3 to choose
one of my three buttons. The user can also use -F, S, and T to choose the
first, second, and third dialog items, respectively. NOTE: You must make certain that
the editText item(s) in the DITL resource that you use are set to Disabled, or else my
technique will not work.
When drawing your dialogs with ResEdit, keep in mind that the command-key
character is ASCII 17 in the Chicago font, but cannot be typed directly from the
keyboard. Microsoft Word will generate this character if you use the Change function
to replace a specified character with a ^17. You can then copy this character and paste
it into your ResEdit fields. RMaker can generate this character by using \11.
Since ModalDialog does not pass disk events to the filterProc, I use the fourth
technique to allow the user to insert a blank disk while the ModalDialog is active. It
gets initialized by DIBadMount, which then mounts it (or ejects it, if initialization
failed). This might be useful in a modal dialog which displays on-line volumes.
SFPutFile uses this technique to allow disks to be mounted while its dialog is active.
There are other ways you can handle events in a modal dialog using filterProcs.
You can do anything you want during null events. You can plot a series of icons in your
dialog to give the appearance of animation, or draw the current time using DrawString.
After all, you have the current grafPort, and can draw directly with QuickDraw.
Remember, though, that your drawing operations should be as fast as possible, and
must take much less than a single tick to work effectively. Also, remember your poor
end-user: don’t make your dialogs overly confusing or complex. We want to extend the
Macintosh interface, not bury it.
If you like my code, you are welcome to use it “as-is,” modify it, or completely
rewrite it. I’d appreciate it if you’d put my name somewhere in your application, but
you can drop me a postcard instead.
Listing: driveDialog.c
/* This Think C 3.02/4.0 code should be compiled with MacHeaders
turned on. Create a new project called FilterProc.π, and add this
file, driveDialog.c, along with HandleDialog.c and MacTraps.
Prototypes are provided. Compile the resource file, FilterProc.r,
with RMaker and put it in the folder with the FilterProc project.*/
/*********************************************/
/* File: driveDialog.c */
/* A simple driver application for the dialog
handler. Your own application would call
it instead. */
void main (void);
int HandleDialog(short ID);
void main()
{
int itemHit,
/* Dialog item hit, returned by HandleDialog */
counter;
InitGraf(&thePort);
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs(0L);
FlushEvents(everyEvent, 0);
InitCursor();
/* Now send the HandleDialog function the resource ID of the DLOG to
use */
itemHit = HandleDialog ((short)1);
/* Now beep to tell us what item number the HandleDialog call sent
back (i.e., what item was hit to exit ModalDialog */
for (counter = 1; counter<= itemHit; counter++)
SysBeep(40);
}
Listing: HandleDialog.c
/*****************************************/
/* File HandleDialog.c */
/*****************************************/
/* Prototypes */
int HandleDialog(short Dialog_ID);
/* Called whenever you want to put up a modal dialog */
pascal Boolean FProc(DialogPtr theDialog,
EventRecord *theEvent,
int *itemHit);
/* Filter procedure called by ModalDialog to screen dialog events */
Point CenterDialogItem(DialogPtr theDialog, int
item_number);
/* Returns the center of the rect of a dialog item, to be called from
a filter proc while a modal dialog is active. */
void FlashDialogItem(DialogPtr theDialog, int
item_number);
/* Flashes an item of a dialog, to be called from a filter proc while
a modal dialog is active. */
void HandleUpdate(DialogPtr theDialog);
/* Used to handle update events while a modal
dialog is active, called from a filter proc.*/
/********************************************/
/* This procedure returns the center of a dialog item as a point. It
is designed to be used with buttons, but will work with any type of
dialog item. */
/* Input: theDialog (a DialogPtr), item number (an integer) */
/* Output: a point */
Point CenterDialogItem(theDialog, item_number)
DialogPtr theDialog;
int item_number;
{
Point the_center; /* Center of item, to return */
int itemType; /* Returned by GetDItem but not used */
Handle theItem; /* Returned by GetDItem but not used */
Rect the_Rect; /* Returned by GetDItem */
GetDItem(theDialog, item_number, &itemType, &theItem,
&the_Rect);
the_center.h = the_Rect.left + ((the_Rect.right -
the_Rect.left)/ 2);
the_center.v = the_Rect.top + ((the_Rect.bottom - the_Rect.top)/ 2);
return the_center;
}
/********************************************/
/* This procedure returns the center of the rectangle of the default
dialog item as a single point. */
/* Input: theDialog (a DialogPtr), item_number (an integer) */
/* Output: none */
void FlashDialogItem(theDialog, item_number)
DialogPtr theDialog;
int item_number;
{
long tickScratch; /* returned by Delay, unused */
int itemType; /* Returned by GetDItem but not used */
Handle the_item; /* Handle to default button */
Rect controlRect; /* Returned by GetDItem but not used */
GetDItem(theDialog, item_number, &itemType, &the_item,
&controlRect);
HiliteControl(the_item, 1);
Delay(6, &tickScratch);
}
/********************************************/
/* The following function handles update events for your dialog box.
It is called whenever the filterProc receives an update event. You
can put whatever you want to be drawn in the dialog in it. Right now