Src/LogManager.c

/*                                  LogManager.c                                */
/*
 * LogManager.c
 * Copyright © 1993 Apple Computer Inc. All rights reserved.
 *
 * These functions manage a logging display for error messages and other text.
 * The log is implemented as a ListManager list that can hold nLogItems. This
 * module is intentionally more-or-less self-contained so it can easily be
 * exported to other applications.
 */
#include "LogManager.h"
#ifndef THINK_C             /* MPW includes         */
#include <Errors.h>
#include <Script.h>
#include <Types.h>
#include <Files.h>
#include <Resources.h>
#include <QuickDraw.h>
#include <Fonts.h>
#include <Events.h>
#include <Windows.h>
#include <ToolUtils.h>
#include <Memory.h>
#include <Menus.h>
#include <Lists.h>
#include <Printing.h>
#include <Dialogs.h>
#include <StandardFile.h>
#endif
#include <Serial.h>
#include <Printing.h>
#include <Packages.h>
#pragma segment LogManager
 
/*
 * There is a 32000 byte maximum for the list, so don't
 * make nLogLines too big.
 */
#ifndef nDefaultLogLines
#define nDefaultLogLines    128
#endif
#define width(r)                ((r).right - (r).left)
#define height(r)               ((r).bottom - (r).top)
#ifndef TRUE
#define TRUE                    1
#define FALSE                   0
#endif
enum {
    kScrollBarWidth     = 16,
    kScrollBarOffset    = kScrollBarWidth - 1,
    kActiveControl      = 0,        /* Normal button hilite             */
    kDisabledControl    = 255       /* Disabled button hilite           */
};
/*
 * When the horizontal scrollbar is at zero, the list is indented +4 pixels.
 * When the horizontal scrollbar increases, the list indent decreases. This
 * value is "by inspection," but we could extract it from the list record
 * when the list is initially created.
 */
enum {
    kZeroIndent         = 4
};
/*
 * This sets a nominal value for the maximum cell width. This might be
 * better provided as a user-settable parameter.
 */
#define kMaxHorizontalScroll    (CharWidth('M') * 255)
static const char       endOfLine[1] = { 0x0D };    /* <CR>         */
 
/*
 * Cheap 'n dirty pascal string copy routine.
 */
#ifndef pstrcpy
#define pstrcpy(dst, src) do {                          \
        StringPtr   _src = (src);                       \
        BlockMove(_src, dst, _src[0] + 1);              \
    } while (0)
#endif
/*
 * Cheap 'n dirty pascal string concat.
 */
#ifndef pstrcat
#define pstrcat(dst, src) do {                          \
        StringPtr       _dst = (dst);                   \
        StringPtr       _src = (src);                   \
        short           _len;                           \
        _len = 255 - _dst[0];                           \
        if (_len > _src[0]) _len = _src[0];             \
        BlockMove(&_src[1], &_dst[1] + _dst[0], _len);  \
        _dst[0] += _len;                                \
    } while (0)
#endif
 
static OSErr                        AddStringToList(
        ListHandle                      logListHandle,
        ConstStr255Param                theString
    );
static pascal void                  ScrollLogAction(
        register ControlHandle          theControl,
        short                           partcode
    );
static void                         ScrollLogList(
        ControlHandle                   theControl
    );
static StringHandle                 GetLogStringHandle(
        ListHandle                      logListHandle,
        short                           theRow
    );
 
#define LIST            (**logListHandle)
#define LOGINFO         (**((LogInfoHdl) (LIST.userHandle)))
#define HSCROLL         (LOGINFO.hScroll)
#define IS_COLOR(port)  (((((CGrafPtr) (port))->portVersion) & 0xC000) != 0)
#define COLOR_LIST      (IS_COLOR(LIST.port))
 
/*
 * This code sequence is copied into the ListProc handle. It is designed so we
 * don't have to flush the instruction and data caches. Note that this must
 * be revised if you compile for a non-68000 environment.
 */
static const short gDummyLDEF[] = {
        0x207A,             /*      movea.l procPtr,a0          */
        0x0004,             /*              <offset to procPtr> */
        0x4ED0,             /*      jmp     a0                  */
        0x0000, 0x0000      /*      dc.l    <Drawing Proc here> */
};
 
static pascal void
LogListDefProc(
        short                   listMessage,
        Boolean                 listSelect,
        Rect                    *listRect,
        Cell                    listCell,
        short                   listDataOffset,
        short                   listDataLen,
        ListHandle              errorLogList
    );
 
/*
 * Create the data display list.
 */
