HandyWindow.c

/*
    File: HandyWindow.c
    
    Description:
        HandyWindow.c implements the routines used for drawing the windows
    displayed by this application.
 
    Copyright:
        © Copyright 2000 Apple Computer, Inc. All rights reserved.
    
    Disclaimer:
        IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
        ("Apple") in consideration of your agreement to the following terms, and your
        use, installation, modification or redistribution of this Apple software
        constitutes acceptance of these terms.  If you do not agree with these terms,
        please do not use, install, modify or redistribute this Apple software.
 
        In consideration of your agreement to abide by the following terms, and subject
        to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
        copyrights in this original Apple software (the "Apple Software"), to use,
        reproduce, modify and redistribute the Apple Software, with or without
        modifications, in source and/or binary forms; provided that if you redistribute
        the Apple Software in its entirety and without modifications, you must retain
        this notice and the following text and disclaimers in all such redistributions of
        the Apple Software.  Neither the name, trademarks, service marks or logos of
        Apple Computer, Inc. may be used to endorse or promote products derived from the
        Apple Software without specific prior written permission from Apple.  Except as
        expressly stated in this notice, no other rights or licenses, express or implied,
        are granted by Apple herein, including but not limited to any patent rights that
        may be infringed by your derivative works or by other works in which the Apple
        Software may be incorporated.
 
        The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
        WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
        WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
        PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
        COMBINATION WITH YOUR PRODUCTS.
 
        IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
        CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
        GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
        ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
        OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
        (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
        ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
    Change History (most recent first):
        Tue, Feb 8, 2000 -- created
*/
 
 
 
 
 
 
 
#include "HandyWindow.h"
#include "SampleUtils.h"
 
#ifdef __APPLE_CC__
#include <Carbon/Carbon.h>
#else
#include <Carbon.h>
#endif
 
 
    /* constants used to define the image size. */
#define kBoxSize 80 /* size of color swatches */
#define kLineSize 2 /* pensize used to draw the boxes */
#define kPatchSize (kBoxSize + 2) /* color swatch size with border */
#define kColumnCount 16 /* number of columns of color swatches */
#define kRowCount 16 /* number of rows of color swatches */
#define kImageWidth (kPatchSize * kColumnCount) /* total width of the image */
#define kImageHeight (kPatchSize * kRowCount) /* total height of the image */
#define kTimerDisplayWidth 100 /* width of timer display area */
 
/* resource ID numbers for the STR# resource used for
    storing window title strings.*/
enum {
    kHandyWindowStrings = 128,
    kHandyWindowTitle = 1,
    kHandyWindowSlowTitle = 2
};
 
 
/* some ascii codes for special keys on the US keyboard. we use these codes
    in the HandyWindowKey for dispatching keydown events to the scrolling
    routines.  */
enum {
    SOH = 1,        /* HOME KEY*/
    EOT = 4,        /* END KEY*/
    VT = 11,        /* PAGEUP KEY*/
    FF = 12,        /* PAGEDOWN KEY*/
    FS = 28,        /* LEFT ARROW KEY */
    GS = 29,        /* RIGHT ARROW KEY */
    RS = 30,        /* UP ARROW KEY */
    US = 31     /* DOWN ARROW KEY */
};
 
 
 
/* the HandyWindowRecord type is used for storing information related
    to each HandyWindow.  We use these records to store references
    to the scroll bars, and the window's activation state.  Also, we store
    a pointer to the window in this structure.  In this sample, we use this
    as the key value for finding a given window's HandyWindowRecord record. */
typedef struct HandyWindowStruct HandyWindowRecord;
typedef HandyWindowRecord *HandyWindowPtr;
struct HandyWindowStruct {
    HandyWindowPtr prev, next; /* pointers for our list of handy window records */
    WindowPtr theWindow; /* a pointer to our handy window */
    ControlHandle hScroll, vScroll; /* the horizontal and vertical scroll bars */
    Boolean isActive; /* the window's activation state */
    Boolean useSlowDrawing; /* if true, then add delays while drawing */
};
 
 
 
/* gHWFirst and gHWLast are the first and last pointers in a
    linked list of HandyWindowRecord records. */
static HandyWindowPtr gHWFirst = NULL, gHWLast = NULL;
 
 
 
 
/* ScrollRectInBlack since the background for our image is black, it would
    be desirable that during scrolling operations the scrolled in area should
    be drawn 'black' instead of the default white colour.  To make this happen,
    se set the background colour to black before calling scroll rect.  ScrollRectInBlack
    is the same as calling ScrollRect except it scrolls in a black area rather than
    a white one. This helps avoid unnecessary white to black flickering during scrolling
    operations. */
static void ScrollRectInBlack(const Rect* r, short dh, short dv, RgnHandle updateRgn) {
    RGBColor rgbSave, rgbBlack = { 0x0000, 0x0000, 0x0000 };
        /* save the pen state and background colour */
    GetBackColor(&rgbSave);
        /* set the background colour to black */
    RGBBackColor(&rgbBlack);
        /* scroll the bits */
    ScrollRect(r, dh, dv, updateRgn);
        /* restore the background colour */
    RGBBackColor(&rgbSave);
}
 
 
 
/* FindHandyWindow iterates through the list of open handy windows
    looking for a HandyWindowRecord containing a window pointer
    matching the theWindow.  If one is found, a pointer to the
    HandyWindowRecord is returned.  If there is no HandyWindowRecord
    record for the window in the list of open handy windows, then
    NULL is returned. */
