GetZoneList.c

/* ------------------------------------------------------------------------------
#
#   Apple Macintosh Developer Technical Support
#
#   AppleTalk GetZoneList Sample Application
#
#   GetZoneList
#
#   GetZoneList.c   -   C Source
#
#   Copyright © 1988-90 Apple Computer, Inc.
#   All rights reserved.
#
#   Versions:   1.00                November 1988
#               1.01                October 1989
#               1.02                May 1990
#               1.03                June 1992
#               1.04                July 1992
#
#   Components: GetZoneList.c       May 1, 1990
#               GetZoneList.p       May 1, 1990
#               GetZoneList.r       May 1, 1990
#               MakeFile            May 1, 1990
#               UFailure.a          November 1, 1988
#               UFailure.h          November 1, 1988
#               UFailure.inc1.p     November 1, 1988
#               UFailure.p          November 1, 1988
#
#   GetZoneList is a sample application that uses
#   AppleTalk ATP and ZIP to obtain a list of zones
#   on an AppleTalk internet.
#
#   GetZoneList also demonstrates using a signal, or
#   failure-catching mechanism to recover from error
#   situations.  Since C does not allow nested procedures
#   a la Pascal, a few modifications were made to incorporate
#   the failure handling and keep this sample fairly close in
#   design to the Pascal sample.
#   (Gee, thanks a lot M2 for using nested procs. - pvh)
#
#   GetZoneList is based on DTS Sample.c. For more
#   description and explanation of the non-example
#   specific areas of this application, please refer to
#   either Sample.p or TESample.c.
#
------------------------------------------------------------------------------ */
 
#include <Limits.h>
#include <Types.h>
#include <QuickDraw.h>
#include <Fonts.h>
#include <Events.h>
#include <Controls.h>
#include <Windows.h>
#include <Menus.h>
#include <TextEdit.h>
#include <Dialogs.h>
#include <Packages.h>
#include <Menus.h>
#include <Devices.h>
#include <Events.h>
#include <Scrap.h>
#include <Lists.h>
#include <ToolUtils.h>
#include <Memory.h>
#include <SegLoad.h>
#include <Errors.h>
#include <Files.h>
#include <OSUtils.h>
#include <AppleTalk.h>
#include <Traps.h>
#include <DiskInit.h>
#include <Script.h>
#include <UFailure.h>
 
 
typedef XCallParam *XCallParamPtr;
 
#define     kSysEnvironsVersion     1
#define     kOSEvent                app4Evt     /* event used by Multifinder */
#define     kSuspendResumeMessage   1           /* high byte of suspend/resume event message */
#define     kResumeMask             1           /* bit of message field for resume vs. suspend */
 
#define     kCR                     13          /* carriage return character */
#define     kENTER                  3           /* enter character */
#define     kScrollBarWidth         15          /* the width of the scrollbar in the list */
#define     kListInset              -1          /* adjustment for list frame */
#define     kATPTimeOutVal          3           /* re-try ATP SendRequest every 3 seconds */
#define     kATPRetryCount          5           /* for five times */
#define     kZonesSize              578         /* size of buffer for zone names */
#define     kGZLCall                0x08000000  /* GetZoneList indicator */
#define     kZIPSocket              6           /* the Zone Information Protocol socket */
#define     kMoreZones              0xFF000000  /* mask to see if more zones to come */
#define     kZoneCount              0x0000FFFF  /* mask to count zones in buffer */
#define     kHilite                 1           /* hilite value for button control */
#define     kDeHilite               0           /* dehilite value for button control */
#define     kHiliteDelay            8           /* time in ticks to leave button hilited */
 
#define     kMinHeap                (29 * 1024)
#define     kMinSpace               (20 * 1024)
 
#define     sErrStrings             128         /* error string STR# */
#define     eStandardErr            1
#define     eWrongMachine           2
#define     eSmallSize              3
#define     eNoMemory               4
#define     eAppleTalk              5
#define     eNoZones                6
 