ListHandle
CreateLog(
        const Rect                  *viewRect,
        short                       listFontNumber,
        short                       listFontSize,
        short                       logLines
    )
{
        OSErr                       status;
        ListHandle                  logListHandle;
        FontInfo                    info;
        Point                       cellSize;
        short                       listHeight;
        Rect                        dataBounds;
        Rect                        listRect;
        short                       listFontHeight;
        Handle                      drawProcHdl;
        LogInfoRecord               logInfo;
        Handle                      logInfoHdl;
        ProcPtr                     listProcPtr;
        short                       horizontalMax;
        
        logInfoHdl = NULL;
        drawProcHdl = NULL;
        if (logLines == 0)
            logLines = nDefaultLogLines;
        TextFont(listFontNumber);
        TextSize(listFontSize);
        GetFontInfo(&info);
        listFontHeight = info.ascent + info.descent + info.leading;
        /*
         * Compute the list drawing area, adjusting the list
         * area height so an integral number of lines will be drawn. As with
         * the standard list manager, the scroll bars will be drawn outside
         * of the list rectangle.
         */
        listRect = *viewRect;
        /*
         * Normalize the list area so that an integral number of cells are visible
         * on the display and define the visual shape of each cell.
         */
        listHeight = height(listRect);
        listHeight -= (listHeight % listFontHeight);
        listRect.bottom = listRect.top + listHeight;
        SetPt(&cellSize, width(listRect), listFontHeight);
        /*
         * Note: we create a one-column list with both vertical and horizontal
         * scrollbars, then we steal the horizontal scrollbar because we're
         * scrolling within the list cell. Unfortunately, the List Manager only
         * scrolls from one cell (column in the horizontal direction) to another.
         */
        SetRect(&dataBounds, 0, 0, 1, 0);
        logListHandle = LNew(
                &listRect,                          /* Viewing area             */
                &dataBounds,                        /* Rows and col's           */
                cellSize,                           /* Element size             */
                0,                                  /* No defProc yet           */
                qd.thePort,                         /* Display window           */
                TRUE,                               /* Draw it                  */
                FALSE,                              /* No grow box              */
                TRUE,                               /* Has horizontal scroll    */
                TRUE                                /* Has vertical scroll      */
            );
        if (logListHandle == NULL)
            goto failure;
        LIST.selFlags = lOnlyOne;
        LIST.listFlags = lDoVAutoscroll;            /* Vertical autoscroll only */
        logInfo.logFileRefNum = 0;
        logInfo.logFileVRefNum = 0;
        logInfo.logFileStatus = 0;
        logInfo.logLines = logLines;
        logInfo.fontNumber = listFontNumber;
        logInfo.fontSize = listFontSize;
        if (COLOR_LIST) {
            GetForeColor(&logInfo.foreColor);
            GetBackColor(&logInfo.backColor);
        }
        status = PtrToHand(&logInfo, &logInfoHdl, sizeof logInfo);
        if (status != noErr)
            goto failure;
        LIST.userHandle = (Handle) logInfoHdl;
        HSCROLL = LIST.hScroll;                     /* Grab horizontal scroller */
        LIST.hScroll = NULL;                        /* Remove it from the list  */
        SetCRefCon(HSCROLL, (long) logListHandle);  /* Link scrollbar to list   */
        status = PtrToHand(gDummyLDEF, &drawProcHdl, sizeof gDummyLDEF);
        if (status != noErr)
            goto failure;
        listProcPtr = (ProcPtr) LogListDefProc;
        BlockMove(&listProcPtr, &((short *) *drawProcHdl)[3], sizeof listProcPtr);
        LIST.listDefProc = drawProcHdl;
        horizontalMax = kMaxHorizontalScroll - width((**HSCROLL).contrlRect);
        if (horizontalMax < 0)
            horizontalMax = 0;
        SetCtlMin(HSCROLL, 0);
        SetCtlMax(HSCROLL, horizontalMax);
        SetCtlValue(HSCROLL, 0);
        HiliteControl(
            HSCROLL,
            (horizontalMax == 0) ? kDisabledControl : kActiveControl
        );
        goto success;
failure:
        if (drawProcHdl != NULL) {
            DisposeHandle((Handle) drawProcHdl);
            LIST.listDefProc = NULL;
        }
        DisposeLog(logListHandle);
        logListHandle = NULL;
success:
        return (logListHandle);
}
 
/*
 * DisposeLog disposes of our private information and then disposes of the list.
 */
void
DisposeLog(
        ListHandle                      logListHandle
    )
{
        if (logListHandle != NULL) {
            if (LIST.userHandle != NULL) {
                LIST.hScroll = HSCROLL;         /* The list manager disposes    */ 
                DisposeHandle(LIST.userHandle);
                LIST.userHandle = NULL;
            }
            LDispose(logListHandle);
        }
}
 
/*
 * UpdateLogWindow explicitly updates the log's window. It is generally called only
 * after Modal Dialogs or Alerts obscured the window.
 */
void
UpdateLogWindow(
        ListHandle                      logListHandle
    )
{
        WindowPtr                       theWindow;
        GrafPtr                         savePort;
        Rect                            viewRect;
        RgnHandle                       listRgn;
        RgnHandle                       clipRgn;
        
        if (logListHandle != NULL) {
            theWindow = (WindowPtr) LIST.port;
            if (EmptyRgn(((WindowPeek) theWindow)->updateRgn) == FALSE) {
                viewRect = LIST.rView;
                if (LIST.hScroll != NULL)
                    viewRect.bottom += kScrollBarWidth;
                if (LIST.hScroll != NULL)
                    viewRect.right += kScrollBarWidth;
                listRgn = NewRgn();
                RectRgn(listRgn, &viewRect);
                SectRgn(listRgn, ((WindowPeek) theWindow)->updateRgn, listRgn);
                if (EmptyRgn(listRgn) == FALSE) {
                    /*
                     * We have something to redraw. Fake an update event handler.
                     */
                    GetPort(&savePort);
                    SetPort(theWindow);
                    clipRgn = NewRgn();
                    GetClip(clipRgn);
                    SetClip(listRgn);
                    EraseRgn(listRgn);
                    UpdateLog(logListHandle);
                    SetClip(clipRgn);
                    DisposeRgn(clipRgn);
                    ValidRgn(listRgn);
                    SetPort(savePort);
                }
                DisposeRgn(listRgn);
            }
        }
}
 
