CDEV.c

/*------------------------------------------------------------------------------
#
#   Macintosh Developer Technical Support
#
#   Sample Control Panel Device and INIT Combination
#
#   Program:    INIT - CDEV
#   File:       CDEV.c  -   C Source
#
#   Copyright © 1990 Apple Computer, Inc.
#   All rights reserved.
#
------------------------------------------------------------------------------*/
 
#include <Common.h>
 
#include <Quickdraw.h>
#include <Files.h>
#include <Memory.h>
#include <OSUtils.h>
#include <TextEdit.h>
#include <Devices.h>
#include <Dialogs.h>
#include <Packages.h>
#include <TextUtils.h>
 
/*------------------------------------------------------------------------------
    Constants
------------------------------------------------------------------------------*/
 
#define kNoINIT         -4048           /* Alert: INIT was not installed    */
#define kCantWritePrefs -4047           /* Alert: error writing preferences */
 
#define kAboutText1     1
#define kAboutText2     2
#define kPrompt1        3
#define kPrompt2        4
#define kTimesToBeep    5               /* First editTExt item in cdev      */
#define kBeepInterval   6
 
 
/*------------------------------------------------------------------------------
    Types
------------------------------------------------------------------------------*/
 
typedef struct CDEVRec {
    CommonGlobalsPtr    commonGlobals;
} CDEVRec, *CDEVPtr, **CDEVHnd;
 
 
/*------------------------------------------------------------------------------
    Procedure Prototypes and External Routines
------------------------------------------------------------------------------*/
 
/* These procedures are from the orginal EditTextCDEV sample. */
 
pascal  long    TextCDEV(short message, short item, short numItems,
                        short CPanelID, EventRecord *theEvent,
                        Handle cdevStorage, DialogPtr CPDialog);
        void    DoEditCommand(short message, DialogPtr CPDialog);
 
/* Added for INIT - CDEV combo. */
 
        void    InitEditValues(CDEVHnd cdevStorage, short numItems,
                                DialogPtr CPDialog);
        void    UpdateINIT(CDEVHnd cdevStorage, short numItems,
                                DialogPtr CPDialog);
        void    UpdatePrefs(CDEVHnd cdevStorage);
        long    GetItemValue(short itemNumber, DialogPtr CPDialog);
        void    SetItemValue(short itemNumber, DialogPtr CPDialog, long value);
        void    GetCommonStorage(CommonGlobalsPtr *commonGlobals);
        OSErr   OpenAPort(short *refNum);
pascal  void    StartCompProc(PPCParamBlockPtr ppb);
 
 
/*------------------------------------------------------------------------------
 
    pascal long TextCDEV(short message, short item, short numItems,
                        short CPanelID, EventRecord *theEvent,
                        Handle cdevStorage, DialogPtr CPDialog);
 
    This is the main dispatcher. It must be the first code in the cdev.
    EditCdevÕs dispatcher responds only to the following messages from the
    Control Panel:
 
    macDev      - To indicate what machines it is available on.
    initDev     - To set up some temporary storage and get the caret started.
    keyEvtDev   - To check for an edit command and do the appropriate action.
    cutDev      - To cut the current selection.
    copyDev     - To copy the current selection.
    pasteDev    - To paste the contents of the clipboard.
    clearDev    - To delete the current selection.
    closeDev    - To dispose of any data allocated in initDev.
 
    The Dialog ManagerÕs services are used to handle entry of text, selection
    of text, editing of text, and moving between the editText items via the
    tab key. Since the Dialog Manager handles the selection of text, we do not
    have to be concerned with hitDev messages for the editText items. The only
    things we have to take care of are calling the Dialog Manager editing
    routines in response to an edit command, and getting the caret to show up
    at the beginning. In response to an edit command that was the result of a
    command-key equivalent, we must also eliminate the event so that it does
    not get processed as a keyDown by the Dialog Manager. Otherwise, an ÔxÕ
    would show up in the editText item when the user did a command-x to cut
    the text.
 
------------------------------------------------------------------------------*/
 