#define     rAboutAlert             128         /* about alert */
#define     rZoneDialog             129         /* zone list dialog */
#define     dZoneList               2           /* user item that is zone list */
#define     dDefault                3           /* user item that is default indicator */
#define     rUserAlert              130         /* error alert */
 
#define     rMenuBar                128         /* application's menu bar */
 
#define     mApple                  128         /* Apple menu */
#define     iAbout                  1
 
#define     mFile                   129         /* File menu */
#define     iNew                    1
#define     iClose                  4
#define     iQuit                   12
 
#define     mEdit                   130         /* Edit menu */
#define     iUndo                   1
#define     iCut                    3
#define     iCopy                   4
#define     iPaste                  5
#define     iClear                  6
 
/* kDITop and kDILeft are used to locate the Disk Initialization dialogs. */
#define     kDITop                  0x0050
#define     kDILeft                 0x0070
 
 
/* Globs */
SysEnvRec           gMac;                   /* set up by Initialize */
Boolean             gHasWaitNextEvent;      /* set up by Initialize */
Boolean             gInBackground;          /* maintained by Initialize and DoEvent */
XCallParamPtr       gXPBPBPtr;              /* structure for Phase 2 NBP lookups       */
                                            /* needs to be global for failure handling */
 
 
ListHandle  gList;                  /* the list to be filled with zone names */
 
extern void _DataInit();
 
/*  globals added for C sample use as the Pascal
    example used those horrid :-) nested procedures! */
ATPPBPtr    gATPPBPtr;  /* the parameter block for GetZoneList call */
Ptr         gZones;     /* the data buffer for GetZoneList call */
DialogPtr   gErrDlg;    /* Dialog used for displaying zone list */
 
 
/*  Check to see if a given trap is implemented. This is only used by the
    Initialize routine in this program, so we put it in the Initialize segment.
    The recommended approach to see if a trap is implemented is to see if
    the address of the trap routine is the same as the address of the
    Unimplemented trap. */
/*  1.02 - Needs to be called after call to SysEnvirons so that it can check
    if a ToolTrap is out of range of a pre-MacII ROM. */
 
#pragma segment Initialize
Boolean TrapAvailable(tNumber,tType)
    short       tNumber;
    TrapType    tType;
{
    if ( ( tType == (unsigned char) ToolTrap ) &&
        ( gMac.machineType > envMachUnknown ) &&
        ( gMac.machineType < envMacII ) ) {     /* it's a 512KE, Plus, or SE */
        tNumber = tNumber & 0x03FF;
        if ( tNumber > 0x01FF )                 /* which means the tool traps */
            tNumber = _Unimplemented;           /* only go to 0x01FF */
    }
    return NGetTrapAddress(tNumber, tType) != NGetTrapAddress(_Unimplemented, ToolTrap);
} /*TrapAvailable*/
 
 
#pragma segment Main
void FailOSErrMsg(result, message)
    short   result;
    short   message;
{
    if (result != noErr)
        Failure(result, message);
} /* SignalOSErrMsg */
 
 
#pragma segment Main
void FailnilMsg(p, message)
    Ptr     p;
    short   message;
{
    if (p == nil)
        Failure(memFullErr, message);
} /* FailNILMsg */
 
 
#pragma segment Main
void AlertUser(error, message)
 