/*
 * UpdateLog redraws the list.
 */
void
UpdateLog(
        ListHandle                      logListHandle
    )
{
        Rect                        viewRect;
        RGBColor                    saveForeColor;
        RGBColor                    saveBackColor;
        
        if (logListHandle != NULL) {
            /*
             * Make sure the list is locked down while we draw. Note that we
             * assume that the application has called UpdateControls, so
             * the horizontal scrollbar is correctly drawn.
             */     
            if (COLOR_LIST) {
                GetForeColor(&saveForeColor);
                GetBackColor(&saveBackColor);
                RGBForeColor(&LOGINFO.foreColor);
                RGBBackColor(&LOGINFO.backColor);
            }
            viewRect = LIST.rView;
            /*
             * Include the scrollbars in the frame.
             */
            EraseRect(&viewRect);
            InsetRect(&viewRect, -1, -1);
            viewRect.right += kScrollBarOffset;
            viewRect.bottom += kScrollBarOffset;
            FrameRect(&viewRect);
            LUpdate(LIST.port->visRgn, logListHandle);
            if (COLOR_LIST) {
                RGBForeColor(&saveForeColor);
                RGBBackColor(&saveBackColor);
            }
        }
}
 
/*
 * ActivateLog activates (or deactivates) the log. Call it on activate and
 * suspendResume events.
 */
void
ActivateLog(
        ListHandle                      logListHandle,
        Boolean                         activating
    )
{
        if (logListHandle != NULL) {
            LActivate(activating, logListHandle);
            HiliteControl(
                HSCROLL,
                (activating) ? kActiveControl : kDisabledControl);
        }
}
 
/*
 * MoveLog Repositions the log list area.
 */
void
MoveLog(
        ListHandle                      logListHandle,
        short                           leftEdge,
        short                           topEdge
    )
{
        Rect                            viewRect;
        
        if (logListHandle != NULL) {
            if (LIST.rView.left != leftEdge || LIST.rView.top != topEdge) {
                viewRect = LIST.rView;
                InsetRect(&viewRect, -1, -1);
                InvalRect(&viewRect);
                OffsetRect(
                    &LIST.rView,
                    leftEdge - LIST.rView.left,
                    topEdge -LIST.rView.top
                );
                viewRect = LIST.rView;
                InsetRect(&viewRect, -1, -1);
                InvalRect(&viewRect);
                MoveControl(
                    LIST.vScroll,
                    LIST.rView.right - kScrollBarOffset,
                    LIST.rView.top - 1
                );
                MoveControl(
                    HSCROLL,
                    LIST.rView.left - 1,
                    LIST.rView.bottom - kScrollBarOffset
                );
            }
        }
}
 
/*
 * SizeLog: this is the list rectangle size and does not include the
 * horizontal and vertical scrollbars.
 */
void
SizeLog(
        ListHandle                      logListHandle,
        short                           newWidth,
        short                           newHeight
    )
{
        Rect                            viewRect;
        Point                           cellSize;
        short                           horizontalMax;
        
        if (logListHandle != NULL) {
            viewRect = LIST.rView;
            InsetRect(&viewRect, -1, -1);
            InvalRect(&viewRect);
            /*
             * We put the horizontal scrollbar back into the list record so that
             * the list manager kindly resizes it. Then we take it out again.
             */
            LIST.hScroll = HSCROLL;
            LSize(newWidth, newHeight, logListHandle);
            LIST.hScroll = NULL;
            cellSize = LIST.cellSize;
            cellSize.h = width(LIST.rView);
            LCellSize(cellSize, logListHandle);
            horizontalMax = kMaxHorizontalScroll - width((**HSCROLL).contrlRect);
            if (horizontalMax < 0)
                horizontalMax = 0;
            SetCtlMax(HSCROLL, horizontalMax);
            HiliteControl(
                HSCROLL,
                (horizontalMax == 0) ? kDisabledControl : kActiveControl
            );
            viewRect = LIST.rView;
            InsetRect(&viewRect, -1, -1);
            InvalRect(&viewRect);
        }
}
 
/*
 * DoClickInLog: call this when there is a mouse down in the log area (or in
 * one of the scrollbars.
 */