pascal long TextCDEV(short message, short item, short numItems, short CPanelID,
        EventRecord *theEvent, Handle cdevStorage, DialogPtr CPDialog)
{
#pragma unused (item, CPanelID)         /* unused formal parameters */
 
    char                tempChar;
    CommonGlobalsPtr    commonGlobals;
    long                result;
 
    if (message == macDev) {
        if (Gestalt(gestaltPPCToolboxAttr, &result))
            return(0);                          /* No PPCToolbox; hide me */
        else
            return(1);                          /* Have PPCToolBox; show me */
    }
    else if (cdevStorage != nil) {
        switch (message) {
 
            case initDev:
                GetCommonStorage(&commonGlobals);
                if (commonGlobals) {
                    /* create private storage */
                    cdevStorage = NewHandle(sizeof(CDEVRec));
                    if (cdevStorage) {
                        (**(CDEVHnd)cdevStorage).commonGlobals = commonGlobals;
                        InitEditValues( (CDEVHnd) cdevStorage, numItems, CPDialog);
                        /* make caret show up */
                        SelectDialogItemText(CPDialog, numItems + kTimesToBeep, 0, 999);
                    }
                } else {
                    (void) StopAlert(kNoINIT, (ModalFilterProcPtr) nil);
                    return(cdevGenErr);
                }
                break;
 
            case closeDev:                  /* clean up and dispose */
                UpdateINIT((CDEVHnd) cdevStorage, numItems, CPDialog);
                UpdatePrefs((CDEVHnd) cdevStorage);
                DisposeHandle(cdevStorage);
                break;
 
            case hitDev:                    /* handle hit on item */
            case nulDev:
            case updateDev:                 /* handle any update drawing */
            case activDev:                  /* activate any needed items */
            case deactivDev:                /* deactivate any needed items */
                break;
 
            case keyEvtDev:                 /* respond to keydown */
                tempChar = theEvent->message & charCodeMask;/* get the character, and check */
                if (theEvent->modifiers & cmdKey) {     /*  status of command key */
                    message = nulDev;                   /* start with no message */
                    theEvent->what = nullEvent;         /* and empty event type */
 
                    switch (tempChar) {                 /* set appropriate message */
 
                        case 'X':
                        case 'x':
                            message = cutDev;
                            break;
                        case 'C':
                        case 'c':
                            message = copyDev;
                            break;
                        case 'V':
                        case 'v':
                            message = pasteDev;
                            break;
                    }
                    DoEditCommand(message, CPDialog);   /* Let edit command handler take it */
                }
                break;
 
            case macDev:
            case undoDev:
                break;
 
            case cutDev:
            case copyDev:
            case pasteDev:
            case clearDev:
                DoEditCommand(message, CPDialog);       /* respond to edit command */
                break;
        }
 
        return ((long) cdevStorage);
    }  /* cdevStorage != nil */
 
    /*
    **  We get here iff cdevStorage = NIL. Return 0 so that Control Panel
    **  will put up "out of memory" error
    */
    return (0);
}
 
 
/*------------------------------------------------------------------------------
 
    void DoEditCommand (short message, DialogPtr CPDialog)
 
    Call the appropriate Dialog Manager routine to handle an edit command for
    an editText item. It will do all the work regarding the TEScrap.
 
------------------------------------------------------------------------------*/
 
void DoEditCommand (short message, DialogPtr CPDialog)
{
    switch (message) {
        case cutDev:
            DialogCut(CPDialog);
            break;
        case copyDev:
            DialogCopy(CPDialog);
            break;
        case pasteDev:
            DialogPaste(CPDialog);
            break;
        case clearDev:
            DialogDelete(CPDialog);
            break;
    }
}
 
 
/*------------------------------------------------------------------------------
 
    void InitEditValues(CDEVHnd cdevStorage, short numItems, DialogPtr CPDialog)
 
    Called when the CDEV is opened. It uses the pointer to the public globals
    that we got from the INIT to get the values to stuff into the EditText
    items.
 
------------------------------------------------------------------------------*/
 
void    InitEditValues (CDEVHnd cdevStorage, short numItems, DialogPtr CPDialog)
{
    CommonGlobalsPtr    commonGlobals;
 
    commonGlobals = (**cdevStorage).commonGlobals;
    SetItemValue(numItems + kTimesToBeep,           /* item to set */
                CPDialog,                           /* of this dialog */
                commonGlobals->timesToBeep);        /* set to this value */
    SetItemValue(numItems + kBeepInterval,          /* item to set */
                CPDialog,                           /* of this dialog */
                commonGlobals->beepInterval / 60);  /* set to this value */
}
 
 
/*------------------------------------------------------------------------------
 
    void UpdateINIT(CDEVHnd cdevStorage, short numItems, DialogPtr CPDialog)
 
    Called when the CDEV is about to close. It gets the values from the
    EditText items and stuffs them back into the INIT's public globals.
 
------------------------------------------------------------------------------*/
 
void    UpdateINIT (CDEVHnd cdevStorage, short numItems, DialogPtr CPDialog)
{
    CommonGlobalsPtr    commonGlobals;
 
    commonGlobals = (**cdevStorage).commonGlobals;
    commonGlobals->timesToBeep = GetItemValue(numItems + kTimesToBeep, CPDialog);
    commonGlobals->beepInterval = GetItemValue(numItems + kBeepInterval, CPDialog) * 60;
}
 
 
/*------------------------------------------------------------------------------
 
    void UpdatePrefs(CDEVHnd cdevStorage)
 
    Called when the CDEV is being closed. This routine writes to a preferences
    file, using the INIT's public globals as the output buffer. The data is
    written to the data fork; no resources are used at all. If the preferences
    file doesn't exist, it is created. Any errors are reported through an
    Alert saying that error number such-and-such occured.
 
------------------------------------------------------------------------------*/
 
