Modal Filter
Volume Number: | | 9
|
Issue Number: | | 7
|
Column Tag: | | Getting Started
|
Related Info: Dialog Manager Event Manager
The Modal Dialog Filter
Preprocessing events for a modal dialog
By Dave Mark, MacTech Magazine Regular Contributing Author
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
This month has sure been a busy one. Ive been putting the finishing touches on Learn C++ on the Macintosh (looks like a July release), while Daniel (now 9 months old) makes interesting changes to my source code by pounding on my keyboard with his tiny little fists. Sigh. Despite Daniels best efforts, Ive managed to complete this months program, DLOGFilter. DLOGFilter shows you how to write a filter procedure for a modal dialog. As usual, well create and run the project this month, then walk through the source code next month.
Lets get started...
Creating the DLOGFilter Resources
Create a folder named DLOGFilter in your Development folder. Next, jump into ResEdit and create a new file named DLOGFilter.Π.rsrc inside the DLOGFilter folder.
Create an MBAR resource using the specifications in Figure 1. Be sure that the MBARs resource ID is set to 128.
Next, create three MENU resources using the specifications in Figure 2 as a guide. Be sure to include a separator line as the second item in all three MENUs. Remember to type in the appropriate command key equivalents (two for the File menu and four for the Edit menu).
Figure 1. Specifications for the MBAR resource.
Figure 2. Specifications for the three MENU resources.
Next, create a DLOG resource according to the specifications in Figure 3. Be sure that the modal dialog window icon (8th from the left) is selected, that the DITL ID is set to 128 and the Initially visible and Close box check boxes are unchecked, and that the Top, Left, Height, and Width fields are filled in as noted. Remember, if your DLOG editor uses Bottom and Right instead of Height and Width, select Show Height & Width from the DLOG menu.
Figure 3. Specifications for the DLOG resource.
Next, select Auto Position... from the DLOG menu. When the Auto Position... dialog appears, use the pop-up menus to direct System 7 to automatically center our dialog on the main screen.
Figure 4. Proper settings for our DLOGs auto-position dialog.
Next, double-click on the blank dialog box in the middle of the DLOG editing window to create a new DITL resource with an id of 128. The DITL contains six items. Create an OK button (Figure 5) and then a Cancel button (Figure 6).
Figure 5. Specifications for the OK button.
Figure 6. Specifications for the Cancel button.
Next, create a static text label (Figure 7) and its corresponding edit text field (Figure 8). Our dialog filter procedure will limit the number of characters entered in this field to 10.
Figure 7. Specifications for the first static text item.
Figure 8. Specifications for the first edit text field.
Now create a second static text label (Figure 9) and its corresponding edit text field (Figure 10). Our dialog filter procedure will only allow the digits 0 through 9 to be typed in this field. When the OK button is clicked, our main program will check to be sure the number typed was between 1 and 100.
Figure 9. Specifications for the second static text item.
Figure 10. Specifications for the second edit text field.
Figure 11 shows the DITL resource with all six items in place. Notice that the OK button is in the lower right corner with the Cancel button immediately to its left. Well talk about proper DITL item placement in next months column.
Figure 11. A look at the completed DITL resource.
Now create an ALRT resource using the specifications shown in Figure 12. This alert will be used to display various messages of interest to the user. Make sure that the DITL ID is set to 129. Then, change the ALRTs resource ID to 129 (select Get Resource Info from the Resource menu). See Figure 12.
Figure 12. Specifications for the ALRT resource.
Next, double-click on the ALRT box in the middle of the ALRT editing window to create a new DITL resource. The ALRT DITL should have a resource id of 129 and will consist of 2 items. The OK button, used to dismiss the alert, is detailed in Figure 13.
Figure 13. Specifications for the alerts OK button.
The second DITL item is a static text item (Figure 14). Notice that the text provided reads: ^0. The Dialog Manager allows you to provide up to four strings (^0, ^1, ^2, and ^3) which may appear in any item in any DITL in your program. The function ParamText() allows you to substitute values for any of these strings. Well use ParamText() to specify the message displayed by this alert. Youll see how this is done next month.
Figure 14. Specifications for the static text item.
Figure 15 shows the message alerts DITL once were done creating the two DITL items.
Figure 15. The message alerts DITL in final form.
Creating the DLOGFilter Project
Quit ResEdit, being sure to save your changes. Now launch THINK C and create a new project, named DLOGFilter.Π, in the DLOGFilter folder. When the project window appear, add MacTraps to your project. Next, create a new source code window, save it as DLOGFilter.c, and add it to the project. Heres the source code for DLOGFilter.c:
/* 1 */
/********************************/
/* */
/* DLOGFilter Code*/
/* */
/* Copyright 1993, Dave Mark */
/* Do not duplicate, All */
/* rights reserved.*/
/* */
/********************************/
#define kDialogResID 128
#define kMBARid 128
#define kMessageAlertID 129
#define kSleep 60L
#define kMoveToFront (WindowPtr)-1L
#define kNULLFilterProc (ProcPtr)0L
#define kOn 1
#define kOff0
#define kEditItemExists true
#define kEventNotHandledYet false
#define kEventHandledtrue
#define kMaxFieldLength 10
#define kEnterKey3
#define kBackSpaceKey8
#define kTabKey 9
#define kReturnKey 13
#define kEscapeKey 27
#define kLeftArrow 28
#define kRightArrow29
#define kUpArrow 30
#define kDownArrow 31
#define kPeriodKey 46
#define kDeleteKey 127
#define iTenCharMaxText 4
#define iNumberText6
#define mApple 128
#define iAbout 1
#define mFile 129
#define iDialog 1
#define iQuit 3
/*************/
/* Globals */
/*************/
Boolean gDone;
/***************/
/* 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 );
pascal Boolean DLOGFilter( DialogPtr dialog,
EventRecord *eventPtr, short *itemHitPtr );
Boolean ScrapIsOnlyDigits( void );
Boolean CallFilterProc( DialogPtr dialog,
EventRecord *eventPtr, short *itemHitPtr );
short CurEditField( DialogPtr dialog );
short SelectionLength( DialogPtr dialog );
void Message( Str255 messageStr );
/* 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 */
void main( void )
{
ToolboxInit();
MenuBarInit();
EventLoop();
}
/************************************* ToolboxInit */
void ToolboxInit( void )
{
InitGraf( &thePort );
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs( NULL );
InitCursor();
}
/************************************* MenuBarInit */
void MenuBarInit( void )
{
Handle menuBar;
MenuHandle menu;
menuBar = GetNewMBar( kMBARid );
SetMenuBar( menuBar );
menu = GetMHandle( mApple );
AddResMenu( menu, 'DRVR' );
DrawMenuBar();
}
/************************************* EventLoop */
void EventLoop( void )
{
EventRecordevent;
gDone = false;
while ( gDone == false )
{
if ( WaitNextEvent( everyEvent, &event, kSleep, NULL ) )
DoEvent( &event );
}
}
/************************************* 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;
}
}
/************************************* HandleMouseDown */
void HandleMouseDown( EventRecord *eventPtr )
{
WindowPtrwindow;
short thePart;
long menuChoice;
GrafPtroldPort;
long windSize;
Rect growRect;
thePart = FindWindow( eventPtr->where, &window );
switch ( thePart )
{
case inMenuBar:
menuChoice = MenuSelect( eventPtr->where );
HandleMenuChoice( menuChoice );
break;
case inSysWindow :
SystemClick( eventPtr, window );
break;
case inDrag :
DragWindow( window, eventPtr->where,
&screenBits.bounds );
break;
}
}
/************************************* HandleMenuChoice */
void HandleMenuChoice( long menuChoice )
{
short menu;
short item;
if ( menuChoice != 0 )
{
menu = HiWord( menuChoice );
item = LoWord( menuChoice );
switch ( menu )
{
case mApple:
HandleAppleChoice( item );
break;
case mFile:
HandleFileChoice( item );
break;
}
HiliteMenu( 0 );
}
}
/************************************* HandleAppleChoice */
void HandleAppleChoice( short item )
{
MenuHandle appleMenu;
Str255 accName;
short accNumber;
switch ( item )
{
case iAbout:
SysBeep( 20 );
break;
default:
appleMenu = GetMHandle( mApple );
GetItem( appleMenu, item, accName );
accNumber = OpenDeskAcc( accName );
break;
}
}
/************************************* HandleFileChoice */
void HandleFileChoice( short item )
{
short newPICTid;
switch ( item )
{
case iDialog:
DoDialog();
break;
case iQuit:
gDone = true;
break;
}
}
/************************************* DoDialog */
void DoDialog( void )
{
DialogPtrdialog;
BooleandialogDone = false;
short itemHit, iType;
Handle iHandle;
Rect iRect;
Str255 numberStr;
long number;
dialog = GetNewDialog( kDialogResID, NULL, kMoveToFront );
ShowWindow( dialog );
SetPort( dialog );
SetDialogDefaultItem( dialog, ok );
SetDialogCancelItem( dialog, cancel );
SetDialogTrackCursor( dialog, kEditItemExists );
// Be sure to read tech note #304 which covers
// these three routines
while ( ! dialogDone )
{
ModalDialog( DLOGFilter, &itemHit );
switch( itemHit )
{
case ok:
GetDItem( dialog, iNumberText, &iType,
&iHandle, &iRect );
GetIText( iHandle, numberStr);
if ( numberStr[ 0 ] == 0 )
{
Message(
"\pYou must enter a number in the number field!" );
}
else
{
StringToNum( numberStr, &number );
if ( (number >= 1) && (number <= 100) )
dialogDone = true;
else
{
Message(
"\pPlease enter a number between 1 and 100..." );
SelIText( dialog, iNumberText, 0, 32767 );
}
}
break;
case cancel:
dialogDone = true;
break;
}
}
DisposDialog( dialog );
if ( itemHit == ok )
Message( "\pYour number was valid!!!" );
else
Message( "\pDialog cancelled..." );
}
/************************************* FilterProc */
pascal Boolean DLOGFilter( DialogPtr dialog,
EventRecord *eventPtr, short *itemHitPtr )
{
char c;
short iType;
Handle iHandle;
Rect iRect;
Str255 textStr;
long scrapLength, scrapOffset;
short selecLength;
switch ( eventPtr->what )
{
case keyDown:
case autoKey:
c = (eventPtr->message & charCodeMask);
if ( (c == kReturnKey) || (c == kEnterKey) ||
(c == kTabKey) || (c == kBackSpaceKey) ||
(c == kEscapeKey) || (c == kLeftArrow) ||
(c == kRightArrow) || (c == kUpArrow) ||
(c == kDownArrow) || (c == kDeleteKey) )
{
return( CallFilterProc( dialog, eventPtr,
itemHitPtr ) );
}
else if ( CurEditField( dialog ) == iTenCharMaxText )
{
GetDItem( dialog, iTenCharMaxText, &iType,
&iHandle, &iRect );
GetIText( iHandle, textStr);
selecLength = SelectionLength( dialog );
if ( ( (eventPtr->modifiers & cmdKey) != 0) &&
(c == 'v') )
{
scrapLength = GetScrap( NULL, 'TEXT',
&scrapOffset );
if ( textStr[ 0 ] + scrapLength -
selecLength > kMaxFieldLength )
{
SysBeep( 20 );
*itemHitPtr = iTenCharMaxText;
return( kEventHandled );
}
else
return( CallFilterProc( dialog, eventPtr,
itemHitPtr ) );
}
if ( (textStr[ 0 ] == kMaxFieldLength) &&
(selecLength == 0) )
{
SysBeep( 20 );
return( kEventHandled );
}
else
return( CallFilterProc( dialog, eventPtr,
itemHitPtr ) );
}
else if ( CurEditField( dialog ) == iNumberText )
{
GetDItem( dialog, iNumberText, &iType,
&iHandle, &iRect );
selecLength = SelectionLength( dialog );
if ( ( (eventPtr->modifiers & cmdKey) != 0) &&
(c == 'v') )
{
if ( ScrapIsOnlyDigits() )
{
return( CallFilterProc( dialog, eventPtr,
itemHitPtr ) );
}
else
{
SysBeep( 20 );
*itemHitPtr = iNumberText;
return( kEventHandled );
}
}
else if ( ((c < '0') || (c > '9')) &&
( (eventPtr->modifiers & cmdKey) == 0) )
{
SysBeep( 20 );
*itemHitPtr = iNumberText;
return( kEventHandled );
}
else
{
return( CallFilterProc( dialog, eventPtr,
itemHitPtr ) );
}
}
break;
}
return( CallFilterProc( dialog, eventPtr, itemHitPtr ) );
}
/************************************* ScrapIsOnlyDigits */
Boolean ScrapIsOnlyDigits( void )
{
Handle textHandle;
long scrapLength, scrapOffset;
BooleanonlyDigits = true;
unsigned short i;
textHandle = NewHandle( 0 );
scrapLength = GetScrap( textHandle, 'TEXT', &scrapOffset );
if ( scrapLength <= 0 )
return( false );
HLock( textHandle );
for ( i=0; i<scrapLength; i++ )
{
if ( ((*textHandle)[ i ] < '0') ||
((*textHandle)[ i ] > '9') )
onlyDigits = false;
}
HUnlock( textHandle );
DisposHandle( textHandle );
return( onlyDigits );
}
/************************************* CallFilterProc */
Boolean CallFilterProc( DialogPtr dialog,
EventRecord *eventPtr, short *itemHitPtr )
{
ModalFilterProcPtrtheModalProc;
OSErr myErr;
myErr = GetStdFilterProc(&theModalProc);
if (myErr == noErr)
return( theModalProc( dialog, eventPtr, itemHitPtr ) );
else
return( kEventNotHandledYet );
}
/************************************* CurEditField */
short CurEditField( DialogPtr dialog )
{
return( ((DialogPeek)dialog)->editField + 1 );
}
/************************************* SelectionLength */
short SelectionLength( DialogPtr dialog )
{
TEHandle teH;
teH = ((DialogPeek)dialog)->textH;
return( (**teH).selEnd - (**teH).selStart );
}
/************************************* Message */
void Message( Str255 messageStr )
{
short unused;
ParamText( messageStr, "\p", "\p", "\p" );
unused = NoteAlert( kMessageAlertID, kNULLFilterProc );
}
Running DLOGFilter
Save your source code and then select Run from the Project menu. When DLOGFilter starts running, a menu bar with three menus (, File, and Edit) will appear. If you select About DLOGFilter... from the menu, the program will beep at you. Since you already know how to implement an about alert, I didnt want to take up space in this column with a real about box.
Next, click your mouse in the File menu. The first item, Dialog..., has a command key equivalent of D. Type D or select Dialog... from the File menu. Either way, a dialog box will appear, just like the one in Figure 16. Notice that the text edit cursor is in the first of the two text edit fields.
Figure 16. The DLOGFilter dialog.
To start things off, press the Cancel button. The alert shown in Figure 17 will appear. Click the OK button and type D to bring up the dialog again and this time, type -. (command-period). Once again, the Dialog cancelled... alert appears. Once more, click the OK button and type D to bring up the dialog and this time hit the escape key. As before, the Dialog cancelled... alert appears. Click the OK button to dismiss the alert.
Figure 17. This alert appears when you Cancel the
DLOGFilter dialog.
Type D to bring up the dialog again. This time, type the characters 1234567890 in the first field. Now type another character. The dialog will beep at you and your character will not appear. As you can see, this field limits you to 10 characters. Try copying some text to the clipboard, then pasting it to the field. You will only succeed if the number of characters in the clipboard plus the number of characters in the field that are not selected does not exceed 10.
Without typing anything in the second field, click the OK button. The alert shown in Figure 18 appears, telling you to enter a number in the second field.
Figure 18. Another message alert.
Click the mouse in the second field and type a non-numeric character (like the letter x). Your Mac will beep at you, telling you that you can only type numeric characters in this field. Now type the number 355 in the second field and click OK. The message alert shown in Figure 19 appears, telling you to enter a number between 1 and 100.
Figure 19. Yet another message alert.
Click OK to get back to the dialog and type a number between 1 and 100 in the second field and click OK. The message alert in Figure 20 tells you that your input was valid.
Figure 20. A final message alert.
Till next month...
At the heart of this program was a filter procedure that the Dialog Manager called repeatedly to preprocess all the events associated with the main dialog box. The filter procedure was responsible for limiting the characters that appeared in the two text edit fields. While you are waiting for next months column, read through the source code and try to figure out exactly what the filter proc is doing. In the meantime, Im going back to work on my book. See you next month...