Boolean
DoClickInLog(
        ListHandle                      logListHandle,
        const EventRecord               *eventRecord
    )
{
#define EVENT   (*eventRecord)
 
        Point                           mousePt;
        Boolean                         result;
        Rect                            viewRect;
        short                           part;
        ControlHandle                   theControl;
 
        if (logListHandle == NULL)
            result = FALSE;
        else {
            mousePt = EVENT.where;
            GlobalToLocal(&mousePt);
            /*
             * Handle clicks in the horizontal scrollbar: Do not pass them through
             * LClick, as it does not do what we want and besides, the scrollbar
             * isn't there any more.
             */
            if (PtInRect(mousePt, &(**HSCROLL).contrlRect)) {
                part = FindControl(mousePt, (**HSCROLL).contrlOwner, &theControl);
                if (part >= 0 && theControl == HSCROLL) {
                    if (part == inThumb) {
                        if (TrackControl(theControl, mousePt, NULL))
                            ScrollLogList(theControl);
                    }
                    else {
                        TrackControl(
                            theControl,
                            mousePt,
                            NewControlActionProc(ScrollLogAction)
                        );
                    }
                }
            }
            else {
                viewRect = LIST.rView;
                viewRect.right += kScrollBarOffset;
                result = PtInRect(mousePt, &viewRect);
                if (result)
                    (void) LClick(mousePt, EVENT.modifiers, logListHandle);
            }
        }
        return (result);
#undef EVENT
}
 
/*
 * Log errors.
 */
void
LogStatus(
        ListHandle                      logListHandle,
        OSErr                           theError,
        const StringPtr                 infoText
    )
{
        Handle                          macErrorHdl;
        Str255                          msg;
        Str15                           errorValue;
        
        if (logListHandle != NULL && theError != noErr) {
            pstrcpy(msg, infoText);
            pstrcat(msg, "\p: ");
            NumToString(theError, errorValue);
            pstrcat(msg, errorValue);
            macErrorHdl = GetResource('Estr', theError);
            if (macErrorHdl != NULL) {
                pstrcat(msg, "\p ");
                pstrcat(msg, (StringPtr) *macErrorHdl);
                ReleaseResource(macErrorHdl);
            }
            DisplayLogString(logListHandle, msg);
        }
}
 
/*
 * DisplayLogString
 * Call this function to store a string in the list.
 */
void
DisplayLogString(
        ListHandle                      logListHandle,
        const StringPtr                 theString
    )
{
        short                       theRow;
        Boolean                     scrollAtBottom;
        
        if (logListHandle != NULL) {
            /*
             * If there are already logLines in the
             * list, delete the first row of the list.
             * Then, in any case, append this datum at the
             * bottom.
             *
             * The scroll bars are managed as follows:
             * scroll bar is at the bottom, the new datum
             * is selected and autoscrolled into view.
             * Otherwise, the current cell is unchanged.
             */
            theRow = LIST.dataBounds.bottom;
            scrollAtBottom =
                (GetCtlValue(LIST.vScroll) == GetCtlMax(LIST.vScroll));
            if (theRow >= LOGINFO.logLines)
                LDelRow(1, 0, logListHandle);
            if (AddStringToList(logListHandle, theString) == noErr) {
                if (scrollAtBottom)
                    LScroll(0, LIST.dataBounds.bottom - GetCtlValue(LIST.vScroll), logListHandle);
                if (LOGINFO.logFileRefNum != 0 && LOGINFO.logFileStatus == noErr)
                    WriteLogLine(logListHandle, theString);
            }
        }
failure:
        return;
}
 
static OSErr
AddStringToList(
        ListHandle                      logListHandle,
        ConstStr255Param                theString
    )
{
        StringHandle                    stringHandle;
        Cell                            theCell;
        
        stringHandle = NewString(theString);
        if (stringHandle != NULL) {
            theCell.h = 0;
            theCell.v = LAddRow(1, LIST.dataBounds.bottom, logListHandle);
            if (MemError() == noErr)
                LSetCell(&stringHandle, sizeof stringHandle, theCell, logListHandle);
        }
        return (MemError());
}
 
/*
 * ScrollLogAction is called by the Toolbox while executing the TrackControl
 * routine.  It has to take care of scrolling the log when the user clicks on the
 * up/down arrow or page parts of the scroll bar.
 */
static pascal void
ScrollLogAction(
        register ControlHandle          theControl,
        short                           partcode
    )
{
        short                           delta;
        
        delta = (width((**theControl).contrlRect) * 7) / 8;
        switch (partcode) {
        case inUpButton:    delta = -CharWidth('M');    break;
        case inPageUp:      delta = -(delta);           break;      
        case inDownButton:  delta = CharWidth('M');     break;
        case inPageDown:    /* All set */               break;
        default:            return;     /* Mouse exited control */
        }
        SetCtlValue(theControl, GetCtlValue(theControl) + delta);
        ScrollLogList(theControl);
}
 
/*
 * ScrollLogList scrolls the list rectangle in the proper direction and updates
 * the list's horizontal indentation to match.
 */
static void
ScrollLogList(
        ControlHandle                   theControl
    )
{
        ListHandle                      logListHandle;
        short                           delta;
        RgnHandle                       clipRgn;
        RgnHandle                       updateRgn;
        Rect                            viewRect;
        
        logListHandle = (ListHandle) GetCRefCon(theControl);
        /*
         * LIST.indent.h is negative when the cell is scrolled left. Get the
         * amount it's currently scrolled (as a positive value) and set delta
         * to the amount that must be scrolled. Delta will be positive to
         * scroll right (which means that the scrollbar has moved left).
         */
        delta = kZeroIndent - LIST.indent.h - GetCtlValue(theControl);
        if (delta != 0) {
            /*
             * We need to scroll the list cells. Get a clip rectangle so the
             * scrolling is limited to the drawing area, scroll it, and update
             * the stuff that came into view.
             */
            viewRect = LIST.rView;
            clipRgn = NewRgn();
            updateRgn = NewRgn();
            GetClip(clipRgn);
            ClipRect(&viewRect);
            ScrollRect(&viewRect, delta, 0, updateRgn);
            LIST.indent.h += delta;
            LUpdate(updateRgn, logListHandle);
            SetClip(clipRgn);
            DisposeRgn(updateRgn);
            DisposeRgn(clipRgn);
        }
}
        
