Application.cp

/*------------------------------------------------------------------------------------------
 
    Program:    CPlusTESample 2.0
    File:       Application.cp
    Uses:       Application.h
                Document.h
 
    by Andrew Shebanow
    of Apple Macintosh Developer Technical Support
 
    Copyright © 1989-1990 Apple Computer, Inc.
    All rights reserved.
 
------------------------------------------------------------------------------------------*/
 
// Mac Includes
 
#define OBSOLETE
    // for ClrAppFiles(), CountAppFiles(), GetAppFiles() et. all.
    
#ifndef __TYPES__
#include <Types.h>
#endif
#ifndef __QUICKDRAW__
#include <QuickDraw.h>
#endif
#ifndef __FONTS__
#include <Fonts.h>
#endif
#ifndef __EVENTS__
#include <Events.h>
#endif
#ifndef __CONTROLS__
#include <Controls.h>
#endif
#ifndef __WINDOWS__
#include <Windows.h>
#endif
#ifndef __RESOURCES__
#include <Resources.h>
#endif
#ifndef __MENUS__
#include <Menus.h>
#endif
#ifndef __TEXTEDIT__
#include <TextEdit.h>
#endif
#ifndef __DIALOGS__
#include <Dialogs.h>
#endif
#ifndef __MENUS__
#include <Menus.h>
#endif
#ifndef __DEVICES__
#include <Devices.h>
#endif
#ifndef __EVENTS__
#include <Events.h>
#endif
#ifndef __SCRAP__
#include <Scrap.h>
#endif
#ifndef __TOOLUTILS__
#include <ToolUtils.h>
#endif
#ifndef __MEMORY__
#include <Memory.h>
#endif
#ifndef __SEGLOAD__
#include <SegLoad.h>
#endif
#ifndef __FILES__
#include <Files.h>
#endif
#ifndef __OSUTILS__
#include <OSUtils.h>
#endif
#ifndef __TRAPS__
#include <Traps.h>
#endif
 
#ifndef __APPLICATION__
#include "Application.h"
#endif
 
#ifndef __DOCUMENT__
#include "Document.h"
#endif
 
// OSEvent is the event number of the suspend/resume and mouse-moved events sent
// by MultiFinder. Once we determine that an event is an osEvent, we look at the
// high byte of the message sent to determine which kind it is. To differentiate
// suspend and resume events we check the resumeMask bit.
const short kOsEvent = app4Evt;             // event used by MultiFinder
const short kSuspendResumeMessage = 0x01;   // high byte of suspend/resume event message
const short kClipConvertMask = 0x02;        // bit of message field clip conversion
const short kResumeMask = 0x01;             // bit of message field for resume vs. suspend
const short kMouseMovedMessage = 0xFA;      // high byte of mouse-moved event message
 
extern "C" {
    // from MPW standard library
    void _DataInit();               // sets up A5 globals
};
 
// useful state checking Boolean
Boolean gHaveColorQD;
 
// just as "normal" global variables that are declared extern in
// header files must still be declared somewhere in order to reserve
// space, static class variables must also be declared outside of
// the class definition. The syntax is confusing, but then, thats
// what makes C++ so ***interesting***.
OSType TApplication::fCreator;
 