/* Display an alert to inform the user of an error. Message acts as an
 index into a STR# resource of error messages. if no message is given,
 i.e. = 0, then use a standard message. if error is not noErr then
 display it as well. */
 
    short   error;
    long    message;
{
    Str255  msg1, msg2;
    short   itemHit;
 
    if (message <= 0L)  message = eStandardErr;
    GetIndString(msg1, sErrStrings, message);
    if (error == noErr)
        msg2[0] = '';
    else
        NumToString(error, msg2);
    ParamText(msg1, msg2, "\p", "\p");
    itemHit = Alert(rUserAlert, nil);
} /* AlertUser */
 
 
#pragma segment Main
Boolean IsDAWindow(window)
    WindowPtr   window;
{
    if (window == nil)
        return (false);
    else    /* DA windows have negative windowKinds */
        return ((WindowPeek) window)->windowKind < 0;
} /* IsDAWindow */
 
 
#pragma segment Main
Boolean IsAppWindow(window)
    WindowPtr   window;
{
    short       windowKind;
 
    if ( window == nil )
        return false;
    else {  /* application windows have windowKinds >= userKind (8) or dialogKind (2) */
        windowKind = ((WindowPeek) window)->windowKind;
        return (windowKind >= userKind) || (windowKind == dialogKind);
    }
} /* IsAppWindow */
 
 
#pragma segment Main
void ZoneListCleanUp()
{
    if (gATPPBPtr != nil)
        DisposePtr((Ptr)gATPPBPtr);         /* get rid of pb block */
    if (gZones != nil)
        DisposePtr(gZones);                 /* and buffer */
} /* ZoneListCleanUp */
 
 
#pragma segment Main
pascal void HandleZoneListErr(short error, long message)
{
    #pragma unused (error, message)
 
    ZoneListCleanUp();                      /* get rid of allocated junk */
} /* HandleZoneListErr */
 
 
#pragma segment Main
void BuildZoneList()
 
/*
 *  Create the list of zones on the network. Find a bridge to talk to , if one is
 *  present, then ask it for zone names. Add the names to the list in the dialog.
 */
 
{
    BDSElement  dBDS;               /* the BDS for GetZoneList call */
    Ptr         dCurr;              /* the data buffer for GetZoneList call */
    short       dIndex, dCount;
    short       ignore;
    Cell        cSize;
    FailInfo    fi;
    short       nodeNetAddress, bridgeNode;
 
    gATPPBPtr = nil;                                            /* init some important variables*/
    gZones = nil;
 
    CatchCFailures(&fi, HandleZoneListErr);
 
    gATPPBPtr = (ATPPBPtr)NewPtr(sizeof(ATPParamBlock));
    FailnilMsg(gATPPBPtr, eNoMemory);
    gZones = NewPtr(kZonesSize);
    FailnilMsg(gZones, eNoMemory);
    dBDS.buffSize = kZonesSize;                                 /* set up BDS */
    dBDS.buffPtr = gZones;
 
    gATPPBPtr->ATPatpFlags = 0;
 
    /*
     *  get network address of node & node ID of bridge (if any)
     */
    FailOSErrMsg(GetNodeAddress(&ignore, &nodeNetAddress), eAppleTalk);
    bridgeNode = GetBridgeAddress();
 
    /*
     * test to see if bridge node fails.  If so, no internet.
     */
    if (bridgeNode == 0)
        Failure(0, eNoZones);                                   /* bail if no zones present */
 
    gATPPBPtr->ATPaddrBlock.aNet = nodeNetAddress;
    gATPPBPtr->ATPaddrBlock.aNode = bridgeNode;                 /* get node of bridge */
    gATPPBPtr->ATPaddrBlock.aSocket = kZIPSocket;               /* the socket we want */
    gATPPBPtr->ATPreqLength = 0;
    gATPPBPtr->ATPreqPointer = nil;
    gATPPBPtr->ATPbdsPointer = (Ptr) &dBDS;
    gATPPBPtr->ATPnumOfBuffs = 1;
    gATPPBPtr->ATPtimeOutVal = kATPTimeOutVal;
    gATPPBPtr->ATPretryCount = kATPRetryCount;
 
    dIndex = 1;
    dCount = 0;
    SetPt(&cSize, 0, 0);                                                /* we always stuff into first */
 
    do {
        gATPPBPtr->ATPuserData = kGZLCall + dIndex;                     /* indicate GetZoneList request */
        FailOSErrMsg(PSendRequest(gATPPBPtr, false), eAppleTalk);       /* send sync request */
 
        dCount = dCount + dBDS.userBytes & kZoneCount;                  /* find out how many returned */
        dCurr = gZones;                                                 /* put current pointer at start */
        do {                                                            /* get each zone */
            ignore = LAddRow(1, 0, gList);                              /* create new cell at start */
            LSetCell((Ptr)dCurr + 1L, (short) *dCurr, cSize, gList);    /* stuff in zone */
            dCurr = (Ptr)(dCurr + *dCurr + 1 );                         /* bump up current pointer */
            dIndex = dIndex + 1;                                        /* increment which zone */
        } while(! (dIndex > dCount));
 
    } while ((dBDS.userBytes & kMoreZones) == 0);               /*   keep going until none left */
 
    ZoneListCleanUp();
 
    Success(&fi);
} /* BuildZoneList */
 
 
#pragma segment Main
void ZoneListCleanUpPhase2()
{
    if (gXPBPBPtr != nil)
        DisposePtr((Ptr)gXPBPBPtr);         /* get rid of pb block */
    if (gZones != nil)
        DisposePtr(gZones);                 /* and buffer */
} /* ZoneListCleanUpPhase2 */
 