/*
 * Draw the string stored in the list. The only difference between this function
 * and a "normal" LDEF is that we don't visually indicate selection.
 */
static pascal void
LogListDefProc(
        short                           listMessage,
        Boolean                         listSelect,
        Rect                            *listRect,
        Cell                            listCell,
        short                           listDataOffset,
        short                           listDataLen,
        ListHandle                      logListHandle
    )
{
        char                            stringLockState;
        RGBColor                        saveForeColor;
        RGBColor                        saveBackColor;
        StringHandle                    stringHandle;
        
        if (0) {
            listCell;
        }
        /*
         * If the userHandle isn't setup, do nothing: this is an initialization
         * or a spurious command while we're disposing of the list.
         */
        if (LIST.userHandle != NULL) {
            TextFont(LOGINFO.fontNumber);
            TextSize(LOGINFO.fontSize);
            if (COLOR_LIST) {
                GetForeColor(&saveForeColor);
                GetBackColor(&saveBackColor);
                RGBForeColor(&LOGINFO.foreColor);
                RGBBackColor(&LOGINFO.backColor);
            }
            switch (listMessage) {
            case lInitMsg:
                break;
            case lDrawMsg:
                EraseRect(listRect);
                if (listDataLen == sizeof stringHandle) {
                    BlockMove(
                        (*LIST.cells) + listDataOffset,
                        &stringHandle,
                        sizeof stringHandle
                    );
                    /*
                     * We don't indent in the vertical direction: by default,
                     * it contains the font ascent which is fine for DrawText
                     */
                    stringLockState = HGetState((Handle) stringHandle);
                    HLock((Handle) stringHandle);
                    MoveTo(
                        listRect->left + LIST.indent.h,
                        listRect->top + LIST.indent.v
                    );
                    DrawString(*stringHandle);
                    HSetState((Handle) stringHandle, stringLockState);
                }
                if (listSelect == FALSE)
                    break;
                /* Continue to do hilite */
            case lHiliteMsg:
#if 0       /* Hiliting is disabled */
#ifdef THINK_C
                HiliteMode &= ~(1 << hiliteBit);
#else /* MPW */
                *((char *) HiliteMode) &= ~(1 << hiliteBit); /* Inside Mac V-61 */
#endif
                InvertRect(listRect);
#endif
                break;
            }
            if (COLOR_LIST) {
                RGBForeColor(&saveForeColor);
                RGBBackColor(&saveBackColor);
            }
        }
}
 
StringHandle
GetLogStringHandle(
        ListHandle                  logListHandle,
        short                       theRow
    )
{
        Cell                        theCell;
        StringHandle                result;
        short                       dataLength;
        
        dataLength = sizeof result;
        theCell.h = 0;
        theCell.v = theRow;
        LGetCell(&result, &dataLength, theCell, logListHandle);
        if (dataLength != sizeof result)
            result = NULL;
        return (result);
}
 
/*
 * Prompt the user for a file name and write the current log contents
 * to the chosen file. Returns an error status (noErr is ok). This function
 * returns userCanceledErr if the user cancelled the SFPutFile dialog.
 */
OSErr
SaveLogFile(
        ListHandle                      logListHandle,
        ConstStr255Param                dialogPromptString,
        ConstStr255Param                defaultFileName,
        OSType                          creatorType
    )
{
        Point                           where;
        SFReply                         reply;
        DialogTHndl                     dialog;
        Rect                            box;
        OSErr                           status;
        
        /*
         * Center the dialog
         */
        dialog = (DialogTHndl) GetResource('DLOG', putDlgID);
        if (dialog == NULL)                         /* No such dialog!  */
            SetRect(&box, 0, 0, 0, 0);              /* Center on screen */
        else {
            box = (*dialog)->boundsRect;            /* Dialog shape     */
            ReleaseResource((Handle) dialog);       /* Done with dialog */
        }
        SetPt(&where,
            (width(qd.thePort->portRect) - width(box)) / 2,
            ((height(qd.thePort->portRect) - GetMBarHeight()) / 3)
                + GetMBarHeight()
        );
        SFPutFile(where, dialogPromptString, defaultFileName, NULL, &reply);
        if (reply.good == FALSE)
            status = userCanceledErr;
        else {
            SetCursor(*GetCursor(watchCursor));
            status = CreateLogFile(
                        logListHandle,
                        creatorType,
                        reply.fName,
                        reply.vRefNum
                    );
            InitCursor();
            if (status != noErr)
                (void) FSDelete(reply.fName, reply.vRefNum);
        }
        return (status);
}
 
/*
 * Create a log on the specified file and volume. Normally, called only
 * by SaveLogFile.
 */
