• Updated 2023-07-12: Hello, Guest! Welcome back, and be sure to check out this follow-up post about our outage a week or so ago.

Fixing The Macintosh Pascal Programming Primer Dialog Example

Snial

Well-known member
I've written a full blog post about this, here, but here's a summary: I've been a big fan of the wonderfully simple Macintosh Pascal Programming Primer since I bought it in 1993. I've never written an awful lot of classic Macintosh apps: almost my entire career has been embedded computing in very PC-oriented (& Mac-hostile) companies. However, at the moment I'm interested in creating a simple Simulation framework where each app has a dialog box for entering parameters; a main window for showing the simulation and a few menu options for editing the parameters or running the simulation.

I thought a good way of doing this would be to start with the THINK C Object Bullseye demo and add the MPPP dialog code, but it crashed. I thought it was a dumb programming error by me, because I don't really have so much classic Mac OS programming experience, but it turned out that's because they weren't handling dialogs properly: the dialog box was created at the beginning and sat behind any subsequently opened window, and when MPPP's dialog code did ShowWindow(dialogptr); .. ModalDialog(dialogptr, ...); it would crash. The conventional way is to GetNewDialog(..) to create and load the dialog when you need it and then DisposeDialog(dialogptr) at the end. Also, it models the data incorrectly, by storing the dialog parameters in the dialog box itself and using an internal data structure only to save the existing dialog parameters so they can be restored if you cancelled the dialog box instead of clicking [Save]. It should be the other way around: the internal data structure is definitive; the values get copied to the dialog box parameters after the dialog is loaded and get copied back if you [Save] the new settings (not if you click [Cancel]).

So, here's my version of the Dialog demo. It's newbie stuff to most of you, but figuring it out has helped me.

Dlog.h

Code:
/**
 * @file: Dlog.h
 */

#ifndef Dlog_h
#define Dlog_h

#define kBaseResId 400
#define kAboutAlert 401
#define kBadSysAlert 402

#define kSleep 60

#define kSaveButton 1
#define kCancelButton 2
#define kTimeField 4
#define kSOrMField 5
#define kSoundOnBox 6
#define kIconOnBox 7
#define kAlertOnBox 8
#define kSecsRadio 10
#define kMinsRadio 11

#define kDefaultSecsId 401
#define kDefaultMinsId 402

#define kOff 0
#define kOn 1

#define kSecondsPerMinute 60

#define kTop 25
#define kLeft 12

#define kMarkApplication 1
#define kAppleMenuId (kBaseResId)
#define kFileMenuId (kBaseResId+1)
#define kAboutItem 1

#define kChangeItem 1
#define kStartStopItem 2
#define kQuitItem 3

#define kSysVersion 2

typedef enum{
    kBoolFalse=0,
    kBoolTrue=1
}tBool;

typedef enum {
    kTimeUnitSeconds=0,
    kTimeUnitMinutes=1
}tTimeUnit;

typedef struct {
    long iTime;
    int iSound, iIcon, iAlert;
    tTimeUnit iUnit;
}tSettings;



extern Handle DlogItemGet(DialogPtr aDialog, int aItem);
extern void CtlSet(DialogPtr aDialog, int aItem, int aValue);
extern int CtlGet(DialogPtr aDialog, int aItem);
extern void CtlFlip(DialogPtr aDialog, int aItem);
extern void ITextSet(DialogPtr aDialog, int aItem, Str255 *aStr);

extern void StartCountDown(long aNumSecs);
extern void HandleCountDown(void);
extern void UpdateCountDown(void);

extern void RestoreSettings(DialogPtr aSettingsDialog);
extern void SaveSettings(DialogPtr aSettingsDialog);
extern void HandleDialog(void);
extern void HandleFileChoice(int aTheItem);
extern void HandleAppleChoice(int aTheItem);
extern void HandleMenuChoice(long aMenuChoice);
extern void HandleMouseDown(void);
extern void HandleEvent(void);
extern void MainLoop(void);
extern void MenuBarInit(void);
extern void DialogInit(void);
extern void WinInit(void);
extern tBool Sys6OrLater(void);
extern void ToolboxInit(void);
extern int main(void);