static HandyWindowPtr FindHandyWindow(WindowPtr theWindow) {
    HandyWindowPtr rover;
    for (rover = gHWFirst; rover != NULL; rover = rover->next)
        if (rover->theWindow == theWindow)
            return rover;
    return NULL;
}
 
 
 
/* NewHandyWindowRec allocates a new HandyWindowRecord record,
    adds it to the list of open handy windows, and returns a pointer
    to it.  If there is not enough memory to complete the allocation
    request, this routine will return NULL. */
static HandyWindowPtr NewHandyWindowRec(WindowPtr theWindow) {
    HandyWindowPtr local;
    local = (HandyWindowPtr) NewPtr(sizeof(HandyWindowRecord));
    if (local == NULL)
        return NULL;
    else {
        local->theWindow = theWindow;
        local->prev = local->next = NULL;
        if (gHWFirst == NULL) {
            gHWFirst = gHWLast = local;
        } else {
            local->next = gHWFirst;
            gHWFirst->prev = local;
            gHWFirst = local;
        }
    }
    return local;
}
 
 
 
/* DisposeHandyWindowRec removes the HandyWindowRecord
    from the linked list of open handy windows and deallocates
    the storage it occupies. */
static void DisposeHandyWindowRec(HandyWindowPtr hwRec) {
    if (hwRec->next != NULL)
        hwRec->next->prev = hwRec->prev;
    else gHWLast = hwRec->prev;
    if (hwRec->prev != NULL)
        hwRec->prev->next = hwRec->next;
    else gHWFirst = hwRec->next;
    DisposePtr((Ptr) hwRec);
}
 
 
 
/* DrawTimer draws the time tickcount in the bottom left corner of the window.
    This little timer display allows you to monitor redraw times during
    scrolling and update operations.  As you will notice, the strategy of 
    only redrawing squares that will actually appear on the screen greatly
    reduces the amount of processor time spent in the drawing routine during
    scrolling  and update operations.
    
    To draw the entire image contains 256 squares that would require
    a total of 256 ticks (4:16 Seconds) to draw when slow drawing
    is turned on.  As you will notice, because of the 'draw only what needs
    to be drawn strategy', timings are much than 4:16 Seconds.  */
static void DrawTimer(CGrafPtr wPort, HandyWindowPtr handp, unsigned long tickcount) {
    Rect windowBounds, timerRect;
    RgnHandle clipsave;
    FontInfo fin;
    unsigned long seconds, sixtieths;
    Str255 title, temp;
    
        /* set up for drawing */
    GetClip((clipsave = NewRgn()));
    SetPort(wPort);
    UseThemeFont(kThemeSmallSystemFont, smSystemScript);
    GetFontInfo(&fin);
    GetPortBounds( wPort, &windowBounds);
    SetRect( &timerRect, windowBounds.left, windowBounds.bottom-15, windowBounds.left+kTimerDisplayWidth, windowBounds.bottom+1);
    timerRect.top += 1;
        /* set the clip region */
    ClipRect(&timerRect);
    EraseRect(&timerRect);
        /* calculate the string to display */
    seconds = tickcount/60;
    sixtieths = tickcount%60;
    NumToString(seconds, title);
    PLstrcat(title, "\p:");
    NumToString(sixtieths, temp);
    if (temp[0] == 1) PLstrcat(title, "\p0");
    PLstrcat(title, temp);
    PLstrcat(title, "\p Seconds");
        /* display the string, centered. */
    MoveTo((timerRect.right + timerRect.left - StringWidth(title)) / 2, (timerRect.bottom + timerRect.top - (fin.ascent + fin.descent))/2 + fin.ascent);
    DrawString(title);
        /* if we're not active, gray it out. */
    if ( ! handp->isActive)
        GrayOutBox(&timerRect);
        /* restore and return. */
    SetClip(clipsave);
    DisposeRgn(clipsave);
}
 
 
 
/* DrawHandyWindowImage is called to re-draw the contents of the window.  To optimize
    drawing performance this routine limits the drawing routines it calls to only those
    ones that will have a visual result appearing on the screen.  To do this, it calculates
    the intersection of the grafport's visRgn, clipRgn, and the *bounds parameter and
    tests to see if what it is about to draw resides in this region before performing
    the actual drawing operations. Since the scrolling routines set the clip region to
    the region returned by ScrollRect, this improves scrolling performance dramatically
    by limiting the drawing operation to only the part of the image scrolled into view.*/
