Live Controls Scroll.c

/*
    File:       Scroll Controls Offscreen.c
 
    Contains:   Sample code illustrating scrolling of controls via an off screen GWorld
 
    Written by: Geoff Stahl (ggs)
 
    Copyright:  Copyright (c) 1999 Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
                
*/
 
// system includes ----------------------------------------------------------
 
#include <MacMemory.h>
#include <Events.h>
#include <Fonts.h>
#include <Windows.h>
#include <TextEdit.h>
#include <Dialogs.h>
#include <Sound.h>
#include <SoundInput.h>
#include <ToolUtils.h>
#include <OSUtils.h>
#include <Controls.h>
#include <Devices.h>
#include <LowMem.h>
 
#include <stdlib.h>
 
 
// project includes ---------------------------------------------------------
 
 
 
// statics/globals (internal only) ------------------------------------------
 
// Menu defs
enum 
{
    kMenuApple = 128,
    kMenuFile = 129,
    
    kFileQuit = 1,
    
    kButtonMenuID = 150
};
 
enum
{
                                                    // Window Constants
    kOffScreenHeight = 1600,                        // self e.
    kOffScreenWidth = 900,                          //   "
    kWindowHeight = 400,                            //   "
    kWindowWidth = 500,                             //   "
    kWindowOffset = 100,                            //   "
    
                                                    // Scroll bar constants
    kScrollBarWidth = 16,                           // fixed
    kScrollArrowWidth = kScrollBarWidth,            //   "
    kScrollBarWidthAdjust = kScrollBarWidth - 1,    //   "
    
    kPageOverlap = 10,                      // overlap for a page down or up
    kThumbTrackSlop = 100,                  // delta from scroll bar for snap to start
    
    kNumControls = 400                      // number of controls
};
 
Rect gRectWindowGbl;                        // window rectanglein global coordinates
 
ControlHandle ghControls [kNumControls];    // test controls
Point gPtCntlPos [kNumControls];            // initial control postions
 
WindowPtr gpWindow = NULL;                  // main window
GWorldPtr gpGWOffScreen = NULL;             // main window offscreen scratch
 
short gScrollVThumbSize = 0;
short gScrollHThumbSize = 0;
short gTotalVSizeAdjust = 0;
short gTotalHSizeAdjust = 0;
 
ControlHandle ghHScrollBar = NULL;          // main window scroll bar
ControlHandle ghVScrollBar = NULL;          // main window scroll bar
 
RgnHandle gSaveClip = NULL;                 // clipping save for control drawing exclusions
ControlRef gControl;                        // active scroll control
SInt32 gStartValue;                         // scroll start value
SInt32 gValueSlop;                          // slop for mouse live scroll calcs
SInt32 gSaveValue;                          // current scroll value on live exit
 
SInt32 gSleepTime = 60;                     // WaitNextEvent sleep time
Boolean gDone = false;                      // done yet
 
// functions Prototypes (internal/private) -----------------------------------
 
void CleanUp (void);                                                    // clean ups after application
void ScrollControls (WindowPtr pWinParent, short left, short top);      // moves postion of controls in relation to scrolled positon of window without updating
void ScrollDrawContent (WindowPtr pWindow, short wWindowLeft, short wWindowTop); // draws contents from offscreen to on and diretcly draws scroll bar on screen
void DoUpdate (WindowPtr pWindow);                                      // draw from off screen to on and updates control postions on screen for events
void MoveScroll (ControlHandle hScrollBar, short wDist);                // adjust scroll bar cotnrol position accouting for control limits
pascal void ProcScroll (ControlHandle hControl, short wPart);           // proc to handle clicks in scroll bar gray or arrows (page up/down, line up/down)
void EndThumbTracking (void);                                           // handles release of live scroll
SInt32 CalcValueFromPoint (ControlHandle hControl, Point thePoint);     // converts mouse to scroll postion and handles out of slop area
pascal void ProcScrollThumbAction (void);                               // proc to handle live scrolling
OSErr BeginThumbTracking (ControlRef hControl);                         // handles beginning of live scrolling
void DrawOneControlOffscreen (ControlHandle hControl);                  // updates the image of hControl for offscreen
void DoContentClick (WindowPtr pWindow, EventRecord * pEvent);          // overall content click handler
void DoMenu (SInt32 menuResult);                                        // menu event handler
void DoEvent (void);                                                    // event handler
void DrawOffScreen (CGrafPtr pCGOffScreen);                             // offscreen update (this would also be called for control value updates)
void SetScrollParams (WindowPtr hWindow);                               // sets scroll bar size reference vars
void AdjustScrollBar (WindowPtr pWindow, ControlHandle scrollBar);      // sets appearance savvy portional scroll bar up
void InitToolbox(void);                                                 // standard inits
Boolean SetUp (void);                                                   // application setup
 