OSErr
CreateLogFile(
        ListHandle                      logListHandle,
        OSType                          creatorType,
        ConstStr255Param                fileName,
        short                           vRefNum
    )
{
        OSErr                           status;
        short                           refNum;
        
        /*
         * Create the file, elmininating any duplicate.
         */
        if (creatorType == 0)
            creatorType = 'ttxt';                   /* TeachText            */
        status = Create(fileName, vRefNum, creatorType, 'TEXT');
        if (status == dupFNErr) {                   /* Exists already?      */
            status = FSDelete(fileName, vRefNum);
            if (status == noErr)
                status = Create(fileName, vRefNum, creatorType, 'TEXT');
        }
        if (status == noErr)
            status = FSOpen(fileName, vRefNum, &refNum);
        if (status == noErr) {
            LOGINFO.logFileRefNum = refNum;
            LOGINFO.logFileVRefNum = vRefNum;
            LOGINFO.logFileStatus = noErr;
            WriteCurrentLog(logListHandle);
            status = LOGINFO.logFileStatus;
        }
        if (status != noErr) {
            (void) CloseLogFile(logListHandle);
            LogStatus(logListHandle, status, "\pCreateLogFile failed");
        }
        return (status);
}
 
/*
 * Write the current contents of the log file. Any error will be
 * in LOGINFO.logFileStatus.
 */
void
WriteCurrentLog(
        ListHandle                      logListHandle
    )
{
        short                           theRow;
        long                            fileLength;
        StringHandle                    stringHandle;
        char                            stringLockState;
 
        
        theRow = LIST.dataBounds.top;
        while (LOGINFO.logFileStatus == noErr && theRow < LIST.dataBounds.bottom) {
            stringHandle = GetLogStringHandle(logListHandle, theRow);
            if (stringHandle != NULL) {
                stringLockState = HGetState((Handle) stringHandle);
                HLock((Handle) stringHandle);
                fileLength = (*stringHandle)[0];
                LOGINFO.logFileStatus = FSWrite(
                    LOGINFO.logFileRefNum,
                    &fileLength,
                    &(*stringHandle)[1]
                );
                HSetState((Handle) stringHandle, stringLockState);
                if (LOGINFO.logFileStatus == noErr) {
                    fileLength = 1;
                    LOGINFO.logFileStatus = FSWrite(
                                LOGINFO.logFileRefNum,
                                &fileLength,
                                endOfLine
                            );
                }
            }
            ++theRow;
        };
}       
 
void
WriteLogLine(
        ListHandle                      logListHandle,
        ConstStr255Param                theText
    )
{
        long                            fileLength;
        
        if (LOGINFO.logFileRefNum != 0 && LOGINFO.logFileStatus == noErr) {
            fileLength = theText[0];
            LOGINFO.logFileStatus = FSWrite(
                        LOGINFO.logFileRefNum,
                        &fileLength,
                        &theText[1]
                    );
            if (LOGINFO.logFileStatus == noErr) {
                fileLength = 1;
                LOGINFO.logFileStatus = FSWrite(
                            LOGINFO.logFileRefNum,
                            &fileLength,
                            endOfLine
                        );
            }
            if (LOGINFO.logFileStatus != noErr) {
                CloseLogFile(logListHandle);
                LogStatus(
                    logListHandle,
                    LOGINFO.logFileStatus,
                    "\pCan't write to log file"
                );
            }
        }
}
 
OSErr
CloseLogFile(
        ListHandle                      logListHandle
    )
{
        OSErr                           status;
        
        status = noErr;
        if (LOGINFO.logFileRefNum != 0) {
            status = FSClose(LOGINFO.logFileRefNum);
            if (status == noErr)
                status = FlushVol(NULL, LOGINFO.logFileVRefNum);
        }
        LOGINFO.logFileRefNum = 0;
        LOGINFO.logFileVRefNum = 0;
        if (status != noErr)
            LogStatus(logListHandle, status, "\pCan't close log file");
        if (LOGINFO.logFileStatus == noErr)
            LOGINFO.logFileStatus = status;
        status = LOGINFO.logFileStatus;
        LOGINFO.logFileStatus = noErr;
        return (status);
}
 
OSErr
GetLogFileError(
        ListHandle                      logListHandle
    )
{
        return (LOGINFO.logFileStatus);
}
 
Boolean
HasLogFile(
        ListHandle                      logListHandle
    )
{
        return (LOGINFO.logFileRefNum != 0);
}
 
/*
 * Copyright © 1993, Apple Computer Inc. All Rights reserved.
 *
 * This started life as a generic print driver written by Rich Siegel and posted
 * to Usenet in around 1988 or so. It has been extensively rewritten, but not
 * necessarily improved.
 */
 