static void DrawHandyWindowImage(CGrafPtr wPort, Rect *bounds, HandyWindowPtr handp) {
    RgnHandle clipsave, temp, exposedBits;
    short x, y, index, hposition, vposition;
    Rect box, steps, nameplate;
    RGBColor theColor, savedColor;
    RGBColor rgbWhite = { 0xFFFF, 0xFFFF, 0xFFFF }, rgbBlack = { 0x0000, 0x0000, 0x0000 };
    PenState ps;
    Str255 titleStr, tempStr;
    FontInfo fin;
    CTabHandle clut;
    Pattern blackPen;
    unsigned long startTicks, stopTicks;
        /* start the timer */
    startTicks = TickCount();
        /* set up locals */
    hposition = GetControlValue(handp->hScroll);
    vposition = GetControlValue(handp->vScroll);
    index = 0;
    temp = NewRgn();
    clipsave = NewRgn();
    exposedBits = NewRgn();
    clut = GetCTable(72);
        /* calculate content region */
    SetPort(wPort);
    UseThemeFont(kThemeSystemFont, smSystemScript);
    GetFontInfo(&fin);
    GetForeColor(&savedColor);
    SetOrigin(0, 0);
    GetClip(clipsave);
        /* calculate the region of the content that needs to
        be redrawn.  Here, this is the intersection of the content
        area, the current clipping region, and the window's visRgn. */
    RectRgn(exposedBits, bounds);
        /* intersect with the clip region */
    SectRgn(clipsave, exposedBits, exposedBits);
        /* intersect with the visible region */
    GetPortVisibleRegion(wPort, temp);
    SectRgn(temp, exposedBits, exposedBits);
        /* only draw, if there's actually something to draw... */
    if ( ! EmptyRgn(exposedBits) ) {
            /* offset the region to it's location in the contents.
            We do this explicitly because although SetOrigin will move
            the portRect and visRgn, it will not move the clipRgn. */
        OffsetRgn(exposedBits, hposition, vposition);
            /* set the origin and clipping region */
        SetOrigin(hposition, vposition);
        SetClip(exposedBits);
            /* set up for drawing */
        GetPenState(&ps);
        PenSize(kLineSize, kLineSize);
        PenMode(patCopy);
        PenPat(GetQDGlobalsBlack(&blackPen));
        RGBForeColor(&rgbBlack);
        RGBBackColor(&rgbWhite);
            /* erase the background *black* */
        PaintRgn(exposedBits);
            /* iterate through rows and columns */
        for (y=0; y<kColumnCount; y++)
            for (x=0; x<kRowCount; x++, index++) {
                    /* calculate the location for this color swatch */ 
                SetRect(&box, 0, 0, kBoxSize, kBoxSize);
                OffsetRect(&box, x*kPatchSize + 1, y*kPatchSize + 1);
                    
                    /* only draw this item if the drawing operation will have
                    some noticable effect on the screen.  Overall, this will save
                    some time as we're not doing any of the following drawing
                    or calculation operations. */
                RectRgn(temp, &box);
                SectRgn(exposedBits, temp, temp);
                if ( EmptyRgn(temp) )
                    continue;
                    
                    /* set the colour */
                theColor = (**clut).ctTable[ index ].rgb;
                RGBForeColor(&theColor);
                    /* draw an x in the box */
                MoveTo(box.left, box.top);
                LineTo(box.right - kLineSize, box.bottom - kLineSize);
                MoveTo(box.right - kLineSize, box.top);
                LineTo(box.left, box.bottom - kLineSize);
                
                
                    /* for an illustration of how drawing only the exposed
                    area can affect timing, uncomment the following line. */
                    
                    /* the following line is used to slow down drawing
                    to simulate a complex drawing task.  given that the default
                    size of a window will contain 6 X 4 squares, it will
                    take at the very least 24 ticks (nearly half a second)
                    to redraw the entire window.  But, since during scrolling
                    operations only the part of the image that needs to be
                    redrawn is being drawn, this doesn't really matter. */
                if (handp->useSlowDrawing) {
                    unsigned long finalTicks;
                    Delay(1, &finalTicks);
                }
 
                    /* draw some rescinding frames */
                steps = box;
                while ( ! EmptyRect(&steps)) {
                    FrameRect(&steps);
                    InsetRect(&steps, kLineSize + 1, kLineSize + 1);
                }
                    /* calculate the cell title */
                NumToString(x, titleStr);
                PLstrcat(titleStr, "\p, ");
                NumToString(y, tempStr);
                PLstrcat(titleStr, tempStr);
                    /* calculate a box for the title */
                SetRect(&nameplate, 0, 0,
                    StringWidth(titleStr) + kLineSize*6,
                    fin.ascent + fin.descent + kLineSize*2);
                    /* center it in the color swatch */
                OffsetRect(&nameplate,
                    (box.right + box.left - nameplate.right) / 2,
                    (box.bottom + box.top - nameplate.bottom) / 2);
                    /* erase it and draw a frame */
                EraseRect(&nameplate);
                FrameRect(&nameplate);
                    /* draw the text in black and white */
                RGBForeColor(&rgbBlack);
                MoveTo(nameplate.left + kLineSize*3, nameplate.top + fin.ascent + kLineSize);
                DrawString(titleStr);
            }
            
            /* reset the origin and clipping region */
        SetPort(wPort);
        SetOrigin(0, 0);
        SetClip(clipsave);
    }
        /* clean up */
    RGBForeColor(&savedColor);
    DisposeRgn(clipsave);
    DisposeRgn(exposedBits);
    DisposeRgn(temp);
    SetPenState(&ps);
    DisposeCTable(clut);
        /* stop the timer */
    stopTicks = TickCount();
    DrawTimer(wPort, handp, stopTicks - startTicks);
}
 
 
 
/* IsHandyWindow returns true if the window pointer
    in the HandyWindow parameter is not NULL and
    points to the about box. */
Boolean IsHandyWindow(WindowPtr theWindow) {
    return (FindHandyWindow(theWindow) != NULL);
}
 
 
 
/* GetHandyWindowGrowLimits sets the sizerect rectangle parameter
    to values appropriate for passing to the GrowWindow routine. */
OSErr GetHandyWindowGrowLimits(WindowPtr theWindow, Rect *sizerect) {
    HandyWindowPtr handp;
        /* get our window globals */
    handp = FindHandyWindow(theWindow);
    if (handp == NULL) return paramErr;
    SetRect(sizerect, 128, 64, kImageWidth+15, kImageWidth+15);
    return noErr;
}
 
 
 
/* HandyWindowSizeChanged should be called whenever the size of a window
    created by OpenHandyWindow has been changed by either a call to
    ZoomWindow or SizeWindow. */