// NOTE: Initial control position is stored in gPtCntlPos Point array to allow reference during scrolling, 
 
// functions (internal/private) ---------------------------------------------
 
void CleanUp (void)
{
    MenuHandle hMenu;
 
    HideWindow (gpWindow);
    KillControls (gpWindow);
    if (gpWindow)
        DisposeWindow (gpWindow);
    gpWindow = NULL;
    if (gpGWOffScreen)
        DisposeGWorld (gpGWOffScreen);
    gpGWOffScreen = NULL;
    
    hMenu = GetMenu (kButtonMenuID);
    DeleteMenu (kButtonMenuID);
    DisposeMenu (hMenu);
 
    hMenu = GetMenu (kButtonMenuID);
    DeleteMenu (kMenuFile);
    DisposeMenu (hMenu);
 
    hMenu = GetMenu (kButtonMenuID);
    DeleteMenu (kMenuApple);
    DisposeMenu (hMenu);
}
 
// --------------------------------------------------------------------------
 
void ScrollControls (WindowPtr pWinParent, short left, short top)
{
    Rect rectNull = {0, 0, 0, 0};
    GrafPtr pGrafSave;
    RgnHandle hRgnClipSave;
    short i;
    
    GetPort (&pGrafSave);
    SetPort ((GrafPtr) pWinParent);
    
    // set the clip region to empty to avoid updates
    hRgnClipSave = NewRgn ();
    GetClip ( hRgnClipSave );
    ClipRect ( &rectNull );
    
    // move controls to support mouse down events in the window
    for (i = 0; i < kNumControls; i++)
        if (((gPtCntlPos [i].v - top) != (**ghControls[i]).contrlRect.top) || ((gPtCntlPos [i].h - left) != (**ghControls[i]).contrlRect.left))// move Controls for window pos using top stored in refcon
            MoveControl (ghControls[i], gPtCntlPos [i].h - left, gPtCntlPos [i].v - top);
 
    SetClip (hRgnClipSave);
    SetPort (pGrafSave);
}
 
// --------------------------------------------------------------------------
 
void ScrollDrawContent (WindowPtr pWindow, short wWindowLeft, short wWindowTop) // offset and draw window content from offscreen to on
{
    Rect rectDest = ((GrafPtr)pWindow)->portRect;
    Rect rectSource = ((GrafPtr)pWindow)->portRect;
    
    GrafPtr pCGrafSave;
    GetPort (&pCGrafSave);
 
    SetPort ((GrafPtr) pWindow);
    
    OffsetRect (&rectSource, wWindowLeft, wWindowTop);  // offset for position in offscreen
 
    rectDest.right -= kScrollBarWidthAdjust;            // don't draw scroll bars
    rectDest.bottom -= kScrollBarWidthAdjust;
 
    rectSource.right -= kScrollBarWidthAdjust;
    rectSource.bottom -= kScrollBarWidthAdjust;
    
    CopyBits (&((GrafPtr)gpGWOffScreen)->portBits, &pWindow->portBits, &rectSource, &rectDest, srcCopy, NULL);
    
    Draw1Control (ghHScrollBar); // draw scroll bar
    Draw1Control (ghVScrollBar); // draw scroll bar
    
    SetPort (pCGrafSave);
}
 
// --------------------------------------------------------------------------
 
void DoUpdate (WindowPtr pWindow)
{
    ScrollDrawContent (pWindow, GetControlValue (ghHScrollBar), GetControlValue (ghVScrollBar));    // draw content offscreen to on 
    ScrollControls (pWindow, GetControlValue (ghHScrollBar), GetControlValue (ghVScrollBar));       // update controls positions
}
 