#pragma segment Main
void BuildZoneListPhase2()
 
/*  Create the list of zones on the network. Find a bridge to talk to , if one is
    present, then ask it for zone names. Add the names to the list in the dialog.   */
 
{
    Ptr         dCurr;              /* the data buffer for GetZoneList call */
    short       dIndex, dCount;
    short       ignore;
    Cell        cSize;
    FailInfo    fi;
 
    short       xppDriverRefNum;
 
    gXPBPBPtr = nil;                                            /* init some important variables*/
    gZones = nil;
 
    CatchCFailures(&fi, HandleZoneListErr);
 
    /* Get network address of bridge.  If zero, no internet. */
    if (GetBridgeAddress() == 0)
        Failure(0, eNoZones);                                   /* bail if no zones present */
 
    /* get a hold of the XPP driver reference number-this is the safest way */
    FailOSErrMsg(OpenDriver("\p.XPP", &xppDriverRefNum), eAppleTalk);
 
    gXPBPBPtr = (XCallParamPtr)NewPtr(sizeof(XCallParam));
    FailnilMsg(gXPBPBPtr, eNoMemory);
    gZones = NewPtr(kZonesSize);
    FailnilMsg(gZones, eNoMemory);
 
    gXPBPBPtr->zipInfoField[0] = 0; /* ALWAYS 0 on first call.  has state info on subsequent calls */
    gXPBPBPtr->zipInfoField[1] = 0; /* ALWAYS 0 on first call.  has state info on subsequent calls */
    gXPBPBPtr->zipLastFlag = 0;
 
    gXPBPBPtr->ioRefNum = xppDriverRefNum;
    gXPBPBPtr->csCode = xCall;
    gXPBPBPtr->xppSubCode = zipGetZoneList;
    gXPBPBPtr->xppTimeout = kATPTimeOutVal;
    gXPBPBPtr->xppRetry = kATPRetryCount;
    gXPBPBPtr->zipBuffPtr = (Ptr) gZones;
 
    dIndex = 1;
    dCount = 0;
    SetPt(&cSize, 0, 0);                                                /* we always stuff into first */
 
    do {
        FailOSErrMsg(PBControl((ParmBlkPtr) gXPBPBPtr, false), eAppleTalk);     /* send sync control call */
 
        dCount = dCount + gXPBPBPtr->zipNumZones;                       /* find out how many returned */
        dCurr = gZones;                                                 /* put current pointer at start */
        do {                                                            /* get each zone */
            ignore = LAddRow(1, 0, gList);                              /* create new cell at start */
            LSetCell((Ptr)dCurr + 1L, (short) *dCurr, cSize, gList);    /* stuff in zone */
            dCurr = (Ptr)(dCurr + *dCurr + 1 );                         /* bump up current pointer */
            dIndex = dIndex + 1;                                        /* increment which zone */
        } while(! (dIndex > dCount));
 
    } while (gXPBPBPtr->zipLastFlag == 0);              /*   keep going until none left */
 
    ZoneListCleanUpPhase2();
 
    Success(&fi);
} /* BuildZoneList */
 