void    UpdatePrefs(CDEVHnd cdevStorage)
{
    OSErr       err;
    FSSpec      spec;
    short       refNum;
    long        amountToWrite;
    Str255      errString;
 
    err = FindFolder(kOnSystemDisk, kPreferencesFolderType, kCreateFolder,
                    &spec.vRefNum, &spec.parID);
    if (err == noErr) {
        (void) PLstrcpy(spec.name, kPrefsFileName);
        err = FSpCreate(&spec, kCreator, kDocKind, smSystemScript);
        if ((err == noErr) || (err == dupFNErr)) {
            err = FSpOpenDF(&spec, fsRdWrPerm, &refNum);
            if (err == noErr) {
                amountToWrite = sizeof(CommonGlobalsRec);
                err = FSWrite(refNum, &amountToWrite,
                                (Ptr) (**cdevStorage).commonGlobals);
                (void) FSClose(refNum);
            }
        }
    }
 
    if (err) {
        NumToString(err, errString);
        ParamText(errString, nil, nil, nil);
        (void) StopAlert(kCantWritePrefs, (ModalFilterProcPtr) nil);
    }
}
 
 
/*------------------------------------------------------------------------------
 
    long GetItemValue(short itemNumber, DialogPtr CPDialog)
 
    Utility routine used to turn the text in an EditText item into a long,
    and return it.
 
------------------------------------------------------------------------------*/
 
long    GetItemValue(short itemNumber, DialogPtr CPDialog)
{
    short       itemType;
    Handle      itemHandle;
    Rect        itemRect;
    Str255      text;
    long        theNum;
 
    GetDialogItem(CPDialog, itemNumber, &itemType, &itemHandle, &itemRect);
    GetDialogItemText(itemHandle, text);
    StringToNum(text, &theNum);
    return(theNum);
}
 
 
/*------------------------------------------------------------------------------
 
    void SetItemValue(short itemNumber, DialogPtr CPDialog, long value)
 
    Utility routine used to turn a short into text, and stuff it into
    an EditText item
 
------------------------------------------------------------------------------*/
 
void    SetItemValue(short itemNumber, DialogPtr CPDialog, long value)
{
    short       itemType;
    Handle      itemHandle;
    Rect        itemRect;
    Str255      text;
 
    NumToString(value, text);
    GetDialogItem(CPDialog, itemNumber, &itemType, &itemHandle, &itemRect);
    SetDialogItemText(itemHandle, text);
}
 
 
/*------------------------------------------------------------------------------
 
    void GetCommonStorage(CommonGlobalsPtr *commonGlobals)
 
    This routine is called when the CDEV is opend to get a pointer to the
    INIT's public globals. During the life of the INIT, this pointer is  saved
    in a block referenced by a handle. This handle is saved for us by the
    Control Panel.
 
    We get the pointer to the INIT's public globals by using the PPCToolbox.
    Because we wrote the INIT, we know that it is identifying itself to us by
    creator/type. So we fill out a portName with that information, and make an
    asynchronous PPCStart call. We make the call asynchronously so that the
    PPCToolbox can go an process all of the subsequent Read/Write/
    End/Inform/etc. calls required in a PPC transaction. However, we have to
    hang around for the PPCStart call to finish, so we poll ioResult until it
    no longer equal 1 (ioResult = 1 is the international symbol for "I'm
    working on a task right now, come back later when I'm done.").
 
    When the PPCStart call completes, this means that the completion routine
    we installed, StartCompProc, has been called by the PPCToolbox. It is
    StartCompProc that was responsible for reading the data being sent to us
    by the INIT. Therefore, when PPCStart completes, our data buffer should
    have the location of the INIT's public globals.
 
------------------------------------------------------------------------------*/
 