// --------------------------------------------------------------------------
 
void MoveScroll (ControlHandle hScrollBar, short wDist) // update thumb position check for ends
{
    short wScrollPos = GetControlValue (hScrollBar);            
    wScrollPos = wScrollPos + wDist;
 
    if ( wScrollPos < GetControlMinimum ( hScrollBar ) )
        wScrollPos = GetControlMinimum ( hScrollBar );
    else if ( wScrollPos > GetControlMaximum ( hScrollBar ) )
        wScrollPos = GetControlMaximum ( hScrollBar );
 
    SetControlValue (hScrollBar, wScrollPos);
}
 
// --------------------------------------------------------------------------
 
pascal void ProcScroll (ControlHandle hControl, short wPart)    // handle scroll callback
{
    WindowPtr pWindow = (**hControl).contrlOwner;
    short wScrollDist = 0;
    switch (wPart)
    {
        case kControlIndicatorPart:         // scroll up
            wScrollDist = gStartValue - GetControlValue (hControl); 
            break;
        case kControlUpButtonPart:          // scroll up
            wScrollDist = -2;
            break;
        case kControlDownButtonPart:        // scroll down
            wScrollDist = 2;
            break;
        case kControlPageUpPart:            // page up
            if ( hControl == ghHScrollBar)
                wScrollDist = -(pWindow->portRect.right - pWindow->portRect.left - kPageOverlap);
            else
                wScrollDist = -(pWindow->portRect.bottom - pWindow->portRect.top - kPageOverlap);
            break;
        case kControlPageDownPart:          // page up
            if ( hControl == ghHScrollBar)
                wScrollDist = (pWindow->portRect.right - pWindow->portRect.left - kPageOverlap);
            else
                wScrollDist = (pWindow->portRect.bottom - pWindow->portRect.top - kPageOverlap);
            break;
    }
    if ((wScrollDist) && (pWindow))
    {
        MoveScroll (hControl, wScrollDist);
        ScrollDrawContent (pWindow, GetControlValue (ghHScrollBar), GetControlValue (ghVScrollBar));
    }
}
 
// --------------------------------------------------------------------------
 
void EndThumbTracking ( void )  // mouse up on thumb determine and set final value
{
    SetClip (gSaveClip);
    DisposeRgn (gSaveClip);
    SetControlValue (gControl, gSaveValue); 
    gSaveValue = 0; 
    return;
}
 
// --------------------------------------------------------------------------
 
SInt32 CalcValueFromPoint (ControlHandle hControl, Point thePoint)  // figure where we are in scroll bar terms
{
    SInt32 theValue = 0, theRange, theDistance, thePin;
    Rect rectControl;
    WindowPtr pWindow;
    
    pWindow = (*hControl)->contrlOwner;
        
    rectControl = (*hControl)->contrlRect;
    theRange = GetControlMaximum ( hControl ) - GetControlMinimum ( hControl );
    if ( hControl == ghHScrollBar)
    {
        // Scroll distance adjusted for scroll arrows and the thumb
        theDistance = rectControl.right - rectControl.left - gTotalHSizeAdjust;
        // Pin thePoint to the middle of the thumb
        thePin = rectControl.left + (gScrollHThumbSize / 2);
        theValue = ((thePoint.h - thePin) * theRange) / theDistance;
    }
    else if ( hControl == ghVScrollBar)
    {
        // Scroll distance adjusted for scroll arrows and the thumb
        theDistance = rectControl.bottom - rectControl.top - gTotalVSizeAdjust;
        // Pin thePoint to the middle of the thumb
        thePin = rectControl.top + (gScrollVThumbSize / 2);
        theValue = ((thePoint.v - thePin) * theRange) / theDistance;
    }
    theValue += gValueSlop;
    return theValue;
}
 
// --------------------------------------------------------------------------
 