OSErr HandyWindowSizeChanged(WindowPtr theWindow) {
    HandyWindowPtr handp;
    Rect wRect, hsRect, vsRect, contentRect, timerRect;
    CGrafPtr wPort; 
    short hmax, vmax;
    RgnHandle invalidRegion;
    
        /* get our window globals */
    handp = FindHandyWindow(theWindow);
    if (handp == NULL) return paramErr;
        /* set up locals */
    invalidRegion = NewRgn();
    SetPort((wPort = GetWindowPort(theWindow)));
    GetPortBounds( wPort, &wRect);
    contentRect = wRect;
    contentRect.right -= 15;
    contentRect.bottom -= 15;
        /* accumulate the old scroll bar and timer rectangles into the update region */
    RgnUnionRect(invalidRegion, GetControlBounds(handp->hScroll, &hsRect));
    SetRect( &timerRect, 0, hsRect.top, kTimerDisplayWidth, hsRect.bottom);
    RgnUnionRect(invalidRegion, &timerRect);
    RgnUnionRect(invalidRegion, GetControlBounds(handp->vScroll, &vsRect));
        /* calculate the boxes */
    SetRect( &timerRect, wRect.left, wRect.bottom-15, wRect.left+kTimerDisplayWidth, wRect.bottom+1);
    SetRect( &hsRect, wRect.left+kTimerDisplayWidth, wRect.bottom-15, wRect.right-14, wRect.bottom+1);
    SetRect( &vsRect, wRect.right-15, wRect.top-1, wRect.right+1, wRect.bottom-14);
        /* reposition the scroll bars */
    SetControlBounds( handp->hScroll, &hsRect);
    SetControlBounds( handp->vScroll, &vsRect);
    vsRect.bottom += 15; /* include the grow box */
    RgnUnionRect(invalidRegion, &timerRect);
    RgnUnionRect(invalidRegion, &hsRect);
    RgnUnionRect(invalidRegion, &vsRect);
        /* set the horizontal scroll bar maximum and value */
    hmax = kImageWidth - contentRect.right;
    if (hmax < 0) hmax = 0;
    if (GetControlValue(handp->hScroll) > hmax) {
        SetControlValue(handp->hScroll, hmax);
        RgnUnionRect(invalidRegion, &contentRect);
    }
    SetControlMaximum(handp->hScroll, hmax);
    SetControlViewSize(handp->hScroll, kImageWidth);
        /* set the vertical scroll bar maximum and value */
    vmax = kImageWidth - contentRect.bottom;
    if (vmax < 0) vmax = 0;
    if (GetControlValue(handp->vScroll) > vmax) {
        SetControlValue( handp->vScroll, vmax);
        RgnUnionRect(invalidRegion, &contentRect);
    }
    SetControlMaximum(handp->vScroll, vmax);
    SetControlViewSize(handp->vScroll, kImageWidth);
        /* announce the accumulated update region */
    InvalWindowRgn(theWindow, invalidRegion);
    DisposeRgn(invalidRegion);
    return noErr;
}
 
 
 
/* OpenHandyWindow opens a handy window using the WIND resource
    with the windowID ID. If successful, OpenHandyWindow returns a
    pointer to the newly created window. */
WindowPtr OpenHandyWindow(short windowID, Boolean drawSlow) {
    Rect wRect, hsRect, vsRect;
    HandyWindowPtr handp;
    WindowPtr theWindow;
    ControlHandle hscroll, vscroll;
    Str255 title;
        /* set up locals */
    handp = NULL;
    theWindow = NULL;
    hscroll = vscroll = NULL;
        /* get the window */
    theWindow = GetNewCWindow(windowID, NULL, (WindowPtr)(-1));
    if (theWindow == NULL) goto bail;
    handp = NewHandyWindowRec(theWindow);
    if (handp == NULL) goto bail;
        /* set up the zoom size */
    SetWindowStandardStateSize(theWindow, kImageWidth+15, kImageWidth+15);
        /* create the scroll bars */
    GetPortBounds(GetWindowPort(theWindow), &wRect);
    SetRect(&hsRect, wRect.left, wRect.bottom-15, wRect.right-14, wRect.bottom+1);
    SetRect(&vsRect, wRect.right-15, wRect.top-1, wRect.right+1, wRect.bottom-14);
    hscroll = NewControl(theWindow, &hsRect, "\p", true, 0, 0, 100, kControlScrollBarLiveProc, (long) handp);
    if (hscroll == NULL) goto bail;
    HiliteControl(hscroll, 255);
    vscroll = NewControl(theWindow, &vsRect, "\p", true, 0, 0, 100, kControlScrollBarLiveProc, (long) handp);
    if (vscroll == NULL) goto bail;
    HiliteControl(vscroll, 255);
        /* store our data */
    handp->hScroll = hscroll;
    handp->vScroll = vscroll;
    handp->isActive = false;
    handp->useSlowDrawing = drawSlow;
        /* readjust the coordinates and we're done... */
    HandyWindowSizeChanged(theWindow);
    GetIndString(title, kHandyWindowStrings, (handp->useSlowDrawing ? kHandyWindowSlowTitle : kHandyWindowTitle));
    SetWTitle(theWindow, title);
    ShowWindow(theWindow);
    return theWindow;
bail:
    if (hscroll != NULL) DisposeControl(hscroll);
    if (vscroll != NULL) DisposeControl(vscroll);
    if (theWindow != NULL) DisposeWindow(theWindow);
    if (handp != NULL) DisposeHandyWindowRec(handp);
    return NULL;
}
 
 
 
/* HandyWindowCloseWindow closes the about box window. 
    this routine deallocates any structures allocated
    by the OpenHandyWindow. */