/*
 *      OSErr
 *      PrintDriver(
 *              THPrint         hPrint,
 *              void            *clientData,
 *              Boolean         doStyle,
 *              OSErr           (*userPrepProc)(
 *                                      THPrnt      hPrint,
 *                                      void        *clientData
 *                                  ),
 *              OSErr           (*userPageProc)(
 *                                      THPrnt      hPrint,
 *                                      void        *clientData,
 *                                      const Rect  *pageRect,
 *                                      short       pageNum
 *                                  )
 *          );
 *      hPrint      A Print Manager handle.  If NULL, ReportPrintDriver
 *                  will allocate a handle, call the Page Setup dialog,
 *                  and dispose of the handle on exit.
 *      clientData  A longword parameter that becomes a parameter to the
 *                  prepProc and pageProc functions. For the LogManager,
 *                  it is the ListHandle.
 *      doStyle     If TRUE, the page setup modal dialog is always called.
 *                  Otherwise, it's called only if the print record is invalid.
 *
 *      userPrepProc A user-provided function that will perform print setup
 *                  initializations.  It must be defined
 *                      OSErr           UserPrepProc(
 *                              THPrint     hPrint,
 *                              void        *clientData
 *                          );
 *                  UserPrepProc returns noErr on success or an error status that
 *                  will  be returned to the PrintDriver caller on failure.
 *                  UserPrepProc must set (**hPrint).prJob.iLstPage  to the correct
 *                  number of pages in the document. If all pages are specified
 *                  by the user dialog, this field will be 999 when UserPrepProc
 *                  is called; it must then be re-set to the correct value.
 *      userPageProc A user-provided function that draws the page.  It is defined:
 *                      OSErr           UserPageProc(
 *                              THPrint     hPrint,
 *                              void        *clientData,
 *                              Rect        *pageRect,
 *                              short       pageNum
 *                      );
 *                  UserPageProc returns noErr if it completed correctly, or an
 *                  error code that will be returned to the user.  It should
 *                  return iPrAbort on user-specified aborts.
 */
 
/*
 * Note: the serial stuff has not been tested in several years.
 */
#ifndef bDevCItoh
#define bDevCItoh       1               /* ImageWriter              */
#endif
#ifndef bDevLaser
#define bDevLaser       3               /* LaserWriter              */
#endif
#define ImageWriter     (bDevCItoh)
#define LaserWriter     (bDevLaser)
 
OSErr                               PrintLog(
        ListHandle                      logListHandle,
        THPrint                         hPrint
    );
OSErr                               LogPrepProc(
        THPrint                         hPrint,
        void                            *clientData
    );
OSErr                               LogPageProc(
        THPrint                         hPrint,
        void                            *clientData,
        const Rect                      *pageRect,
        short                           pageNumber
    );
OSErr                               PrintDriver(
        THPrint                         hPrint,
        Boolean                         doStyleDialog,
        void                            *clientData,
        OSErr                           (*userPrepProc)(
                        THPrint     hPrint,
                        void        *clientData
                    ),
        OSErr                           (*userPageProc)(
                        THPrint     hPrint,
                        void        *clientData,
                        const Rect  *pageRect,
                        short       pageNumber
                    )
    );
 
/*
 * PrintLog, LogPrepProc, and LogPageProc are specific to the LogManager.
 */
 
OSErr
PrintLog(
        ListHandle                      logListHandle,
        THPrint                         hPrint
    )
{
        OSErr           status;
        
        status = PrintDriver(
                    hPrint,
                    FALSE,
                    logListHandle,
                    LogPrepProc,
                    LogPageProc
                );
        return (status);
}
 
OSErr
LogPrepProc(
        THPrint                     hPrint,
        void                        *clientData
    )
{
        unsigned short              linesInLog;
        unsigned short              linesPerPage;
        unsigned short              pagesInLog;
        Rect                        printRect;
#define logListHandle   ((ListHandle) clientData)
 
        /*
         * We could add a page header here, of course.
         */
        printRect = (**hPrint).prInfo.rPage;
        linesInLog = height(LIST.dataBounds);
        linesPerPage = height(printRect) / LIST.cellSize.v;
        pagesInLog = (linesInLog + linesPerPage - 1) / linesPerPage;
        (**hPrint).prJob.iLstPage = pagesInLog;
        return (noErr);
#undef logListHandle
}
 
OSErr
LogPageProc(
        THPrint                         hPrint,
        void                            *clientData,
        const Rect                      *pageRect,
        short                           pageNumber
    )
{
        unsigned short                  linesPerPage;
        short                           lastCell;
        Point                           drawLoc;
        FontInfo                        info;
        short                           theRow;
        StringHandle                    stringHandle;
        char                            stringLockState;
#define logListHandle   ((ListHandle) clientData)
 
        if (0) {
            hPrint;
        }
        linesPerPage = height(*pageRect) / LIST.cellSize.v;
        TextFont(LOGINFO.fontNumber);
        TextSize(LOGINFO.fontSize);
        GetFontInfo(&info);
        theRow = (pageNumber - 1) * linesPerPage + LIST.dataBounds.top;
        lastCell = theRow + linesPerPage - 1;
        if (lastCell > LIST.dataBounds.bottom)
            lastCell = LIST.dataBounds.bottom;
        SetPt(
            &drawLoc,
            LIST.indent.h,
            pageRect->top + info.ascent
        );
        /*
         * We could add a page header here, of course.
         */
        for (; theRow < lastCell; ++theRow) {
            stringHandle = GetLogStringHandle(logListHandle, theRow);
            if (stringHandle != NULL) {
                stringLockState = HGetState((Handle) stringHandle);
                HLock((Handle) stringHandle);
                MoveTo(drawLoc.h, drawLoc.v);
                DrawString(*stringHandle);
                HSetState((Handle) stringHandle, stringLockState);
            }
            drawLoc.v += LIST.cellSize.v;
        };
        return (PrError());
}
 