pascal void ProcScrollThumbAction (void)    // handle thumb callbacks
{
    Rect            rectNull = {0, 0, 0, 0};
    SInt32          theValue;
    WindowRef       pWindow;
    ControlRef      hControl = gControl;
    Point           thePoint;
    Rect            rectControl;
    
    
    pWindow = (*hControl)->contrlOwner;
    
    rectControl = (*hControl)->contrlRect;
    InsetRect (&rectControl, -kThumbTrackSlop, -kThumbTrackSlop);   // expand control rect by tracking slop
    
    // Assumes the port is correctly set up
    GetMouse (&thePoint);
    if (PtInRect (thePoint, &rectControl))
        theValue = CalcValueFromPoint (hControl, thePoint); // track new point point
    else
        theValue = gStartValue;     // snap to start pos
    
    if (theValue != GetControlValue (hControl)) // if we scrolled
    {
        SetClip (gSaveClip);
 
        gSaveValue = theValue;
        
        MoveScroll (hControl, theValue - GetControlValue (hControl));                                   // move the scroll bar and check edges
        ScrollDrawContent (pWindow, GetControlValue (ghHScrollBar), GetControlValue (ghVScrollBar));    // draw stuff in window
        
        gSaveValue = GetControlValue (hControl);
 
        GetClip (gSaveClip);
        ClipRect (&rectNull);
    }
}
 
// --------------------------------------------------------------------------
 
OSErr BeginThumbTracking (ControlRef hControl)  // start of thumb record values and save clip setting current clip to null
{
    Rect    rectNull = {0, 0, 0, 0};
    OSErr   theErr = noErr;
    Point   thePoint;
    
    gControl = hControl;
    gStartValue = GetControlValue (hControl);
    
    gValueSlop = 0;
    GetMouse (&thePoint);
    gValueSlop = GetControlValue (hControl) - CalcValueFromPoint (hControl, thePoint);  // delta from center
    
    gSaveClip = NewRgn ();
    GetClip (gSaveClip);
    ClipRect (&rectNull);
    
    return theErr;
}
 
// --------------------------------------------------------------------------
 
void DrawOneControlOffscreen (ControlHandle hControl)   // draw a control into our offscreen port
{
    GDHandle hGDSave;
    CGrafPtr pCGrafSave;
    Point ptSave;
    short i = (**hControl).contrlRfCon;
    
    GetGWorld (&pCGrafSave, &hGDSave);
    SetGWorld ((CGrafPtr) gpGWOffScreen, NULL);
    
    if (((gPtCntlPos [i].v) != (**hControl).contrlRect.top) || ((gPtCntlPos [i].h) != (**hControl).contrlRect.left)) 
    {   // reset control pos to original for offscreen update then restore it
        ptSave.v = (**hControl).contrlRect.top;
        ptSave.h = (**hControl).contrlRect.left;            
        MoveControl (hControl, gPtCntlPos [i].h, gPtCntlPos [i].v);
        DrawControlInCurrentPort (hControl); // draw in offscreen
        MoveControl (hControl, ptSave.h, ptSave.v);
    }
    else    // already in correct position
        DrawControlInCurrentPort (hControl); // draw in offscreen
        
    SetGWorld (pCGrafSave, hGDSave);
}
// --------------------------------------------------------------------------
 
void DoContentClick (WindowPtr pWindow, EventRecord * pEvent)
{
    GrafPtr pPortSave = NULL;
    Point pointClick;
    short partWindow = 0;
    ControlHandle hControlHit = NULL;
    ControlActionUPP myActionUPP = NewControlActionProc (ProcScroll);
    ControlActionUPP myThumbUPP = NewControlActionProc (ProcScrollThumbAction);
    
    GetPort (&pPortSave);
    SetPort ((GrafPtr) pWindow);
    
    pointClick = pEvent->where;
    GlobalToLocal (&pointClick);
    
    partWindow = FindControl(pointClick, pWindow, &hControlHit);
    if ((hControlHit == ghHScrollBar) || (hControlHit == ghVScrollBar))
    {
        gStartValue = GetControlValue (hControlHit);    
        switch (partWindow)
        {
            case kControlIndicatorPart:         // live scroll
                if ( BeginThumbTracking (hControlHit) == noErr )
                {
                    TrackControl ( hControlHit, pointClick, myThumbUPP);
                    EndThumbTracking ( );
                }
                break;
            case kControlUpButtonPart:          // scroll up
            case kControlDownButtonPart:        // scroll down
            case kControlPageUpPart:            // page up
            case kControlPageDownPart:          // page up
                partWindow = TrackControl (hControlHit, pointClick, myActionUPP);
                break;
            default:
                break;
        }
        if (hControlHit)
        {
            if (GetControlValue (hControlHit) - gStartValue) // if we really did move our scroll bar 
                InvalRect (&pWindow->portRect); // generate a window update
        }
    }
    else if (hControlHit)
    {
        TrackControl(hControlHit, pointClick, (ControlActionUPP) -1);
        DrawOneControlOffscreen (hControlHit);  // update our offscreen info
    }
 
    SetPort ((GrafPtr) pPortSave);
}
 