OSErr HandyWindowCloseWindow(WindowPtr theWindow) {
    HandyWindowPtr handp;
    handp = FindHandyWindow(theWindow);
    if (handp == NULL) return paramErr;
    DisposeControl(handp->hScroll);
    DisposeControl(handp->vScroll);
    DisposeWindow(handp->theWindow);
    DisposeHandyWindowRec(handp);
    return noErr;
}
 
 
 
/* CloseAllHandyWindows closes every handy window
    opened by calling OpenHandyWindow. */
void CloseAllHandyWindows(void) {
    while (gHWFirst != NULL) 
        HandyWindowCloseWindow(gHWFirst->theWindow);
}
 
 
/* HandyWindowUpdate should be called for update events
    directed at a handy window.  It calls
    BeginUpdate and EndUpdate and does all of the
    drawing required to refresh the handy window. */
OSErr HandyWindowUpdate(WindowPtr theWindow) {
    HandyWindowPtr handp;
    CGrafPtr windowGp;
    Rect contents, timerRect, wRect;
        /* set up our variables */
    handp = FindHandyWindow(theWindow);
    if (handp == NULL) return paramErr;
        /* set up the window */
    windowGp = GetWindowPort(theWindow);
    SetPort(windowGp);
    GetPortBounds(windowGp, &wRect);
        /* make sure the timer rectangle is part of the update region */
    SetRect( &timerRect, wRect.left, wRect.bottom-15, wRect.left+kTimerDisplayWidth, wRect.bottom+1);
    InvalWindowRect(theWindow, &timerRect);
    contents = wRect;
    contents.right -= 15;
    contents.bottom -= 15;
        /* set the window for drawing */
    BeginUpdate(theWindow);
        /* draw the controls and grow icon */
    Draw1Control(handp->hScroll);
    Draw1Control(handp->vScroll);
    DrawGrowIcon(theWindow);
        /* update the image */
    DrawHandyWindowImage(windowGp, &contents, handp);
        /* if the window is inactive, gray out the image */
    if ( ! handp->isActive)
        GrayOutBox(&contents);
        /* done drawing */
    EndUpdate(theWindow);
    return noErr;
}
 
 
 
/* HandyWindowActivate should be called for activate events
    directed at a handy window. */
OSErr HandyWindowActivate(WindowPtr theWindow, Boolean activate) {
    HandyWindowPtr handp;
        /* verify the window is a handy window, get the variables */
    if ((handp = FindHandyWindow(theWindow)) == NULL) return paramErr;
        /* if the activate state is changing */
    if (handp->isActive != activate) {
        Rect bounds;
        CGrafPtr wPort;
            /* set the new activation state */
        handp->isActive = activate;
        wPort = GetWindowPort(theWindow);
            /* redraw as appropriate for new activation state */
        SetPort(wPort);
        GetPortBounds(wPort, &bounds);
        bounds.right -= 15;
        bounds.bottom -= 15;
        if (handp->isActive) {
                /* if the window is active, enable scroll bars and redraw contents */
            HiliteControl(handp->hScroll, 0);
            HiliteControl(handp->vScroll, 0);
            InvalWindowRect(theWindow, &bounds);
        } else {
                /* if the window is inactive, disable the scroll bars and gray out contents */
            HiliteControl(handp->hScroll, 255);
            HiliteControl(handp->vScroll, 255);
            GrayOutBox(&bounds);
            DrawGrowIcon(theWindow);
            DrawTimer(wPort, handp, 0);
        }
    }
    return noErr;
}
 
 
 
/* gPreviousScrollValue contains the last value of the scroll bar before the
    most recent scroll bar tracking operation.  This value is used for tracking
    previous scroll values so we can determine the distance the display needs
    to be scrolled during live scrolling and indicator draggs.   It is only used
    in when the click is in the kControlIndicatorPart part. */
short gPreviousScrollValue = 0;
 
 
 
/* WindowScrollBarProc is the callback procedure we pass to TrackControl
    during the handling of mouse clicks in scroll bars.  It is also possible
    to call this routine specifying one of the part codes to obtain different
    scrolling behaviors (we do this for the arrow keys in the HandyWindowKey
    routine defined below). */