#endif // Dlog_h

Dlog.c

Code:
/**
 * Dlog.c
 *
 * Literal code was 2462b long.
 * GetDItem, SetCtlValue, GetCtlValue Factored: 2104
 * CtlFlip: 2024. Finally, with proper Dialog handling, 1722.
 */

#include "Dlog.h"

tBool gDone;

StringHandle gDefaultSecsH, gDefaultMinsH;
EventRecord gTheEvent;
tSettings gSavedSettings;


WindowPtr gCountDownWindow;
long gTimeout, gOldTime;
tBool gIsCounting;

Handle DlogItemGet(DialogPtr aDialog, int aItem)
{
    int itemType;
    Rect itemRect;
    Handle itemHandle;
    GetDItem(aDialog, aItem, &itemType, &itemHandle, &itemRect);
    return itemHandle;
}

void CtlSet(DialogPtr aDialog, int aItem, int aValue)
{
    Handle itemHandle=DlogItemGet(aDialog, aItem);
    SetCtlValue((ControlHandle)itemHandle, aValue);
}

int CtlGet(DialogPtr aDialog, int aItem)
{
    Handle itemHandle=DlogItemGet(aDialog, aItem);
    return GetCtlValue((ControlHandle)itemHandle);
}

/*
void ITextSet(DialogPtr aDialog, int aItem, Str255 *aStr)
{
    Handle itemHandle=DlogItemGet(aDialog, aItem);
    SetIText(itemHandle, aStr);
}
*/
void CtlFlip(DialogPtr aDialog, int aItem)
{
    Handle itemHandle=DlogItemGet(aDialog, aItem);
    SetCtlValue((ControlHandle)itemHandle,
        (GetCtlValue((ControlHandle)itemHandle)==kOn)? kOff:kOn);
}

void StartCountDown(long aNumSecs)
{
    GetDateTime(&gOldTime);
    if(gSavedSettings.iUnit==kTimeUnitMinutes) {
        aNumSecs*=kSecondsPerMinute;
    }
    gTimeout=gOldTime+aNumSecs; // this is the timeout.
    gIsCounting=kBoolTrue;
}

// Called on Null event.
void HandleCountDown(void)
{
    if(gIsCounting==kBoolTrue) {
        long myTime;
        GetDateTime(&myTime);
        if(myTime!=gOldTime) {
            GrafPtr oldPort;
            gOldTime=myTime; // gTimeout-gOldTime==remaining seconds.
            // gen update, but how?
            GetPort(&oldPort);
            SetPort((GrafPtr)gCountDownWindow);
            InvalRect(&gCountDownWindow->portRect);
            SetPort(oldPort);
        }
    }    
}

void UpdateCountDown(void)
{
    // 
    WindowPtr win=(WindowPtr)gTheEvent.message;
    if(win==gCountDownWindow) {
        long remaining=gTimeout-gOldTime;
        Str255 myTimeString;
        BeginUpdate(win);
        MoveTo(kLeft, kTop);
        if(remaining<=0 || gIsCounting==kBoolFalse) {
            remaining=0;
            gIsCounting=kBoolFalse;
        }
        NumToString(remaining, myTimeString);
        EraseRect(&(gCountDownWindow->portRect));
        DrawString(myTimeString);
        EndUpdate(win);
    }
}