// --------------------------------------------------------------------------
 
void DoMenu (SInt32 menuResult)
{
    SInt16 theMenu;
    SInt16 theItem;
    Str255 daName;
    MenuRef theMenuHandle;
        
    theMenu = HiWord(menuResult);
    theItem = LoWord(menuResult);
    theMenuHandle = GetMenuHandle(theMenu);
 
    switch (theMenu)
    {
        case kMenuApple:
            switch (theItem)
            {
                default:
                    OpenDeskAcc(daName);
                    break;
            }
            break;
        case kMenuFile:
            switch (theItem)
            {
                case kFileQuit:
                    gDone = true;
                    break;
            }
            break;
    }
    HiliteMenu(0);
    DrawMenuBar();
}
 
// --------------------------------------------------------------------------
 
void DoEvent (void)
{
    EventRecord theEvent;
    SInt16 whatPart;
    SInt32 menuResult;
    WindowPtr whichWindow;
    SInt8 theKey;
    SInt8 theCode;
    Rect rectGrow;
    long grow;
    
    if (WaitNextEvent(everyEvent, &theEvent, gSleepTime, NULL))
    {
        switch (theEvent.what)
        {
            case mouseDown:
                whatPart = FindWindow(theEvent.where, &whichWindow);
                switch (whatPart)
                {
                    case inGoAway:
                        break;
                    case inMenuBar:
                        DrawMenuBar();
                        menuResult = MenuSelect(theEvent.where);
                        if (HiWord(menuResult) != 0)
                            DoMenu(menuResult);
                        break;
                    case inSysWindow:
                        SystemClick(&theEvent, whichWindow);
                        break;
                    case inContent:
                        if (whichWindow != FrontWindow ())
                            SelectWindow (whichWindow);
                        else
                            DoContentClick (whichWindow, &theEvent);
                        break;
                    case inDrag:
                        DragWindow (whichWindow, theEvent.where, &(**LMGetGrayRgn()).rgnBBox);
                        break;
                    case inGrow:
                        SetRect (&rectGrow, 100, 100, kOffScreenWidth + kScrollBarWidth, kOffScreenHeight + kScrollBarWidth);
                        grow = GrowWindow (whichWindow, theEvent.where, &rectGrow);
                        if (grow)
                        {
                            SizeWindow (whichWindow, grow & 0x0000FFFF, grow >> 16, true);
                            SetScrollParams (whichWindow);                  // reset scroll limits
                            AdjustScrollBar (gpWindow, ghVScrollBar);       // adjust and draw scroll bars
                            AdjustScrollBar (gpWindow, ghHScrollBar);
                            SetPort (whichWindow);
                            InvalRect (&whichWindow->portRect);             // redraw all
                        }
                        break;
                }
                break;
            case keyDown:
            case autoKey:
                theKey = theEvent.message & charCodeMask;
                theCode = (theEvent.message & keyCodeMask) >> 8;
                if ((theEvent.modifiers & cmdKey) != 0)
                {
                    menuResult = MenuKey(theKey);
                    if (HiWord(menuResult) != 0)
                        DoMenu (menuResult);
                }
                break;
            case updateEvt:
                BeginUpdate((WindowPtr) theEvent.message);
                DoUpdate((WindowPtr) theEvent.message);
                EndUpdate((WindowPtr) theEvent.message);
                break;
            case diskEvt:
                break;
            case osEvt:
                break;
 
            case kHighLevelEvent:
                AEProcessAppleEvent(&theEvent);
                break;
        }
    }
    else
    {
        // idle tasks
    }
}
 