static pascal void WindowScrollBarProc(ControlHandle theControl, short partCode) {
    short prev, max, next, *delta;
    short dh, dv, pageSize;
    HandyWindowPtr handp;
    Rect content;
    RgnHandle parttodraw, clipsave;
    CGrafPtr wPort;
        /* set up our local variables */
    handp = (HandyWindowPtr) GetControlReference(theControl);
    SetPort((wPort = GetWindowPort(handp->theWindow)));
    GetPortBounds(wPort, &content);
    content.right -= 15;
    content.bottom -= 15;
         /* initialize the horizontal and the vertical delta variables.
         dv and dh are used to store the vertical and horizontal distance
         the display is to be scrolled. */
    dh = dv = 0;
        /* calculate the page size and the delta variable
        we are going to be using.  The delta variable we are
        changing and the page size depends on the scroll
        bar we are tracking.  */
    if (theControl == handp->vScroll) {
        pageSize = content.bottom - content.top;
        delta = &dv;
    } else {
        pageSize = content.right - content.left;
        delta = &dh;
    }
        /* calculate the previous and next scroll bar
        positions. */
    switch (partCode) {
    
            /* for the up and down buttons, we scroll
            sizteen pixels */
        case kControlUpButtonPart:
            prev = GetControlValue(theControl);
            next = prev - 16;
            break;
        case kControlDownButtonPart:
            prev = GetControlValue(theControl);
            next = prev + 16;
            break;
            
            /* for the page up and page down areas, we scroll
            one page at a time (the width of the window for the
            horizontal scroll bar, and the height of the window for
            the vertical scroll bar. */
        case kControlPageUpPart:
            prev = GetControlValue(theControl);
            next = prev - pageSize;
            break;
        case kControlPageDownPart:
            prev = GetControlValue(theControl);
            next = prev + pageSize;
            break;
            
            /* kControlIndicatorPart will be passed to this routine
            during live scrolling operations or when the user is tracking
            the indicator part.  Here, we calculate the distance the indicator
            has been moved using the gPreviousScrollValue global variable.
            note, we set the gPreviousScrollValue variable to the current
            scroll bar value so we can calculate the next delta the next time
            we are called. */
        case kControlIndicatorPart:
            prev = gPreviousScrollValue;
            gPreviousScrollValue = next = GetControlValue(theControl);
            break;
            
            /* if the user moves the mouse outside of the part that was
            originally clicked in, we may be called with a part code of zero.
            nothing to do here...*/
        default:
            return;
    }
        /* verify that our next value is in the allowable range of value */
    max = GetControlMaximum(theControl);
    if (next < 0) next = 0; else if (next > max) next = max;
        /* calculate the change.  If there's no change, we're done.
        This is an important check as calling ScrollRect(.., 0, 0, xx)
        will crash on some systems. */
    *delta = prev - next;
    if (*delta == 0) return;
        /* set the new control bar value.  if we're tracking the indicator
        next will already be the scroll value so there's no need to change
        it here, that's why there's the extra check */
    if (GetControlValue(theControl) != next)
        SetControlValue(theControl, next);
        /* save the current clipping region */
    GetClip((clipsave = NewRgn()));
        /* scroll the content area using our delta.  This call to
        ScrollRect will set the region parttodraw to the new
        'undrawn' area exposed by the operation */
    ScrollRectInBlack(&content, dh, dv, (parttodraw = NewRgn()));
        /* DrawHandyWindowImage will only perform drawing operations
        that are visible in the current clipping region, so to tell it that it does
        not have to draw the entire image, we set the clipping region to
        only the part that has been erased by the scrolling operation. */
    SetClip(parttodraw);
        /* draw the scrolled in area */
    DrawHandyWindowImage(GetWindowPort(handp->theWindow), &content, handp);
        /* restore the clipping region and clean up our storage */
    SetClip(clipsave);
    DisposeRgn(clipsave);
    DisposeRgn(parttodraw);
}
 
 
 
/* HandyWindowScrollUsingHand is called to scroll the window's view when the
    mouse is clicked in the window's contents.  basically, this routine redraws the
    cursor as the 'grabbing hand' and then follows the mouse scrolling the contents
    and adjusting the scroll bar values as appropriate. */
static void HandyWindowScrollUsingHand(HandyWindowPtr handp, Point where) {
    Point currentpt, lastpt, startpt;
    Point start, next, last, offset;
    RgnHandle clip, drawp;
    Rect mypin;
    Rect content;
    CGrafPtr wPort;
        /* set up our local variables */
    SetPort((wPort = GetWindowPort(handp->theWindow)));
    GetPortBounds(wPort, &content);
    content.right -= 15;
    content.bottom -= 15;
    startpt = lastpt = where;
        /* calculate the starting point */
    SetPt(&start, GetControlValue(handp->hScroll), GetControlValue(handp->vScroll));
    last = start;
        /* create a pin rectangle.  We use this rectangle with the window manager
        PinRect routine to ensure the next value we calculate will always be
        within the allowable scrollable range. */
    SetRect(&mypin, 0, 0, GetControlMaximum(handp->hScroll)+1, GetControlMaximum(handp->vScroll)+1);
    clip = NewRgn();
    drawp = NewRgn();
        /* set the cursor to the grabbing hand */
    SetThemeCursor(kThemeClosedHandCursor);
        /* hilite the scroll bar indicators.  This provides some visual feedback indicating
        the user is now scrolling using both scroll bar indicators at the same time. */
    HiliteControl(handp->hScroll, kControlIndicatorPart);
    HiliteControl(handp->vScroll, kControlIndicatorPart);
        /* while the mouse is being held down */
    while (StillDown()) {
            /* only recalculate when the mouse moves */
        GetMouse(&currentpt);
        if ( ! EqualPt(currentpt, lastpt)) {
            lastpt = currentpt;
                /* calculate the new scroll position based on cursor movement */
            next = startpt;
            SubPt(currentpt, &next);
            AddPt(start, &next);
                /* pin the new position into the maximum allowable range */
            (* (long*) &next) = PinRect(&mypin, next);
                /* calculate the delta from the last drawn position */
            offset = next;
            SubPt(last, &offset);
                /* only redraw and adjust scroll bar values if we
                are going to move the image.... */
            if (offset.h != 0 || offset.v != 0) {
                    /* set the new scrolling position */
                SetControlValue(handp->hScroll, next.h);
                SetControlValue(handp->vScroll, next.v);
                last = next;
                    /* save the clipping region */
                GetClip(clip);
                    /* scroll the image.  drawp will be set to the area exposed by
                    the scrolling operation. */
                ScrollRectInBlack(&content, -offset.h, -offset.v, drawp);
                    /* DrawHandyWindowImage will only perform drawing operations
                    inside of the current clipping region.  So, to speed up drawing, we
                    set the clip region to the area exposed by the scroll. */
                SetClip(drawp);
                    /* redraw the empty part of the screen */
                DrawHandyWindowImage(GetWindowPort(handp->theWindow), &content, handp);
                    /* restore the clip region */
                SetClip(clip);
            }
        }
    }
        /* clean up our storage */
    DisposeRgn(clip);
    DisposeRgn(drawp);
        /* unhilite the scroll bar indicators */
    HiliteControl(handp->hScroll, 0);
    HiliteControl(handp->vScroll, 0);
        /* set the cursor back to the open hand */
    SetThemeCursor(kThemeOpenHandCursor);
}
 
 
 