void RestoreSettings(DialogPtr aSettingsDialog)
{
    Handle itemHandle;
    Str255 timeString;
    tBool isInSeconds=(gSavedSettings.iUnit==kTimeUnitSeconds)?
            kBoolTrue:kBoolFalse;
    
    itemHandle=DlogItemGet(aSettingsDialog, kTimeField);
    NumToString(gSavedSettings.iTime, &timeString);
    SetIText(itemHandle, timeString);
    
    CtlSet(aSettingsDialog, kSoundOnBox, gSavedSettings.iSound);
    CtlSet(aSettingsDialog, kIconOnBox, gSavedSettings.iIcon);
    CtlSet(aSettingsDialog, kAlertOnBox, gSavedSettings.iAlert);
    CtlSet(aSettingsDialog, kSecsRadio, (isInSeconds==kBoolTrue)?kOn:kOff);
    CtlSet(aSettingsDialog, kMinsRadio, (isInSeconds==kBoolFalse)?kOn:kOff);

    itemHandle=DlogItemGet(aSettingsDialog, kSOrMField);
    SetIText(itemHandle,(gSavedSettings.iUnit==kTimeUnitSeconds)?
            "\pseconds":"\pminutes");
}

void SaveSettings(DialogPtr aSettingsDialog)
{
    Handle itemHandle;
    Str255 timeString;

    itemHandle=DlogItemGet(aSettingsDialog, kTimeField);
    GetIText(itemHandle, &timeString);
    StringToNum(timeString, &gSavedSettings.iTime);
    
    gSavedSettings.iSound=CtlGet(aSettingsDialog, kSoundOnBox);
    gSavedSettings.iIcon=CtlGet(aSettingsDialog, kIconOnBox);
    gSavedSettings.iAlert=CtlGet(aSettingsDialog, kAlertOnBox);
    gSavedSettings.iUnit=(CtlGet(aSettingsDialog, kSecsRadio)==kOn)?
                kTimeUnitSeconds:kTimeUnitMinutes;
}

void HandleDialog(void)
{
    tBool dialogDone;
    int itemHit;
    long alarmDelay;
    Handle itemHandle;
    DialogPtr settingsDialog;
    
    settingsDialog=GetNewDialog(kBaseResId, NULL, (WindowPtr)-1);

    ShowWindow(settingsDialog);
    RestoreSettings(settingsDialog);
    
    dialogDone=kBoolFalse;
    while(dialogDone==kBoolFalse) {
        ModalDialog(NULL, &itemHit);
        switch(itemHit) {
        case kSaveButton:
            SaveSettings(settingsDialog); // update them.
            dialogDone=kBoolTrue;
            break;
        case kCancelButton:
            dialogDone=kBoolTrue;
            break;
        case kSoundOnBox:
        case kIconOnBox:
        case kAlertOnBox:
            CtlFlip(settingsDialog, itemHit);
            break;
        case kSecsRadio:
            CtlSet(settingsDialog, kSecsRadio, kOn);
            CtlSet(settingsDialog, kMinsRadio, kOff);

            itemHandle=DlogItemGet(settingsDialog, kSOrMField);
            SetIText(itemHandle, "\pseconds");
            itemHandle=DlogItemGet(settingsDialog, kTimeField);
            SetIText(itemHandle, *gDefaultSecsH);
            
            break;
        case kMinsRadio:
            CtlSet(settingsDialog, kSecsRadio, kOff);            
            CtlSet(settingsDialog, kMinsRadio, kOn);

            itemHandle=DlogItemGet(settingsDialog, kSOrMField);
            SetIText(itemHandle, "\pminutes");
            itemHandle=DlogItemGet(settingsDialog, kTimeField);
            SetIText(itemHandle, *gDefaultMinsH);
            break;
        }
    }
    DisposeDialog(settingsDialog);
}

void HandleFileChoice(int aTheItem)
{
    switch(aTheItem) {
    case kChangeItem:
        HandleDialog();
        break;
    case kStartStopItem:
        HiliteMenu(0);
        StartCountDown(gSavedSettings.iTime);
        break;
    case kQuitItem:
        gDone=true;
        break;
    }
}

void HandleAppleChoice(int aTheItem)
{
    Str255 accName;
    int accNumber, itemNumber, dummy;
    MenuHandle appleMenu;
    switch(aTheItem) {
    case kAboutItem:
        NoteAlert(kAboutAlert, NULL);
        break;
    default:
        appleMenu=GetMHandle(kAppleMenuId);
        GetItem(appleMenu, aTheItem, &accName);
        OpenDeskAcc(accName);
        break;
    }
}