// --------------------------------------------------------------------------
 
void DrawOffScreen (CGrafPtr pCGOffScreen)
{
    GDHandle hGDSave;
    CGrafPtr pCGrafSave;
    Rect rectSource = (pCGOffScreen->portRect);
    RGBColor rgbGray = {0xC000, 0xC800, 0xD000};
    short i;
    
    GetGWorld (&pCGrafSave, &hGDSave);
    SetGWorld (pCGOffScreen, NULL);
    
    // draw some background
    EraseRect (&rectSource);
    RGBForeColor (&rgbGray);
    PaintRect (&rectSource);    
    MoveTo (rectSource.left, rectSource.top);
    LineTo (rectSource.right, rectSource.bottom);
    MoveTo (rectSource.right, rectSource.top);
    LineTo (rectSource.left, rectSource.bottom);
    
    // draw all my controls
    for (i = 0; i < kNumControls; i++)
        DrawControlInCurrentPort (ghControls[i]); // draw in offscreen (controls should not be scrolled here)
 
    SetGWorld (pCGrafSave, hGDSave);
}
 
// --------------------------------------------------------------------------
 
void SetScrollParams (WindowPtr pWindow)    // set up scroll bars statics
{
    short windowHeight = pWindow->portRect.bottom - pWindow->portRect.top;
    short windowWidth = pWindow->portRect.right - pWindow->portRect.left;
    
    gScrollVThumbSize = windowHeight * (windowHeight - kScrollBarWidthAdjust - kScrollArrowWidth * 2) / kOffScreenHeight; // thumb size based on relative size of window to offscreen
    gScrollHThumbSize = windowWidth * (windowWidth - kScrollBarWidthAdjust - kScrollArrowWidth * 2) / kOffScreenWidth; // thumb size based on relative size of window to offscreen
 
    gTotalVSizeAdjust = ((kScrollArrowWidth * 2) + gScrollVThumbSize);  // amount that scroll bar is less than full height of window
    gTotalHSizeAdjust = ((kScrollArrowWidth * 2) + gScrollHThumbSize);  // amount that scroll bar is less than full height of window
}
 
// --------------------------------------------------------------------------
 
void AdjustScrollBar (WindowPtr pWindow, ControlHandle scrollBar)   // sets up scroll settings and thumb
{
    short totalSize, viewSize;
    short windowHeight = pWindow->portRect.bottom - pWindow->portRect.top;
    short windowWidth = pWindow->portRect.right - pWindow->portRect.left;
    
    // get rect to determine if we are vert or horiz
    if (scrollBar == ghVScrollBar)
    {   // vertical
        totalSize= kOffScreenHeight - (windowHeight - kScrollBarWidthAdjust); // total doc. height - visable doc
        viewSize= windowHeight + kScrollBarWidth; // vis. doc. height
        MoveControl (scrollBar, windowWidth - kScrollBarWidthAdjust, -1);
        SizeControl (scrollBar, kScrollBarWidth, windowHeight + 2 - kScrollBarWidthAdjust);
    }
    else
    {   // horizontal
        totalSize= kOffScreenWidth - (windowWidth - kScrollBarWidthAdjust); // total doc. width - visable doc
        viewSize= windowWidth + kScrollBarWidth; // vis. doc. width
        MoveControl (scrollBar, -1, windowHeight - kScrollBarWidthAdjust);
        SizeControl (scrollBar, windowWidth + 2 - kScrollBarWidthAdjust, kScrollBarWidth);
    }
    // set the min, max, and current value of the scroll bar
    SetControl32BitMinimum (scrollBar, 0);
    SetControl32BitMaximum (scrollBar, totalSize);
    SetControl32BitValue (scrollBar, GetControlValue (scrollBar));
    // set the scroll bar view size to create a proportional scroll box
    SetControlViewSize (scrollBar, viewSize);
}
 
// --------------------------------------------------------------------------
 