/* HandyWindowScrollTo can be called to scroll the image to a particular
    location on screen.   This routine attempts to scroll the image so that
    the location refered to by position is aligned with the top left corner of
    the window.  Of course, the actual distance scrolled is pinned within
    the allowable range of scroll bar values. */
OSErr HandyWindowScrollTo(WindowPtr theWindow, Point position) {
    HandyWindowPtr handp;
    Point next, last, offset;
    RgnHandle clip, drawp;
    Rect mypin;
    Rect content;
    CGrafPtr wPort;
        /* set up our local variables */
    handp = FindHandyWindow(theWindow);
    if (handp == NULL) return paramErr;
    SetPort((wPort = GetWindowPort(handp->theWindow)));
    GetPortBounds(wPort, &content);
    content.right -= 15;
    content.bottom -= 15;
    SetRect(&mypin, 0, 0, GetControlMaximum(handp->hScroll)+1, GetControlMaximum(handp->vScroll)+1);
        /* set the cursor to the grabbing hand */
    SetPt(&last, GetControlValue(handp->hScroll), GetControlValue(handp->vScroll));
    clip = NewRgn();
    drawp = NewRgn();
    next = position;
    (* (long*) &next) = PinRect(&mypin, next);
        /* calculate the delta from the last drawn position */
    offset = next;
    SubPt(last, &offset);
        /* if the new drawing position has changed, scroll and redraw... */
    if (offset.h != 0 || offset.v != 0) {
            /* set the new scrolling position */
        SetControlValue(handp->hScroll, next.h);
        SetControlValue(handp->vScroll, next.v);
            /* scroll the image */
        GetClip(clip);
        ScrollRectInBlack(&content, -offset.h, -offset.v, drawp);
            /* set the clip region to the area exposed by the scroll */
        SetClip(drawp);
            /* redraw the empty part of the screen */
        DrawHandyWindowImage(GetWindowPort(handp->theWindow), &content, handp);
            /* restore the clip region */
        SetClip(clip);
    }
    DisposeRgn(clip);
    DisposeRgn(drawp);
    return noErr;
}
 
 
 
/* HandyWindowActivate should be called for activate events
    directed at the about box window. */
OSErr HandyWindowMouse(WindowPtr theWindow, Point where, short modifiers) {
    HandyWindowPtr handp;
    CGrafPtr wPort;
    Rect content, hscroll, vscroll;
    ControlHandle theControl;
    
        /* find the windows variables.  If there are none, then it's
        not one of our windows. */
    if ((handp = FindHandyWindow(theWindow)) == NULL) return paramErr;
    
        /* set up locals, thePort, etc... */
    wPort = GetWindowPort(theWindow);
    SetPort(wPort);
    GetPortBounds(wPort, &content);
    content.right -= 15;
    content.bottom -= 15;
    theControl = NULL;
        /* check if its in a scroll bar.  We do it this way incase other controls
        are being drawn in the window's content area.  Since they are being
        drawn in a different coordinate system, when this call is made is may
        be possible that they overlap. */
    if (PtInRect(where, GetControlBounds(handp->hScroll, &hscroll)))
        theControl = handp->hScroll;
    else if (PtInRect(where, GetControlBounds(handp->vScroll, &vscroll)))
        theControl = handp->vScroll;
        /* if it was a scroll bar, then we handle the click as either an
        indicator click or as a regular part click. */
    if (theControl != NULL) {
        short partCode;
        static ControlActionUPP gMyActionProc = NULL;
            /* allocate our action procedure pointer. */
        if (gMyActionProc == NULL)
            gMyActionProc = NewControlActionUPP(WindowScrollBarProc);
            /* find the part where the mouse was clicked. */
        partCode = TestControl(theControl, where);
        if (partCode == kControlIndicatorPart) {
                /* if we're dragging the indicator, then we set the cursor
                so it looks like we are grabbing the indicator. */
            SetThemeCursor(kThemeClosedHandCursor);
                /* save the previous scroll value.  For live scrolling, this
                value is used inside of WindowScrollBarProc.  */
            gPreviousScrollValue = GetControlValue(theControl);
            TrackControl(theControl, where, gMyActionProc);
                /* if the scroll bar does not support live scrolling, then this
                is where the actual scrolling will occur. Performing the scrolling
                at this point is 'the old way' kControlIndicatorPart (inThumb) drags
                were handled.  It's not really necessary here, but it has been included
                for illustration.  Without live scrolling, we would also call TrackControl
                with a NULL action procedure parameter.  */
            if (gPreviousScrollValue != GetControlValue(theControl))
                WindowScrollBarProc(theControl, kControlIndicatorPart);
                /* restore the cursor to the 'open hand' since we are 'letting go'
                of the indicator. */
            SetThemeCursor(kThemeOpenHandCursor);
        } else if (partCode == kControlUpButtonPart || partCode == kControlDownButtonPart
        || partCode == kControlPageUpPart || kControlPageDownPart) {
                /* if it's another part, then we handle all of the scrolling in our
                tracking procedure. */
            TrackControl(theControl, where, gMyActionProc);
        }
    } else if (PtInRect(where, &content)) {
        HandyWindowScrollUsingHand(handp, where);
    }
    return noErr;
}
 
 
 