#pragma segment Main
pascal void ZoneListDraw(dlg, item)
    DialogPtr   dlg;
    short       item;
{
 
/* The user item void for the zone list user item and default
 box user item in the dialog. Draw the list and the frame that goes with it.
 Draw the default box around the OK button */
 
    GrafPtr     port;
    short       kind;
    Handle      h;
    Rect        r;
    PenState    ps;
 
    GetPort(&port);                                     /* save old port */
    SetPort(dlg);                                       /* make dialog port */
    switch (item) {
        case dZoneList:
            LUpdate(dlg->visRgn, gList);                /* re-draw list */
            GetDialogItem(dlg, dZoneList, &kind, &h, &r);
            InsetRect(&r, kListInset, kListInset);
            FrameRect(&r);                              /* re-draw frame */
            break;
        case dDefault:
            GetDialogItem(dlg, dDefault, &kind, &h, &r);
            GetPenState(&ps);
            PenNormal();                                /* always be on the defensive */
            PenSize(3, 3);
            InsetRect(&r, -4, -4);
            FrameRoundRect(&r, 16, 16);                 /* draw default box */
            SetPenState(&ps);
            break;
        }
    SetPort(port);                                      /* restore old port */
} /* ZoneListDraw */
 
 
#pragma segment Main
pascal Boolean ListFilter (dlg, event, item)
    DialogPtr   dlg;
    EventRecord *event;
    short       *item;
{
 
/*  Passed as parameter to ModalDialog. Handle key presses and mouse clicks
    from the user. Do all the right default actions since we override them
    by virtue of our existence. */
 
    GrafPtr     port;
    Point       loc;
    short       kind;
    Handle      h;
    Rect        r;
    Boolean     ignore;
    char        key;
    long        finalTicks;
 
    Boolean     returnValue;
 
    returnValue = false;                                            /*  always default false */
 
    switch (event->what) {
        case keyDown:                                               /*  check for <cr> or <enter> */
        case autoKey:
            key = (char) event->message;
            if (key == kCR || key == kENTER) {                      /*  it was a <cr> or <enter> */
                GetDialogItem(dlg, ok, &kind, &h, &r);
                HiliteControl((ControlHandle)h, kHilite);
                Delay(kHiliteDelay, &finalTicks);
                HiliteControl((ControlHandle)h, kDeHilite);
                returnValue = true;                                 /*  so we handle it */
                *item = 1;                                          /*  and make the first item hit */
                }
            break;
        case mouseDown:                                             /*  we want mouseDowns */
            GetPort(&port);
            SetPort(dlg);
            loc = event->where;
            GlobalToLocal(&loc);                                    /*  find where clicked */
            GetDialogItem(dlg, dZoneList, &kind, &h, &r);               /*  get rect for list */
            if (PtInRect(loc, &r)) {                                /*  if clicked insideÉ */
                returnValue = true;                                 /*  we take care of it */
                ignore = LClick(loc, event->modifiers, gList);      /*  by passing click to list */
                }
            SetPort(port);
            break;
        }
    return (returnValue);
} /* ListFilter */
 
 
#pragma segment Main
void CleanUp_DoZoneList()
{
    if (gList != nil)
        LDispose(gList);                                    /*  get rid of list */
    if (gErrDlg != nil)
        DisposeDialog(gErrDlg);                             /*  get rid of dialog */
} /* CleanUp_DoZoneList */
 
 
#pragma segment Main
pascal void HandleErr_DoZoneList(short error, long message)
{
    #pragma unused (error, message)
 
    CleanUp_DoZoneList();                               /*  release junk */
} /* HandleErr_DoZoneList */
 
 
#pragma segment Main
void DoZoneList()
 
/*  Put up a modal dialog that shows a list of the zones on the net. Create the dialog
 and list, call BuildZoneList to fill it, then wait for the user to click OK. */
 