void InitToolbox(void)  // standard inits
{
    MenuHandle menu;
    SInt16 modifiers = 0;
    EventRecord event;
 
    MaxApplZone ();
 
    InitGraf((Ptr) &qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(nil);
    InitCursor();
    
    qd.randSeed =  TickCount();
 
    // init events
    EventAvail(everyEvent, &event);
    modifiers |= event.modifiers;
    EventAvail(everyEvent, &event);
    modifiers |= event.modifiers;
    EventAvail(everyEvent, &event);
    modifiers |= event.modifiers;
 
    // Init Menus
    menu = NewMenu (kMenuApple, "\p\024");          // new  apple menu
    InsertMenu (menu, 0);                           // add menu to end
    AppendResMenu(menu, 'DRVR');
    
    menu = NewMenu (kMenuFile, "\pFile");           // new menu
    InsertMenu (menu, 0);                           // add menu to end
    AppendMenu (menu, "\pQuit/Q");                  // add items
    
    DrawMenuBar();
} 
 
// --------------------------------------------------------------------------
 
Boolean SetUp (void) // set up windows, controls, off screen
{
    short i;
    MenuHandle hMenu;
    Rect rectSize;
 
    InitToolbox ();
 
    SetRect (&gRectWindowGbl, 0, 0, kWindowWidth, kWindowHeight);
    OffsetRect (&gRectWindowGbl, kWindowOffset, kWindowOffset);
    gpWindow = NewCWindow (NULL, &gRectWindowGbl, "\pControl Scroll Test", false, kWindowGrowDocumentProc, (WindowPtr) -1, false, 0);
    SetScrollParams (gpWindow);
    
    SetRect (&rectSize, -1, kWindowHeight - kScrollBarWidthAdjust, kWindowWidth + 1 - kScrollBarWidthAdjust, kWindowHeight + 1);
    ghHScrollBar = NewControl (gpWindow, &rectSize, "\p", false, 0, 0, 0, kControlScrollBarProc, 0);
    if (!ghHScrollBar)
        return false;
    AdjustScrollBar (gpWindow, ghHScrollBar);
    ShowControl (ghHScrollBar);
    
    SetRect (&rectSize, kWindowWidth - kScrollBarWidthAdjust, -1, kWindowWidth + 1, kWindowHeight + 1 - kScrollBarWidthAdjust);
    ghVScrollBar = NewControl (gpWindow, &rectSize, "\p", false, 0, 0, 0, kControlScrollBarProc, 0);
    if (!ghVScrollBar)
        return false;
    AdjustScrollBar (gpWindow, ghVScrollBar);
    ShowControl (ghVScrollBar);
 
    hMenu = NewMenu (kButtonMenuID, "\pButton");            // new menu
    AppendMenu (hMenu, "\pItem 1;Item 2;Item 3;Item 4");    // add items
    InsertMenu (hMenu, hierMenu);                           // add menu to end
 
    for (i = 0; i < kNumControls; i++)
    {
        short top = 10 + (abs (Random ()) % (kOffScreenHeight - kScrollBarWidth - 10 - 20));
        short left = 10 + (abs (Random ()) % (kOffScreenWidth - kScrollBarWidth - 10 - 75));
        SetRect (&rectSize, left, top, left + 75,  top + 20); 
        // NOTE: intial pos is stored in gPtCntlPos array to allow reference during scrolling
        ghControls [i] = NewControl (gpWindow, &rectSize, "\p", true, 0, kButtonMenuID, 0, popupMenuProc, i);
        if (!(ghControls [i]))
            return false;
        gPtCntlPos [i].v = rectSize.top;
        gPtCntlPos [i].h = rectSize.left;
    }
    
    SetRect (&rectSize, 0, 0, kOffScreenWidth, kOffScreenHeight);
    if (NewGWorld (&gpGWOffScreen, 32, &rectSize, NULL, NULL, 0) != noErr)
        return false;
        
    DrawOffScreen ((CGrafPtr) gpGWOffScreen);
    ShowWindow (gpWindow);
    return true;
}
 
// functions (external/public) ----------------------- ----------------------
 
int main (void)
{   
    if (SetUp ())
        while (!gDone) 
            DoEvent ();
    CleanUp ();
    return 0;
}