/* HandyWindowKey is called in response to key down events.  Here, we dispatch
    special keys such as the arrows, page up, page down, home, and end to
    their equivalent actions using the scroll bars. */
OSErr HandyWindowKey(WindowPtr theWindow, char key, short modifiers) {
    HandyWindowPtr handp;
    Point position;
        /* find the window's variables.  if it has none, it's
        not one of our windows. */
    if ((handp = FindHandyWindow(theWindow)) == NULL) return paramErr;
    if ( ! handp->isActive) return paramErr;
        /* set up locals */
    switch (key) {
        case SOH:               /* HOME KEY*/
            SetPt(&position, 0, 0);
            HandyWindowScrollTo(theWindow, position);
            break;
        case EOT:               /* END KEY*/
            SetPt(&position, kImageWidth, kImageHeight);
            HandyWindowScrollTo(theWindow, position);
            break;
        case VT:                /* PAGEUP KEY*/
            WindowScrollBarProc(handp->vScroll, kControlPageUpPart);
            break;
        case FF:                /* PAGEDOWN KEY*/
            WindowScrollBarProc(handp->vScroll, kControlPageDownPart);
            break;
        case FS:                /* LEFT ARROW KEY */
            WindowScrollBarProc(handp->hScroll, kControlUpButtonPart);
            break;
        case GS:                /* RIGHT ARROW KEY */
            WindowScrollBarProc(handp->hScroll, kControlDownButtonPart);
            break;
        case RS:                /* UP ARROW KEY */
            WindowScrollBarProc(handp->vScroll, kControlUpButtonPart);
            break;
        case US:                /* DOWN ARROW KEY */
            WindowScrollBarProc(handp->vScroll, kControlDownButtonPart);
            break;
        default:
            return paramErr;
            break;
    }
    return noErr;
}
 
 
/* HandyWindowGetSlow returns the state of the handy window's drawing
    speed.  For illustration, it is possible to set the drawing speed to either
    slow or fast. */
OSErr HandyWindowGetSlow(WindowPtr theWindow, Boolean *drawnSlowly) {
    HandyWindowPtr handp;
    if ((handp = FindHandyWindow(theWindow)) == NULL) return paramErr;
    *drawnSlowly = handp->useSlowDrawing;
    return noErr;
}
 
/* HandyWindowSetSlow sets the state of the handy window's drawing
    speed and posts an update event so the contents will be re-drawn
    showing the new speed.  For illustration, it is possible to set the
    drawing speed to either slow or fast. */
OSErr HandyWindowSetSlow(WindowPtr theWindow, Boolean drawSlowly) {
    HandyWindowPtr handp;
    Rect contentRect;
    Str255 title;
    if ((handp = FindHandyWindow(theWindow)) == NULL) return paramErr;
    handp->useSlowDrawing = drawSlowly;
    GetPortBounds( GetWindowPort( theWindow), &contentRect);
    contentRect.right -= 15;
    contentRect.bottom -= 15;
    InvalWindowRect(theWindow, &contentRect);
    GetIndString(title, kHandyWindowStrings, (handp->useSlowDrawing ? kHandyWindowSlowTitle : kHandyWindowTitle));
    SetWTitle(theWindow, title);
    return noErr;
}
 
 
/* HandyWindowCursor is called when a handy window is the frontmost window and
    the HandySample application is the frontmost application.  where is a point in
    the window's coordinates, and modifiers is the state of the modifier
    keys copied from the event record.  mouseRgn is a handle to a region.  If
    HandyWindowCursor sets the cursor, then it will also set this region to
    the region where it is appropriate for the cursor to remain as it has been
    set (in global coordinates).  this region is appropriate for passing to WaitNextEvent
    in the mouse region parameter. */
Boolean HandyWindowCursor(WindowPtr theWindow, Point where, short modifiers, RgnHandle mouseRgn) {
    HandyWindowPtr handp;
    CGrafPtr wPort;
    Rect contentRect;
    Point offset;
        /* find the window's variables.  if it has none, it's
        not one of our windows. */
    if ((handp = FindHandyWindow(theWindow)) == NULL) return false;
    if ( ! handp->isActive) return false;
        /* set up locals */
    SetPort((wPort = GetWindowPort( theWindow)));
        /* calculate the window's origin in global coordinates */
    SetPt(&offset, 0, 0);
    LocalToGlobal( &offset);
        /* calculate the content rectangle */
    GetPortBounds( wPort, &contentRect);
    contentRect.right -= 15;
    contentRect.bottom -= 15;
        /* find the click location */
    if (PtInRect( where, &contentRect)) {
            /* if it's in the content area, then we use the open hand. */
        RectRgn(mouseRgn, &contentRect);
        OffsetRgn(mouseRgn, offset.h, offset.v);
        SetThemeCursor(kThemeOpenHandCursor);
        return true;
        
        /* for scroll bars, we call the routine SetScrollBarCursor defined
        in SampleUtils.h.  That routine sets the cursor to a hand when it's
        over the scroll bar's indicator part.  Otherwise it sets the cursor to
        a pointing finger. */
    } else if (SetScrollBarCursor(handp->hScroll, where, mouseRgn)) {
        OffsetRgn(mouseRgn, offset.h, offset.v);
        return true;
    } else if (SetScrollBarCursor(handp->vScroll, where, mouseRgn)) {
        OffsetRgn(mouseRgn, offset.h, offset.v);
        return true;
    }
        /* if the mouse isn't in our window, then we have nothing to say about it. */
    return false;
}