void    GetCommonStorage(CommonGlobalsPtr *commonGlobals)
{
    OSErr               err;
    SessionRecord       sessionRecord;
    short               refNum;
 
    *commonGlobals = nil;
 
    err = OpenAPort(&refNum);
    if (err) return;
 
    sessionRecord.portName.nameScript = GetScriptManagerVariable(smSysScript);
    (void) PLstrcpy(sessionRecord.portName.name, "\pBG Beeper");
    sessionRecord.portName.portKindSelector = ppcByCreatorAndType;
#if GENERATING68K
    // Universal Interfaces 2.0
    sessionRecord.portName.u.port.portCreator = kCreator;
    sessionRecord.portName.u.port.portType = 'INIT';
#else
    sessionRecord.portName.u.port.creator = kCreator;
    sessionRecord.portName.u.port.type = 'INIT';
#endif
    sessionRecord.pb.startParam.ioCompletion    = (PPCCompProcPtr) StartCompProc;
    sessionRecord.pb.startParam.portRefNum      = refNum;
    sessionRecord.pb.startParam.serviceType     = ppcServiceRealTime;
    sessionRecord.pb.startParam.resFlag         = 0;
    sessionRecord.pb.startParam.portName        = &sessionRecord.portName;
    sessionRecord.pb.startParam.locationName    = nil;
    sessionRecord.pb.startParam.userData        = kGetCommonGlobalsPtr;
    sessionRecord.pb.startParam.userRefNum      = 0;    /* guest access? */
 
    err = PPCStartAsync(&sessionRecord.pb.startParam);
 
    /*  Even though we make an asynchronous call, we have to wait
        around for the call to complete. One reason for this is because
        we can't really do anything until we get the information we are
        asking for from the INIT. Another good reason is because our
        parameter block is allocated on the stack. If we were to leave
        now, our parameter block would vanish in a puff of orange smoke,
        along with the rest of the Operating System... */
 
    while (sessionRecord.pb.startParam.ioResult == 1) ;     /* wait for the reply */
 
    if (sessionRecord.pb.startParam.ioResult == noErr)
        *commonGlobals =
            ((GetCommonGlobalsPtr) &sessionRecord.buffer)->commonGlobalsAddress;
 
    err = PPCEnd(&sessionRecord.pb.endParam, false);
    err = PPCClose(&sessionRecord.pb.closeParam, false);
}
 
 
/*------------------------------------------------------------------------------
 
    OSErr OpenAPort(short *refNum)
 
    Used to open a PPC port. We identify ourselves using the portName record.
    First, we give ourselves a name that will show up in the PPCBrowser
    (portName.name). We then give ourselves an identify to other processes. We
    can do this either by name or by creator/type signatures. In our case, we
    choose creator/type ('INCD'/'CDEV').
 
    We then set up for a PPCOpen call. We are making a synchronous call, so we
    don't need a completion routine. ServiceType and resFlag are set to
    required values (per Inside Mac). We make the CDEV not visible over the
    network. Next, we point to the name record we want to use for our port,
    and use the default location name (which identifies our computer to other
    computers on the network).
 
    Finally, we make the PPCOpen call synchronously, and return any errors.
 
------------------------------------------------------------------------------*/
 
OSErr OpenAPort(short *refNum)
{
    PPCPortRec      thePort;
    PPCOpenPBRec    pb;
    OSErr           err;
 
    thePort.nameScript = GetScriptManagerVariable(smSysScript);
    (void) PLstrcpy(thePort.name, "\pBG Beeper");
    thePort.portKindSelector = ppcByCreatorAndType;
#if GENERATING68K
    // Universal Interfaces 2.0
    thePort.u.port.portCreator = kCreator;
    thePort.u.port.portType = 'CDEV';
#else
    thePort.u.port.creator = kCreator;
    thePort.u.port.type = 'CDEV';
#endif
 
    pb.ioCompletion = nil;
    pb.serviceType = ppcServiceRealTime;
    pb.resFlag = 0;
    pb.networkVisible = true;
    pb.portName = &thePort;
    pb.locationName = nil;          // use the default location
 
    err = PPCOpen(&pb, false);
    if (err) return(err);
 
    *refNum = pb.portRefNum;
    return(noErr);
}
 
 
/*------------------------------------------------------------------------------
 
    pascal void StartCompProc(PPCParamBlockPtr ppb)
 
    When the PPCStart call we made in GetCommonStorage completes, the
    PPCToolbox calls the completion routine we specified (this routine). At
    this point, our INIT has been contacted, because it had an outstanding
    PPCInform call. The INIT's PPCInform completion routine looked at what we
    stuffed in the userData field when we made the PPCStart call, and wrote
    back to us the location of its public globals with PPCWrite. Therefore, we
    have to set ourselves up to read that data with PPCRead. That's what we do
    here.
 
    Because this is a completion routine, it is declared as using the Pascal
    calling convention.
 
------------------------------------------------------------------------------*/
 
pascal void StartCompProc(PPCParamBlockPtr ppb)
{
    OSErr               err;
    GetCommonGlobalsPtr myBuffer;
 
    myBuffer = (GetCommonGlobalsPtr) ((SessionPtr)ppb)->buffer;
 
    ppb->readParam.ioCompletion = nil;
    ppb->readParam.bufferLength = sizeof(GetCommonGlobalsRecord);
    ppb->readParam.bufferPtr    = (Ptr) myBuffer;
    ppb->readParam.more         = false;
 
    err = PPCReadAsync(&ppb->readParam);
}