Modal Filter 2
Volume Number: 9
Issue Number: 8
Column Tag: Getting Started
The Modal Dialog Filter - Part II 
Preprocessing events for a modal dialog.
By Dave Mark, MacTech Magazine Regular Contributing Author
By the time you read this, summer will be reaching towards the fall and
MacWorld Expo will be on everybody’s mind. If you read this before (or at) MacWorld
expo, stop by and say hello. I’ll be at the Addison-Wesley booth and at MacTech Live!
I’d love to hear from you. By the way, has anyone picked up a copy of Learn C++ on the
Macintosh yet? If you have, drop me a line on CompuServe or AOL and let me know
what you think.
Last month, we entered and ran a program called DLOGFilter. DLOGFilter
implemented a dialog box containing two text-edit fields (see Figure 1). The first field
(labeled Ten chars max:) limited input to a maximum of 10 characters. The second
text-edit field (labeled Number (1-100):) limited input to numeric characters only.
Figure 1. The DLOGFilter dialog box.
The dialog box requires that you enter a number between 1 and 100 in the
Number field. If you click the OK button before a legal number is entered, a warning
message is displayed (see Figure 2) and the dialog sticks around.
Figure 2. Here’s what happens when you click the OK button without entering a
number.
The key to this program is the filter procedure, or filterproc, used to preprocess
all events before they are passed on to the Dialog Manager. In this case, we are
interested in intercepting all keyDown and autoKey events before the Dialog Manager
interprets them as text-edit keystrokes. You’ll see how to do this as we walk through
the DLOGFilter code.
Walking Through the DLOGFilter code
DLOGFilter starts off with a host of #defines. You’ll see these again as they are
used throughout the code. I know this is obvious, but here’s a word or two about the
naming convention I use in my code. Start all constants with a lower-case k, with two
exceptions. Menu and dialog items start with a lower-case i and menu resource id’s
start with a lower case m. Notice that the remainder of the constant name is spelled
according to Pascal rules, as opposed to C rules. Pascal starts each word with an
upper-case letter, while C traditionally uses all upper-case letters, separating each
word by an underscore (_). I think the Pascal method is much easier to read. As it
happens, most of Apple’s C code uses the Pascal convention as well.
/* 1 */
#define kDialogResID 128
#define kMBARid 128
#define kMessageAlertID 129
#define kSleep 60L
#define kMoveToFront (WindowPtr)-1L
#define kNULLFilterProc (ProcPtr)0L
#define kOn 1
#define kOff 0
#define kEditItemExists true
#define kEventNotHandledYet false
#define kEventHandled true
#define kMaxFieldLength 10
#define kEnterKey 3
#define kBackSpaceKey 8
#define kTabKey 9
#define kReturnKey 13
#define kEscapeKey 27
#define kLeftArrow 28
#define kRightArrow 29
#define kUpArrow 30
#define kDownArrow 31
#define kPeriodKey 46
#define kDeleteKey 127
#define iTenCharMaxText 4
#define iNumberText 6
#define mApple 128
#define iAbout 1
#define mFile 129
#define iDialog 1
#define iQuit 3
As usual, we’ve declared the global gDone as a Boolean to tell us when to drop out
of the main event loop. As always, we start global variables with the letter g. There are
lots of other naming conventions for variables. For example, some folks start their
variables with a letter indicating the type of the variable. This can come in handy if
you are writing code that gets shared among a group of people.
/* 2 */
/*************/
/* Globals */
/*************/
Boolean gDone;
Here’s the function prototypes for every single routine in the program. Get in the
habit of providing function prototypes for all your routines. Since C++ requires
function prototypes, this is a good habit to get into.
/* 3 */
/***************/
/* Functions */
/***************/
void ToolboxInit( void );
void MenuBarInit( void );
void EventLoop( void );
void DoEvent( EventRecord * eventPtr );
void HandleMouseDown( EventRecord * eventPtr );
void HandleMenuChoice( long menuChoice );
void HandleAppleChoice( short item );
void HandleFileChoice( short item );
void DoDialog( void );
Here’s an unusual prototype. Look at the return type for the function
DLOGFilter().
/* 4 */
pascal Boolean DLOGFilter( DialogPtr dialog, EventRecord * eventPtr,
short *itemHitPtr );
The pascal keyword tells the compiler that this routine should be called using
Pascal, as opposed to C, calling conventions. Here’s why this is important. When your
code calls a regular C function, the compiler has no trouble using the C
function-calling conventions. When you call a Toolbox function, the rules change a bit.
Since the Toolbox was originally written in Pascal, all calls to it are made using the
Pascal calling conventions. When you call a Toolbox function from your code, the
compiler is smart enough to use the Pascal convention to pass parameters and return
the return value to your code. The compiler knows to do this because the Toolbox
function prototypes use the pascal keyword.
Where things get tricky is when you write a function in C that you’d like to be
called by a Toolbox function. For example, in this program, we’re creating a function
that will be called, periodically, by ModalDialog(). Which conventions do we use, C or
Pascal? As it turns out, we yield to the Toolbox and declare our function using the
pascal keyword. It’s not important to understand the difference between C and Pascal
calling conventions, just as long as you remember this rule: If your routine is designed
to be called by the Toolbox, be sure to declare it using the pascal keyword.
Here’s the rest of the function prototypes.
/* 5 */
Boolean ScrapIsOnlyDigits( void );
Boolean CallFilterProc( DialogPtr dialog, EventRecord
* eventPtr, short *itemHitPtr );
short CurEditField( DialogPtr dialog );
short SelectionLength( DialogPtr dialog );
void Message( Str255 messageStr );
Here’s some routines that arrived too late to be added to the THINK C 5 header
files. They are part of System 7. The first three should be familiar to you from
previous columns. The fourth will be used to retrieve the address of the default
ModalDialog() filter procedure. You’ll see how this is used later on in the program.
/* 6 */
/* see tech note 304 */
pascal OSErr SetDialogDefaultItem( DialogPtr theDialog,
short newItem )
= { 0x303C, 0x0304, 0xAA68 };
pascal OSErr SetDialogCancelItem( DialogPtr theDialog,
short newItem )
= { 0x303C, 0x0305, 0xAA68 };
pascal OSErr SetDialogTrackCursor( DialogPtr theDialog,
Boolean tracks )
= { 0x303C, 0x0306, 0xAA68 };
pascal OSErr GetStdFilterProc( ModalFilterProcPtr *theProc )
= { 0x303C, 0x0203, 0xAA68 };
main() initializes the Toolbox and menu bar, then enters the main event loop.
Notice the new spelling of ToolboxInit(). (I used to spell it ToolBoxInit() - Aack!)
/* 7 */
/************************************* main */
void main( void )
{
ToolboxInit();
MenuBarInit();
EventLoop();
}
Nothing new here...
/* 8 */
/************************************* ToolboxInit */
void ToolboxInit( void )
{
InitGraf( &thePort );
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs( NULL );
InitCursor();
}
Not too much new here. This time I added a constant for the MBAR resource id.
/* 9 */
/************************************* MenuBarInit */
void MenuBarInit( void )
{
Handle menuBar;
MenuHandle menu;
menuBar = GetNewMBar( kMBARid );
SetMenuBar( menuBar );
menu = GetMHandle( mApple );
AddResMenu( menu, 'DRVR' );
DrawMenuBar();
}
Same old, same old...
/* 10 */
/************************************* EventLoop */
void EventLoop( void )
{
EventRecord event;
gDone = false;
while ( gDone == false )
{
if ( WaitNextEvent( everyEvent, & event, kSleep, NULL ) )
DoEvent( &event );
}
}
Since our program only supports menus and a single dialog, we’ll only handle a
few events. The dialog window events will be handled by the Dialog Manager.
/* 11 */
/************************************* DoEvent */
void DoEvent( EventRecord * eventPtr )
{
char theChar;
switch ( eventPtr->what )
{
case mouseDown:
HandleMouseDown( eventPtr );
break;
case keyDown:
case autoKey:
theChar = eventPtr->message & charCodeMask;
if ( ( eventPtr->modifiers & cmdKey) != 0 )
HandleMenuChoice( MenuKey( theChar ) );
break;
}
}
Last month’s version of HandleMouseDown() included code for dragging a window
around on the screen. Since we don’t have any windows, I took the liberty of deleting
the offending lines. Sorry about the extra typing.
/* 12 */
/************************************* HandleMouseDown */
void HandleMouseDown( EventRecord * eventPtr )
{
WindowPtr window;
short thePart;
long menuChoice;