void HandleMenuChoice(long aMenuChoice)
{
    int theMenu, theItem;
    if(aMenuChoice!=0) {
        theMenu=HiWord(aMenuChoice);
        theItem=LoWord(aMenuChoice);
        switch(theMenu) {
        case kAppleMenuId:
            HandleAppleChoice(theItem);
            break;
        case kFileMenuId:
            HandleFileChoice(theItem);
            break;
        }
        HiliteMenu(0);
    }
}

void HandleMouseDown(void)
{
    WindowPtr whichWindow;
    int thePart;
    long menuChoice, windSize;
    thePart=FindWindow(gTheEvent.where, &whichWindow);
    switch(thePart) {
    case inMenuBar:
        menuChoice=MenuSelect(gTheEvent.where);
        HandleMenuChoice(menuChoice);
        break;
    case inSysWindow:
        SystemClick(&gTheEvent, whichWindow);
        break;
    case inDrag:
        DragWindow(whichWindow, gTheEvent.where, &screenBits.bounds);
        break;
    case inGoAway:
        gDone=kBoolTrue;
        break;
    }
}

void HandleEvent(void)
{
    char theChar;
    tBool dummy;
    WaitNextEvent(everyEvent, &gTheEvent, kSleep, NULL);
    switch(gTheEvent.what){
    case mouseDown:
        HandleMouseDown();
        break;
    case keyDown: case autoKey:
        theChar=(char)(gTheEvent.message & charCodeMask);
        if((gTheEvent.modifiers & cmdKey)!=0) {
            HandleMenuChoice(MenuKey(theChar));
        }
        break;
    case nullEvent:
        HandleCountDown();
        break;
    case updateEvt:
        UpdateCountDown();
        break;
    }
}

void MainLoop(void)
{
    gDone=kBoolFalse;
    while(gDone==kBoolFalse) {
        HandleEvent();
    }
}

void MenuBarInit(void)
{
    Handle myMenuBar;
    MenuHandle aMenu;
    myMenuBar=GetNewMBar(kBaseResId);
    SetMenuBar(myMenuBar);
    DisposHandle(myMenuBar);
    aMenu=GetMHandle(kAppleMenuId);
    AddResMenu(aMenu, 'DRVR');
    DrawMenuBar();
}

void WinInit(void)
{
    gCountDownWindow=GetNewWindow(kBaseResId, NULL, (WindowPtr)-1);
    gIsCounting=kBoolFalse;
    SetPort(gCountDownWindow);
    TextFace(bold); // it's the same in THINK C.
    TextSize(24);
    ShowWindow(gCountDownWindow);
}

void DialogInit(void)
{
    gDefaultSecsH=GetString(kDefaultSecsId);
    gDefaultMinsH=GetString(kDefaultMinsId);
    
    gSavedSettings.iTime=12;
    
    gSavedSettings.iSound=kOn;
    gSavedSettings.iIcon=kOn;
    gSavedSettings.iAlert=kOn;
    
    gSavedSettings.iUnit=kTimeUnitSeconds;
}

tBool Sys6OrLater(void)
{
    OSErr status;
    SysEnvRec SysEnvData;
    int dummy;
    tBool result=kBoolTrue;
    status=SysEnvirons(kSysVersion, &SysEnvData);
    if(status!=noErr || SysEnvData.systemVersion<0x600) {
        StopAlert(kBadSysAlert, NULL);
        result=kBoolFalse;
    }
    return result;
}

void ToolboxInit(void)
{
    InitGraf(&thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(NULL);
    MaxApplZone();
}

int main(void)
{
    ToolboxInit();
    if(Sys6OrLater()) {
        DialogInit();
        MenuBarInit();
        WinInit();
        InitCursor();
        MainLoop();
    }
    return 0;
}

The complete project, including resources is attached.
 

Attachments

  • Dlog.cpt.hqx
    19.9 KB · Views: 1
Top