/*
 * Rich Siegel's generic printer driver, somewhat modified.
 */
OSErr
PrintDriver(
        THPrint                         hPrint,
        Boolean                         doStyleDialog,
        void                            *clientData,
        OSErr                           (*userPrepProc)(
                        THPrint     hPrint,
                        void        *clientData
                    ),
        OSErr                           (*userPageProc)(
                        THPrint     hPrint,
                        void        *clientData,
                        const Rect  *pageRect,
                        short       pageNumber
                    )
    )
{
        OSErr               status;             /* Random status                */
        Boolean             ourPrintHandle;     /* Did we allocate hPrint       */
        Boolean             printIsOpen;        /* PROpen-PRClose               */      
        Boolean             docIsOpen;          /* PROpenDoc-PRCloseDoc         */
        Boolean             pageIsOpen;         /* PROpenPage-PRClosePage       */
        short               nCopies;            /* Number of copies             */
        short               printDevice;        /* What kind of printer         */
        Boolean             draftMode;          /* Draft or Spool?              */
        TPPrPort            printPort;          /* The print port               */
        TPrStatus           printStatus;        /* PrPicFile status info        */
        GrafPtr             savePort;           /* Old GrafPort                 */
        short               iCopy;              /* Which copy                   */
        short               page;               /* Which page                   */
        Rect                pageRect;           /* Current page image rect      */
/*
 * This macro exits the print handler on any error.
 */
#define CheckError(s)   do {                \
        if ((status = (s)) != noErr)        \
            goto exit;                      \
    } while (0);
 
        GetPort(&savePort);
        status = noErr;
        printIsOpen = FALSE;
        docIsOpen = FALSE;
        pageIsOpen = FALSE;
        ourPrintHandle = (hPrint == NULL);
        PrOpen();
        CheckError(PrError());
        printIsOpen = TRUE;
        /*
         * Allocate the print handle if necessary.  memFullErr on failure
         * it failed.
         */
        if (ourPrintHandle) {
            hPrint = (THPrint) NewHandle(sizeof (TPrint));
            if (hPrint == NULL) {
                status = MemError();
                goto exit;
            }
            PrintDefault(hPrint);
        }
        /*
         * Validate the print handle and call the Style Dialog if necessary.  If
         * the user cancels, just exit (noErr). Then call the job dialog to get
         * the number of copies. Exit (noErr) if the user Cancels. (Strictly
         * speaking, we could exit with userCanceledErr, but that just forces
         * the caller to ignore this informational status.
         */
        if (PrValidate(hPrint) || doStyleDialog) {
            if (PrStlDialog(hPrint) == FALSE)
                goto exit;
        }
        if (PrJobDialog(hPrint) == FALSE)
            goto exit;
        /*
         * Our setup is done. call the user's prep procedure and exit on errors.
         */
        SetCursor(*GetCursor(watchCursor));
        CheckError((*userPrepProc)(hPrint, clientData));
        /*
         * Grab a few interesting parameters:
         *  printDevice Which printer we're using
         *  is_draftMode    TRUE if we're in draft-mode.
         *  nCopies         We do copies if we're in draft-mode on an Imagewriter.
         *                  Otherwise, the spooler does it for us. This hasn't
         *                  been tested in years.
         */
        printDevice = ((**hPrint).prStl.wDev >> 8) & 0xFF;
        draftMode = (**hPrint).prJob.bJDocLoop == bDraftLoop;
        if (draftMode && printDevice == ImageWriter)
            nCopies = (**hPrint).prJob.iCopies;
        else {
            nCopies = 1;
        }
        /*
         * Printing begins here.  PrOpenDoc sets the port.
         */
        printPort = PrOpenDoc(hPrint, NULL, NULL);
        docIsOpen = TRUE;
        CheckError(PrError());
        for (iCopy = 1; iCopy <= nCopies; iCopy++) {
            for (page = (**hPrint).prJob.iFstPage;
                        page <= (**hPrint).prJob.iLstPage;
                        page++) {
                SetCursor(*GetCursor(watchCursor));
                /*
                 * Print the current page.
                 */
                PrOpenPage(printPort, NULL);
                pageIsOpen = TRUE;
                pageRect = (**hPrint).prInfo.rPage;
                status = (*userPageProc)(hPrint, clientData, &pageRect, page);
                PrClosePage(printPort);
                pageIsOpen = FALSE;
                CheckError(status);
            }
        }
        SetPort(savePort);
        PrCloseDoc(printPort);
        docIsOpen = FALSE;
        status = PrError();
        if (draftMode == FALSE && status == noErr) {
            PrPicFile(hPrint, NULL, NULL, NULL, &printStatus);
            status = PrError();
        }
        /*
         * Everyone exits here
         */
exit:   SetPort(savePort);
        InitCursor();
        if (pageIsOpen)
            PrClosePage(printPort);
        if (docIsOpen)
            PrCloseDoc(printPort);
        if (printIsOpen)
            PrClose();
        if (ourPrintHandle && hPrint != NULL)
            DisposHandle((Handle) hPrint);
        return (status);
}