TApplication::TApplication(OSType creator)
{
    SysEnvRec envRec;
    long stkNeeded;
    long heapSize;
 
    // initialize Mac Toolbox components
    InitGraf((Ptr) &qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(NULL);
    InitCursor();
 
    // Unload data segment: note that _DataInit must not be in Main!
    UnloadSeg((ProcPtr) _DataInit);
 
    // ignore the error returned from SysEnvirons; even if an error occurred,
    // the SysEnvirons glue will fill in the SysEnvRec
    (void) SysEnvirons(curSysEnvVers, &envRec);
 
    // Are we running on a 128K ROM machine or better???
    if (envRec.machineType < 0)
      BigBadError(kErrStrings,eWrongMachine);       // if not, alert & quit
 
    gHaveColorQD = envRec.hasColorQD;
 
    // if we need more stack space, get it now
    stkNeeded = StackNeeded();
    if (stkNeeded > StackSpace())
      {
        // new address is heap size + current stack - needed stack
        SetApplLimit((Ptr) ((long) GetApplLimit() - stkNeeded + StackSpace()));
      }
 
    // Check for minimum heap size
    heapSize = (long) GetApplLimit() - (long) ApplicationZone();
    if (heapSize < HeapNeeded())
      BigBadError(kErrStrings,eSmallSize);
 
    // expand the heap so new code segments load at the top
    MaxApplZone();
 
    // allocate an empty document list
    fDocList = new TDocumentList;
 
    // check to see if WaitNextEvent is implemented
    fHaveWaitNextEvent = TrapAvailable(_WaitNextEvent, ToolTrap);
 
    // initialize our class variables
    fCurDoc = nil;
    fDone = false;
    fInBackground = false;
    fMouseRgn = nil;
    fWhichWindow = nil;
    fCreator = creator;
}
 
void TApplication::EventLoop()
{
    int gotEvent;
    EventRecord tEvt;
 
    SetUp();                    // call setup routine
    DoIdle();                   // do idle once
 
    while (!fDone)
      {
        // always set up fWhichWindow before doing anything
        fWhichWindow = FrontWindow();
        if (fWhichWindow != nil)
          {
            // see if window belongs to a document
            fCurDoc = fDocList->FindDoc(fWhichWindow);
            // make sure we always draw into correct window
            SetPort(fWhichWindow);
          }
        else fCurDoc = nil;
 
        DoIdle();           // call idle time handler
 
        if (fHaveWaitNextEvent)
          gotEvent = WaitNextEvent(everyEvent, &tEvt, SleepVal(), fMouseRgn);
        else
          {
            SystemTask();
            gotEvent = GetNextEvent(everyEvent, &tEvt);
          }
        fTheEvent = tEvt;
 
        // if we got a real event, process it
        if (gotEvent)
          ProcessEvent();
 
        AdjustCursor();
      }
}
 
void TApplication::ProcessEvent()
{
    // make sure alert is loaded in memory BEFORE we do any event
    // processing. This is necessary since the alert for the case
    // when we need to display an out of memory alert.
    // CouldAlert(rUserAlert);
    TRY
      {
        AdjustCursor();
        switch (fTheEvent.what)
          {
            case mouseDown:
                DoMouseDown();
                break;
            case mouseUp:
                DoMouseUp();
                break;
            case keyDown:
            case autoKey:
                DoKeyDown();
                break;
            case updateEvt:
                DoUpdateEvt();
                break;
            case diskEvt:
                DoDiskEvt();
                break;
            case activateEvt:
                DoActivateEvt();
                break;
            case kOsEvent:
                DoOSEvent();
                break;
            default:
                break;
          }
      }
    RECOVER
      {
        AlertUser((short) gFailMessage, gFailError);
        // don't let error bubble up any farther
        goto doneEvent;
      }
    ENDTRY
 
doneEvent:
    return;
}
 
void TApplication::DoKeyDown()
{
    char key;
    long mResult;
 
    key = (char) (fTheEvent.message & charCodeMask);
    if ((fTheEvent.modifiers & cmdKey) && (fTheEvent.what == keyDown))
      {
        // only do command keys if we are not autokeying
        AdjustMenus();                  // make sure menus are up to date
        mResult = MenuKey(key);
        if (mResult != 0)               // if it wasn't a menu key, pass it through
          {
            DoMenuCommand(HiWord(mResult), LoWord(mResult));
            return;
          }
      }
    if (fCurDoc != nil)
      {
        EventRecord tEvt;
 
        // we copy event record so that we don't pass reference to object field
        tEvt = fTheEvent;
        fCurDoc->DoKeyDown(&tEvt);
      }
}
 
void TApplication::DoActivateEvt()
{
    // event record contains window ptr
    fWhichWindow = (WindowPtr) fTheEvent.message;
    // see if window belongs to a document
    fCurDoc = fDocList->FindDoc(fWhichWindow);
    SetPort(fWhichWindow);
 
    if (fCurDoc != nil)
      fCurDoc->DoActivate((fTheEvent.modifiers & activeFlag) != 0);
}
 
void TApplication::DoUpdateEvt()
{
    // event record contains window ptr
    fWhichWindow = (WindowPtr) fTheEvent.message;
    // see if window belongs to a document
    fCurDoc = fDocList->FindDoc(fWhichWindow);
    SetPort(fWhichWindow);
 
    if (fCurDoc != nil)
      fCurDoc->DoUpdate();
}
 
// NOTE: we use an anonymous parameter here so that the compiler
// doesn't warn us about it being unused. Since we give it a name
// in the class definition, we still know what its used for.
void TApplication::DoSuspend(Boolean)
{
    if (fCurDoc != nil)
      fCurDoc->DoActivate(!fInBackground);
}
 
void TApplication::DoResume(Boolean)
{
    if (fCurDoc != nil)
      fCurDoc->DoActivate(!fInBackground);
}
 
void TApplication::DoOSEvent()
{
    Boolean doConvert;
    unsigned char evType;
 
    // is it a multifinder event?
    evType = (unsigned char) (fTheEvent.message >> 24) & 0x00ff;
    switch (evType) {   // high byte of message is type of event
        case kMouseMovedMessage:
            DoIdle();                   // mouse-moved is also an idle event
            break;
        case kSuspendResumeMessage:
            doConvert = (fTheEvent.message & kClipConvertMask) != 0;
            fInBackground = (fTheEvent.message & kResumeMask) == 0;
            if (fInBackground)
              DoSuspend(doConvert);
            else DoResume(doConvert);
            break;
    }
}
 
void TApplication::DoMouseDown()
{
    long mResult;
    short partCode;
    WindowPtr tWind;
    EventRecord tEvt;
 
    // gotta watch those object field dereferences
    partCode = FindWindow(fTheEvent.where, &tWind);
    fWhichWindow = tWind;
    tEvt = fTheEvent;
    switch (partCode)
      {
        case inSysWindow:
            DoMouseInSysWindow();
            break;
        case inMenuBar:
            AdjustMenus();
            mResult = MenuSelect(tEvt.where);
            if (mResult != 0)
              DoMenuCommand(HiWord(mResult),LoWord(mResult));
            break;
        case inGoAway:
            DoGoAway();
            break;
        case inDrag:
            DoDrag();
            break;
        case inGrow:
            if (fCurDoc != nil)
              fCurDoc->DoGrow(&tEvt);
            break;
        case inZoomIn:
        case inZoomOut:
            if ((TrackBox(fWhichWindow, tEvt.where, partCode)) &&
                (fCurDoc != nil))
              fCurDoc->DoZoom(partCode);
            break;
        case inContent:
            // If window is not in front, make it so
            if ( fWhichWindow != FrontWindow() )
              SelectWindow(fWhichWindow);
            else if (fCurDoc != nil)
              fCurDoc->DoContent(&tEvt);
            break;
      }
}
 
void TApplication::DoDrag()
{
    DragWindow(fWhichWindow, fTheEvent.where, &qd.screenBits.bounds);
}
 
void TApplication::DoGoAway()
{
    if (TrackGoAway(fWhichWindow, fTheEvent.where))
      {
        if (fCurDoc != nil)
          {
            if (fCurDoc->DoClose(true, yesResult, false) != cancelResult)
              {
                fDocList->RemoveDoc(fCurDoc);
                delete fCurDoc;
              }
          }
        else CloseDeskAcc(((WindowPeek) fWhichWindow)->windowKind);
        // make sure our current document/window references are valid
        fWhichWindow = FrontWindow();
        if (fWhichWindow != nil)
          {
            fCurDoc = fDocList->FindDoc(fWhichWindow);
            SetPort(fWhichWindow);
          }
        else fCurDoc = nil;
      }
}
 
void TApplication::ProcessArgs()
{
    short message, numFiles, curFile;
    AppFile fileInfo;
 
    /* count the files */
    CountAppFiles(&message,&numFiles);
    if (numFiles == 0)
      {
        // create a single empty document
        DoNew();
        return;
      }
    for (curFile = 1; curFile <= numFiles; curFile++)
      {
        /* get file info */
        GetAppFiles(curFile,&fileInfo);
        /* open/print the file */
        if (message != appPrint)
          {
            TRY
              {
                OpenADoc(fileInfo.vRefNum,0,fileInfo.fName,fileInfo.fType);
              }
            RECOVER
              {
                goto processNextFile;
              }
            ENDTRY
          }
processNextFile:
        /* clear finder arg for this file */
        ClrAppFiles(curFile);
      }
}
 
void TApplication::DoQuit(Boolean askUser, YNCResult defaultResult)
{
    while (true)
      {
        fWhichWindow = FrontWindow();
        if (fWhichWindow == nil)
          break;
        fCurDoc = fDocList->FindDoc(fWhichWindow);
        if (fCurDoc != nil)
          {
            // if the user cancels the quit
            if (fCurDoc->DoClose(askUser, defaultResult, true) == cancelResult)
              return;
            else
              {
                fDocList->RemoveDoc(fCurDoc);
                delete fCurDoc;
              }
          }
        else CloseDeskAcc(((WindowPeek) fWhichWindow)->windowKind);
        // make sure we aren't in an infinite loop. This could occur
        // if the CloseDeskAcc was failing for some reason.
        if (FrontWindow() == fWhichWindow)
          {
            // send the window to the back of the list.
            // if the FrontWindow is still the same, we will exit
            // the loop. Otherwise, we let the loop keep running so
            // that we have a chance to close our other windows
            // cleanly
            SendBehind(fWhichWindow, nil);
            if (FrontWindow() == fWhichWindow)
              break;
          }
      }
    fDone = true;
    fWhichWindow = nil;
    fCurDoc = nil;
}
 
Boolean TApplication::TrapAvailable(short tNumber,TrapType tType)
{
    // Check and see if the trap exists. On 64K ROM machines, tType will be ignored.
    return NGetTrapAddress(tNumber, tType) != NGetTrapAddress(_Unimplemented, ToolTrap);
}
 
void TApplication::WDToDirID(short wdRefNum, short& vRefNum, long& dirID)
{
    const short kRootDirID = 2;
    long junk;
 
    OSErr err = GetWDInfo(wdRefNum,&vRefNum,&dirID,&junk);
    if (err != noErr)
      {
        vRefNum = wdRefNum;     // if GetVol doesn't return valid vRefNum/dirID pair,
        dirID = kRootDirID;     // use wdRefNum as a vRefNum and use root for dirID
      }
}
 
void AlertUser(short errResID, short errCode)
{
    Str255 messageStr;
 
    // if we have a hilited menu, turn it off before displaying alert
    HiliteMenu(0);
 
    if (errResID != 0)
      {
        GetIndString(messageStr, errResID, errCode);
        ParamText(messageStr, "\p", "\p", "\p");
      }
    else
      {
        // we need to lookup the error in our table
        LookupErrorString(errCode,kSysErrStrings,messageStr);
        ParamText(messageStr, "\p", "\p", "\p");
      }
    SetCursor(&qd.arrow);
    (void) Alert(rUserAlert, (ModalFilterProcPtr) nil);
}
 
void BigBadError(short errResID, short errCode)
{
    AlertUser(errResID,errCode);
    ExitToShell();
}
 
Boolean LookupErrorString(short value, short resID, StringPtr str)
{
    struct ErrRecord {
        short lowErr;
        short highErr;
        short index;
    };
    typedef struct ErrRecord* ErrRecordPtr;
 
    Handle          table;
    ErrRecordPtr    pEntry;
    unsigned long   tableOffset;
    long            lenTab;
    int             strID;
 
    // start with an empty string
    str[0] = 0;
 
    table = GetResource('errs', resID);
    if (!table)
      {
        lenTab = (long) (GetHandleSize((Handle) table) / sizeof(ErrRecord));
 
        strID = 0;
        tableOffset = 0;
 
        for (long i = 1; i <= lenTab; i++)
          {
            pEntry = (ErrRecordPtr) ((unsigned long) *table) + tableOffset;
 
            if (pEntry->lowErr == 0)
              strID = pEntry->index;
            else if ((pEntry->lowErr <= value) && (value <= pEntry->highErr))
              {
                if (pEntry->index > 0)
                  GetIndString(str, strID, pEntry->index);
                return true;
              }
 
            tableOffset += sizeof(ErrRecord);
          }
      }
    return false;
}
 
// That's all, folks...