{
    DialogPtr   dlg;
    short       item, kind;
    Handle      h;
    Rect        r, rView, dataBounds;
    Cell        cSize;
    FailInfo    fi;
    short       hor, ver;
    ModalFilterUPP mfUPP;
 
    gList = nil;                                            /*  init some important variables */
    dlg = nil;
 
    CatchCFailures(&fi, HandleErr_DoZoneList);
 
    dlg = GetNewDialog(rZoneDialog, nil, (WindowPtr)-1);            /*  create dialog */
    SetPort(dlg);
 
    gErrDlg = dlg;
 
    FailnilMsg(dlg, eNoMemory);
 
    /*  We center the dialog horizontally and position it vertically one-third the
     distance from the menu bar to the bottom of the main device. We do not
     check for the dialog extending past the bottom of the device because we
     know the dialog is not that big. You may wish to make that check. */
 
    hor = dlg->portRect.right - dlg->portRect.left;
    ver = dlg->portRect.bottom - dlg->portRect.top;
 
    hor = ((qd.screenBits.bounds.right - qd.screenBits.bounds.left) - hor) / 2;
    ver = (((qd.screenBits.bounds.bottom - qd.screenBits.bounds.top) - ver - GetMBarHeight()) / 3) + GetMBarHeight();
 
    MoveWindow(dlg, hor, ver, false);
 
    GetDialogItem(dlg, dDefault, &kind, &h, &r);
    SetDialogItem(dlg, dDefault, kind, (Handle) ZoneListDraw, &r);
    GetDialogItem(dlg, dZoneList, &kind, &h, &r);
    SetDialogItem(dlg, dZoneList, kind, (Handle) ZoneListDraw, &r);     /*  connect drawing void */
    rView = r;
    rView.right -= kScrollBarWidth;                         /*  adjust rectangle for scroll */
    SetRect(&dataBounds, 0, 0, 1, 0);                       /*  init to one-wide list */
    SetPt(&cSize, 0, 0);
    gList = LNew(&rView, &dataBounds, cSize, 0, (WindowPtr)dlg,
                    false, false, false, true);             /*  create with vertical scroll */
    FailnilMsg(gList, eNoMemory);
 
    /* changes for Phase 2 - pvh 8/6/89 */
    /* this is the easiest check for Phase 2's existence */
    if(gMac.atDrvrVersNum >= 53)
        BuildZoneListPhase2();                                  /*  put the stuff into the list */
    else
        BuildZoneList();                                        /*  put the stuff into the list */
 
    SetPt(&cSize, 0, 0);
    LSetSelect(true, cSize, gList);                         /*  select the first guy */
    LSetDrawingMode(true, gList);                                   /*  turn on the list */
    ShowWindow(dlg);                                        /*  turn on the dialog */
 
    mfUPP = NewModalFilterProc(ListFilter);
    do {
        ModalDialog(mfUPP, &item);  /*  accept events */
    } while (item != ok);                                   /*  until he presses 'ok' */
    DisposeRoutineDescriptor(mfUPP);
 
    CleanUp_DoZoneList();
 
    Success(&fi);
} /* DoZoneList */
 
 
#pragma segment Main
Boolean DoCloseWindow(window)
    WindowPtr   window;
{
    Boolean  functionValue = true;
 
    if (IsDAWindow(window))
        CloseDeskAcc((short) ((WindowPeek)window)->windowKind);
    if (IsAppWindow(window))
        CloseWindow(window);
 
    return(functionValue);
} /* DoCloseWindow */
 
 
#pragma segment Initialize
pascal void HandleErr_Initialize(error, message)
    short   error;
    long    message;
{
        if (error > 0)
            AlertUser(0, error);
        else
            AlertUser(error, message);
        ExitToShell();
} /* HandleErr_Initialize */
 
 
#pragma segment Initialize
void Initialize()
{
    Handle          menuBar;
    OSErr           ignoreError;
    long            total, contig;
    Boolean         ignoreResult;
    EventRecord     event;
    short           count;
    FailInfo        fi;
 
    gHasWaitNextEvent = TrapAvailable(_WaitNextEvent, ToolTrap);
    gInBackground = false;
 
    InitGraf(&qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(nil);
    InitCursor();
 
    /* get MultiFinder started */
    for (count=1;count<3;count++)
        ignoreResult = EventAvail(everyEvent, &event);
 
    CatchCFailures(&fi, HandleErr_Initialize);
 
    FailOSErrMsg(MPPOpen(), eAppleTalk);
    FailOSErrMsg(ATPLoad(), eAppleTalk);
 
    ignoreError = SysEnvirons(kSysEnvironsVersion, &gMac);
    if (gMac.machineType < 0)
        Failure(0, eWrongMachine);
 
    if (GetApplLimit() - ApplicationZone() < kMinHeap)
        Failure(0, eSmallSize);
 
    PurgeSpace(&total, &contig);
    if (total < kMinSpace)
        Failure(0, eNoMemory);
 
    menuBar = GetNewMBar(rMenuBar);             /*  read menus into menu bar */
    FailnilMsg(menuBar, eNoMemory);
 
    SetMenuBar(menuBar);                        /*  install menus */
    DisposeHandle(menuBar);
    AppendResMenu(GetMenuHandle(mApple), 'DRVR');       /*  add DA names to Apple menu */
    DrawMenuBar();
 
    Success(&fi);
} /* Initialize */
 
 
#pragma segment Main
void Terminate()
{
    WindowPtr   aWindow;
    Boolean     closed;
 
    closed = true;
    do {
        aWindow = FrontWindow();                /*  get the current front window */
        if (aWindow != nil)
            closed = DoCloseWindow(aWindow);    /*  close this window */
    } while ((closed) && (aWindow != nil));     /*  do all windows */
    if (closed)
        ExitToShell();                          /*  exit if no cancellation */
} /* Terminate */
 
 
#pragma segment Main
void AdjustMenus()
{
    WindowPtr   window;
    MenuHandle  menu;
 
    window = FrontWindow();
 
    menu = GetMenuHandle(mFile);
    if (IsDAWindow(window))                     /*  we can allow desk accessories to be closed from the menu */
        EnableItem(menu, iClose);
    else
        DisableItem(menu, iClose);              /*  but not our traffic light window */
 
    menu = GetMenuHandle(mEdit);
    if (IsDAWindow(window)) {                   /*  a desk accessory might need the edit menu */
        EnableItem(menu, iUndo);
        EnableItem(menu, iCut);
        EnableItem(menu, iCopy);
        EnableItem(menu, iPaste);
        EnableItem(menu, iClear);
        }
    else {                                      /*  but we know we do not */
        DisableItem(menu, iUndo);
        DisableItem(menu, iCut);
        DisableItem(menu, iCopy);
        DisableItem(menu, iClear);
        DisableItem(menu, iPaste);
        }
} /* AdjustMenus */
 
 
pascal void HandleMenu(short error, long message)
{
    #pragma unused (error, message)
 
    HiliteMenu(0);                              /*  unhighlight what MenuSelect (or MenuKey) hilited */
} /* HandleMenu */
 
 
#pragma segment Main
void DoMenuCommand(menuResult)
    long    menuResult;
{
    short       menuID;                         /*  the resource ID of the selected menu */
    short       menuItem;                       /*  the item number of the selected menu */
    short       itemHit;
    Str255      daName;
    short       daRefNum;
    Boolean     handledByDA ;
    Boolean     ignore;
    FailInfo    fi;
 
    CatchCFailures(&fi, (HandlerFuncPtr) HandleMenu);
 
    menuID = HiWord(menuResult);                            /*  use built-ins (for efficiency)... */
    menuItem = LoWord(menuResult);                      /*  to get menu item number and menu number */
    switch (menuID) {
        case mApple:
            switch (menuItem) {
                case iAbout:                            /*  bring up alert for About */
                    itemHit = Alert(rAboutAlert, nil);
                    break;
                default:                                /*  all non-About items in this menu are DAs */
                    GetMenuItemText(GetMenuHandle(mApple), menuItem, daName);
                    daRefNum = OpenDeskAcc(daName);
                    break;
                }
            break;
        case mFile:
            switch (menuItem) {
                case iNew:
                    DoZoneList();
                    break;
                case iClose:
                    ignore = DoCloseWindow(FrontWindow());
                    break;
                case iQuit:
                    Terminate();
                    break;
                }
            break;
        case mEdit:                                 /*  call SystemEdit for DA editing & Multifinder */
            handledByDA = SystemEdit(menuItem-1);   /*  since we don't do any editing */
            break;
        }
 
    HiliteMenu(0);                                  /*  cleanup */
 
    Success(&fi);
} /* DoMenuCommand */
 
 
#pragma segment Main
pascal void HandleErr_DoEvent(error, message)
    short   error;
    long    message;
{
    if (error > 0)
        AlertUser(0, error);
    else
        AlertUser(error, message);
    ExitToShell();
} /* HandleErr_DoEvent */
 
 
#pragma segment Main
void DoEvent(event)
    EventRecord event;
{
    short       part;
    WindowPtr   window;
    char        key;
    FailInfo    fi;
    Point       aPoint;
    OSErr       err;
 
    CatchCFailures(&fi, (HandlerFuncPtr) HandleErr_DoEvent);
 
    switch (event.what) {
        case mouseDown:
            part = FindWindow(event.where, &window);
            switch (part) {
                case inMenuBar:                         /*  process the menu command */
                    AdjustMenus();
                    DoMenuCommand(MenuSelect(event.where));
                    break;
                case inSysWindow:                       /*  let the system handle the mouseDown */
                    SystemClick(&event, window);
                    break;
                case inContent:
                    break;
                case inDrag:
                    break;
                case inGrow:
                    break;
                case inZoomIn:
                case inZoomOut:
                    break;
                }
            break;
        case keyDown:                                   /*  check for menukey equivalents */
        case autoKey:
            key = event.message & charCodeMask;
            if (event.modifiers & cmdKey) {             /*  Command key down */
                if (event.what == keyDown) {
                    AdjustMenus();                      /*  enable/disable/check menu items properly */
                    DoMenuCommand(MenuKey(key));
                    }
                }
            break;
        /*  call DoActivate with the window and... */
        case activateEvt:
            break;
        case updateEvt:
            break;
        /* It is not a bad idea to at least call DIBadMount in response
         to a diskEvt, so that the user can format a floppy. */
        case diskEvt:
            if (HiWord(event.message) != noErr) {
                SetPt(&aPoint, kDILeft, kDITop);
                err = DIBadMount(aPoint, event.message);
            }
        case kOSEvent:
            switch ((event.message >> 24) & 0x0FF) {    /* high byte of message */
                case kSuspendResumeMessage:
                    gInBackground = event.message & kResumeMask;
                    break;
                }
            break;
        }
    Success(&fi);
} /* DoEvent */
 
 
#pragma segment Main
void EventLoop()
{
    RgnHandle   cursorRgn;
    Boolean     gotEvent;
    EventRecord event;
 
    cursorRgn = NewRgn();           /*  weÕll pass WNE an empty region the 1st time thru */
 
    do {
        if (gHasWaitNextEvent)      /*  put us 'asleep' forever under Multifinder */
            gotEvent = WaitNextEvent(everyEvent, &event, LONG_MAX, cursorRgn);
        else {
            SystemTask();           /*  must be called if using GetNextEvent */
            gotEvent = GetNextEvent(everyEvent, &event);
            }
        if (gotEvent) {
            DoEvent(event);
            }
    } while (true);                 /*  loop forever; we quit through an ExitToShell */
} /* EventLoop */
 
 
#pragma segment Main
void main()
{
    UnloadSeg(_DataInit);           /*  note that _DataInit must not be in Main! */
    MaxApplZone();                  /*  expand the heap so code segments load at the top */
 
    InitUFailure();
 
    Initialize();                   /* initialize the program */
    UnloadSeg(Initialize);          /* note that Initialize must not be in Main! */
 
    EventLoop();                    /* call the main event loop */
} /* main */