TweetFollow Us on Twitter

Scrollbars XCMD
Volume Number:5
Issue Number:3
Column Tag:HyperChat™

Related Info: Control Manager Quickdraw

XCMD Corner: Window Scrollbars

By Joe Zuffoletto, Apple Computer, Inc.

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

In Last Month's Episode...

Last month I showed you how to write an event-driven XCMD that displays a normal Macintosh window on top of HyperCard. I discussed some interesting human interface issues that come up when you do this, and I demonstrated how to communicate with HyperCard from an XCMD in order to do window updates and other “human interface maintenance” tasks. I also used the XCMD as a springboard for illustrating more general techniques, including how to be MultiFinder aware, how to do cursor tracking under MultiFinder, and how to cope elegantly with multiple-screen environments. Finally, I gave the window scroll bars that didn’t work, and I promised to “breathe life into them” this month, which I will do by adding code to the XCMD that takes a “snapshot” of the current card and displays it in the window. We can then scroll around and admire the snapshot until we either get bored, learn about scrolling, or both!

Scroll Bars!

The scroll bars are here!

The scroll bars are here!

Why should I care?

People have been scrolling for years....

Yes, but few people have taken the time and effort to explain scrolling to neophyte Mac programmers, who must either figure it out themselves, copy code from someone else, or avoid it completely. Why is this? I have a simple theory: People are ashamed of their scrolling code. In fact, some people might be afraid of their scrolling code. Scrolling code is ugly. It consists of gobs of source riddled with special cases and questionable algorithms. Nobody has found an elegant way to do it, so nobody wants to show their version of it to anybody else.

On top of all this, scrolling is implemented differently depending on the type of document being scrolled, be it bitmapped graphics, object-oriented graphics, text, icon lists, etc. While the basic rules stay the same, the special cases change and often multiply. Therefore, learning everything there is to know about scrolling is a daunting task indeed.

I certainly don’t know everything about scrolling, but I know enough to get you started if you’ve never done it before, and I can touch on a few fundamentals that may help you improve your scrolling code if you’ve written some already. In this article I will show how to scroll bitmapped graphics documents, including how to set up and manage an offscreen bitmap for fast, flicker-free scrolling. I will assume we have a static document that cannot be edited or moved around inside its window. I leave these more challenging cases as proverbial exercises for the reader.

If you haven’t done so already, read the Control Manager chapter of Inside Macintosh, Volume I so I don’t have to explain the Toolbox software behind controls. I will repeat some of the general material in that chapter, but I will not go into the data structures and other nuts-and-bolts information because there just isn’t room for it here.

Those of you who have never written scrolling code stand to benefit the most because, like I said, annotated examples of it don’t exactly abound. Unfortunately, the only thing worse than implementing scroll bars is explaining how the code works, so bear with me because there is a lot to cover and most of it is very dry. My plan is to first discuss the anatomy and arithmetic of scroll bars, then talk about all the special cases and implementation details. Finally, as a special bonus, I will describe how to set up and maintain an offscreen bitmap for a document you are scrolling.

This is a ton of material, but remember: No pain, no gain; no guts, no glory. Onward!

The Anatomy of Scroll Bars

Most of this section is a review of material in the Control Manager chapter of Inside Macintosh, Volume I, and of the Scroll Bars specification in Apple’s Human Interface Guidelines: The Apple Desktop Interface (Addison-Wesley, 1987) I repeat it here in order to fix terms and to lend continuity to the article.

Scroll bars consist of five parts, as illustrated in Figure 1. These parts are the up arrow, page up region, thumb, page down region, and down arrow. The names are the same for a horizontal scroll bar, with the left end being “up.”

Figure 1

The up and down arrows scroll the document arbitrarily small but equal distances in their respective directions. If you are scrolling text, for example, the arrows might scroll it one line at a time. If you are scrolling graphics, you might shift your document 5 to 10 pixels at a time depending on how fast and smooth you want the scrolling to be. In any event, the distance scrolled remains constant, regardless of window size.

Page up and page down scroll the document one “page” or “windowful” at a time. Most applications scroll slightly less than a windowful so users can keep track of where they are as they scroll through long documents. Obviously, the distance scrolled varies with window size.

Finally, the thumb can be dragged anywhere in the scroll bar; when it is released, the application scrolls to the corresponding position in the document. The distance scrolled depends only on the thumb displacement and is independent of window size.

Scroll bars can be either active, unhighlighted, or inactive. A scroll bar is active when the window that owns it is active and the document can be scrolled in the scroll bar’s direction; active scroll bars are illustrated in Figure 2a.

Figure 2a

If the owning window is active but the document cannot be scrolled (e.g., when the entire document is visible in the window), then the scroll bars are unhighlighted, and they appear as in Figure 2b.

Figure 2b

Finally, if the owning window is inactive, the scroll bars must be inactive and look like those in Figure 2c.

Figure 2c

Scroll Bar Arithmetic

You will find it much easier to write scrolling code if you can express scroll bar behavior in simple algebraic form. Good scrolling code, like QuickDraw, stands on a firm mathematical foundation. Building this foundation is best accomplished by drawing some pictures.

Figure 3 shows a large Macintosh window and a long, skinny document we wish to scroll. I have labeled important dimensions on the diagram; some of these will figure prominently in our algebraic expression of scrolling behavior.

Figure 3

I define docWidth and docHeight as the width and height of the document’s rectangle; visWidth and visHeight as the width and height of the portion of the document visible in the window; and dhGraf and dvGraf as the distances the document has been scrolled in the horizontal and vertical directions, respectively. dhGraf = dvGraf = 0 when the top left corners of the document and the window’s portRect coincide, and both of these variables increase as the document moves up and to the left relative to the window; i.e., as we scroll down and to the right. The units for all these variables are pixels.

These are all the variables we need to express the document’s position relative to the window. But we also need to express the thumbs’ positions relative to the top (or bottom) of the scroll bars. The thumbs are moved by changing their control values; if we find the algebraic link between the control values and the variables described above, life will be great.

Inside Macintosh tells us that every control has predefined minimum and maximum values and a current value; this information is stored in the ControlRecord data structure for that control, and is initialized by us when we create the control with NewControl. The current value, of course, is where the thumb happens to be relative to the minimum and maximum values, so our first decision is: what should the minimum and maximum values be? Although you can choose a scheme as convoluted as you desire, I’ll choose zero for the minimums, and docWidth and docHeight as the maximums of the horizontal and vertical scroll bars, respectively. I’ll also initialize both current values (thumbs) to zero, placing us at the top left corner of the document.

The Golden Ratio of Scrolling

From now on, I’ll simplify the discussion by considering only the vertical scroll bar; the exact same principles apply to the horizontal one. Here’s a quick exercise: dvGraf limits how far we can scroll up and down in the document. It’s smallest possible value is zero, when we are at the top of the document. What’s the largest value dvGraf can have? docHeight? No! The correct answer is docHeight - visHeight, because once the bottom of the document is visible above the top of the horizontal scroll bar, we don’t need to scroll down any more.

The largest value the thumb can have, however, is docHeight. At the top and bottom of the document, then, we have the following relationships:

--1

Top:  dvGraf  = 0
 vThumb = 0

Bottom: dvGraf  = docHeight - visHeight
 vThumb = docHeight

This suggests an important proportional relationship we must preserve as we scroll through the document:

--2

vThumb = (dvGraf * docHeight) /
               (docHeight - visHeight)

As a check, substituting dvGraf = 0 into the above equation gives vThumb = 0, and substituting dvGraf = docHeight - visHeight gives vThumb = docHeight. I call this the Golden Ratio of Scrolling, just because lots of high-falutin’ proportions in mathematics are called the Golden Ratio of something or another. My Golden Ratio will ensure that you scroll your documents the way users expect you to.

We now have enough mathematical background to implement scroll bars. Before diving into the code, however, let’s learn about offscreen bitmaps and blitting, which will make our scrolling smooth and fast.

Bits, Blits, and CopyBits

You may have noticed that some applications have smooth, flicker-free scrolling while others have the choppy, flicker-ful variety. What you are witnessing is the almost total supremacy of CopyBits and blitting over ScrollRect in the scrolling arena. The basic idea is that CopyBits lets you blast an offscreen copy of your document to the screen immediately after you scroll (this is called blitting), while ScrollRect waits around for update events to be handled. On the downside, blitting takes a lot more memory, and you really have to be clever when you try to use it with large documents or large displays.

Scott Knaster gives a great explanation of blitting and offscreen bitmaps in his book Macintosh Programming Secrets (Addison-Wesley, 1988); I’m going to condense a similar discussion into a much smaller space here. If you get confused, check out pages 190-208 of Scott’s book.

Let’s take a concrete example. My XCMD takes a snapshot of HyperCard’s card window, adds 16 pixels of white padding to the right and bottom edges, and stores the picture into an offscreen bitmap, which is just a nonrelocatable buffer in memory with a bitmap’s structure. We will use NewPtr to allocate memory for the bitmap, so we need to determine how many bytes of memory we need. The card window is 342 pixels tall by 512 wide; adding 16 to both dimensions gives a rectangle 358 x 528 pixels. First we calculate rowBytes using Knaster’s incredibly bizarre but effective formula:

rowBytes = (((pict.right - pict.left - 1) div 16) + 1) * 2
             = (((528 - 0 - 1) div 16) + 1) * 2
             = 66

From here on out things are much more intuitive:

bufferSize = (pict.bottom - pict.top) * rowBytes
              = 358 * 66
              = 23628 bytes

So to create our bitmap we make the following calls:

VAR myBits:  BitMap;

myBits.rowBytes := 66;
myBits.baseAddr := NewPtr(23628);
{ do error checking, of course }
SetRect(myBits.bounds,0,0,528,358);

All that’s left is to copy a picture of the card window into the bitmap; see procedure GetHCBitMap in the source listing for details.

Scrolling and Blitting

Now that our offscreen bitmap is set up, it’s time link it to the scroll bars. This is where that great eclectic concept of blitting comes in.

Quick and efficient blitting is not accomplished by blasting the entire offscreen image to the screen and clipping to the portRect (minus the scroll bars) of the target window. If you did that and Bill Atkinson saw your code, he would say, “No, no, no, that’s dumb.”

Instead, Bill would suggest that you copy a piece of the offscreen image just big enough to fit in your window. Assuming you are scrolling around and want to see different pieces of the image, you need a scheme for deciding which piece to copy. This is where the concept of a blit rectangle comes in.

(By the way, Bill Atkinson probably knows how to compress an arbitrary 512 by 342 pixel bitmap into three bits, so he might suggest something even more efficient. If he does, listen carefully and write it all down, because you’ll never figure it out yourself.)

A blit rectangle is an image of the onscreen window’s portRect (minus the scroll bars) superimposed on the offscreen bitmap; see Figure 4. It is visWidth wide by visHeight tall. If you resize the onscreen window, you resize the blit rectangle accordingly, except that the blit rectangle can never be larger than the offscreen bitmap or you will blast the random garbage that lies beyond into your onscreen window. Similarly, when you scroll the onscreen window, you slide the blit rectangle the appropriate distance and direction, always staying within the bounding rectangle of the offscreen bitmap.

Figure 4

After you have resized or moved the blit rectangle as described above, you copy its contents to the onscreen window with CopyBits. In the case of scrolling, the copy is performed immediately, but in the cases of zooming or resizing, the copy is performed when update events are handled. Summarizing, the sequence of steps is:

• scroll onscreen window and move thumb

• slide blit rectangle proper distance and direction over offscreen bitmap (don’t let it slide off, however!)

• copy contents of blit rectangle to onscreen window immediately

-or-

• zoom or resize onscreen window

• zoom or resize blit rectangle, up to size of offscreen bitmap

• copy contents of blit rectangle to onscreen window at update time

That’s it for the concepts behind scrolling and blitting. Now we can look at the code and see how this stuff is done in practice, complete with refCon data structures, special cases, and other nasty ugliness.

First, a Pair of Data Structures

In this section and the ones that follow, I’m going to walk through the code that was stubbed out of last month’s listing. For your convenience, however, I’m including the entire source of the Window XCMD.

Starting with the TYPE declarations, notice the two data structures I’ve defined, OffScrRecord and ScrollRecord (wonder no more, newcomers to Mac programming, you’re about to find out what on earth refCon fields are used for! ). OffScrRecord contains window-specific information, including a pointer to the grafPort containing the offscreen bitmap for the window, the width and height of the document in the bitmap, the current size of the blit rectangle, and current scroll state of the document. A handle to this information is stored in the window’s refCon field, and I continually update the OffScrRecord via this handle in my scrolling code.

ScrollRecord contains two rather cryptic fields that I use for human interface purposes; I’ll explain them later. In this program, I create only one ScrollRecord on the heap and share it among all my scroll bars. You’ll soon understand why this is possible.

Allocating space on the heap for these data structures is accomplished with a parentheses-rich Pascal statement of the form:

myBlobHandle := BlobHandle(NewHandle(SizeOf(BlobRecord)));

How to Initialize Scroll Bars

Let’s skip down to the main program, and I’ll touch on the procedures used for initializing the scroll bars and offscreen bitmap.

After we’ve put the owning window on the heap, we go ahead and initialize its scroll bars. First we allocate space for the ScrollRecord, because if there’s no room for it we have to quit. Assuming there is space, we grab our handle to it and initialize the ScrollRecord’s fields. Again, I’ll get to what they mean in a minute.

Next we call CreateHScrollBar and CreateVScrollBar, functions of my own design that allocate and return ControlHandles to the respective scroll bars for a given window. I recommend you name your controls as shown because it helps out in debugging.

HiliteScrollBars is called next; it checks the size of the window against the size of the document and unhighlights the appropriate scroll bar if the window is bigger in a given direction (see Figure 2b). Otherwise it checks the scrolled state of the document (given by dvGraf and dhGraf; see Figure 3) and positions the thumbs accordingly. Notice the use of the Golden Ratio. Notice also that HiliteScrollBars is called whenever:

• the window is made active

• the window is resized or zoomed

• the window is updated

After the window and scroll bars are drawn, DrawContents blasts the bits inside the blit rectangle to the screen. The offscreen bitmap has already been initialized by GetHCBitMap, and the blit rectangle has been initialized to the starting window size by the InitBlit procedure.

Scrolling

If we got this far, we should be zipping along in our main event loop, ready to handle events to our scroll bars. Remember from Inside Macintosh that the scroll bars sit on the content region of the window, so when we get a mouseDown there, we first call FindControl, passing it the location of the mouseDown in global coordinates, to find out which control, if any, was hit.

One possibility is that the user grabs the thumb, in which case we call ScrollWithThumb. This routine uses the control titles to determine which of the two thumbs, horizontal or vertical, was grabbed; then it records the start value of the thumb before the thumb is moved. At this point TrackControl is called to handle the dragging of the thumb; when the thumb is released, the ball is in our court again and we immediately record the thumb’s end value. The difference between the start and end values (given by the LONGINT amountToScroll) is munged through a rearranged version of the Golden Ratio, and the result tells us how far to scroll the document. The actual scrolling is accomplished by ScrollContents, which first slides the blit rectangle to the proper location on the offscreen bitmap before calling DrawContents to blast the bits to the screen.

If the user clicks instead on one of the arrows or on a page up/page down region, things get a little more complicated. Let me begin by explaining a feature I call “accelerating thumbs.”

Today’s Macintoshes, of course, are much faster than their early predecessors, which is wonderful. Because of this, however, it is sometimes very difficult to use scroll bars with any precision. Ever try to scroll a list box one line at a time on a Mac II? It requires the reflexes of a fighter pilot to scroll less than a windowful when you hit the up or down arrows. To solve this annoying problem I invented two-speed accelerating thumbs, which scroll slowly at first but switch to top speed if you hold the mouse down long enough. The page up and page down regions work the same way.

Accelerating thumbs are the reason behind the ScrollRecord data structure. The fields of this data structure work in conjunction with procedure MyScroll, which is the actionProc TrackControl calls repeatedly when the user holds the mouse button down in the scroll arrows or page up/page down regions (see pp. I 323-324 of Inside Macintosh). Each time the user clicks in one of these areas, heldDown is initialized to zero and goFast to true before TrackControl is called. TrackControl in turn calls MyScroll, and calls it repeatedly until the mouse button is released. Each pass through MyScroll scrolls the document one notch, the size of the notch depending on whether an arrow or a page region is being pressed. It also increments heldDown by one and compares it to a preset switching value, which in this case is 2. While heldDown is less than 2 we scroll slowly, but when it is greater than 2, goFast is set to true and we switch into high gear. When the user releases the mouse button, TrackControl is finished and the process re-initialized next time a scroll bar is hit.

MyScroll is definitely the workhorse procedure for the “hard part” of scrolling, which is handling the arrows and page regions. I could discuss its workings in great detail here but that would be no more informative than reading the source comments. Therefore, I refer you to the source comments. Just be aware that MyScroll does the math and exception handling for the scroll bars, making extensive use of the Golden Ratio, then calls ScrollContents to adjust the blit rectangle accordingly. Finally, ScrollContents calls DrawContents to blit the stuff to the screen.

Notice that procedure MyScroll and the routines it calls are positioned outside of the Window procedure. This is because MyScroll is an actionProc, and the linker can’t assign a procPtr to it if it is nested. This violates the typical layout of an XCMD but is perfectly legal, as long as you don’t declare global variables.

Wrapping Up

If you study this article and the accompanying code listing carefully, you will gain a clear understanding of the basics of scrolling, offscreen bitmaps, and blitting. If you are a relative newcomer to Mac programming, you should congratulate yourself for learning one of the most challenging aspects of the Apple desktop interface in record time.

Scrolling has some more knots in it that I didn’t even cover. For example, what if the user edits the document being scrolled, possibly changing its length or width? What if you are scrolling a combination of text and graphics? What if your document is too big to fit into an offscreen bitmap? And how about auto-scrolling (when the user drags beyond an edge of the window)? The list goes on and on.

None of these knots are remote possibilities; if your application is at all useful they are inevitable! But untangling them is much easier if you understand the basics I’ve covered in this article. Take a look at what other applications are doing. Then draw some pictures, remember the Golden Ratio, and everything will be all right.

Happy scrolling!

(*

© 1988, Apple Computer, Inc.
All rights reserved.

Window2.p:  A HyperCard XCMD in MPW Pascal 2.0.2 
           by Joe Zuffoletto
           Version 1.0, 29 June 1988

Form:      Window title,top,left,bottom,right

Example:   window “My Window”,50,100,300,400

Notes:     Window puts up a standard document window with
           scroll bars. The window can be dragged, resized,
           zoomed, and closed.

           Command-W is supported for closing the window.
           Command-spacebar toggles the menubar on and off,
           as in HyperCard. If you try to draw the window’s 
           title bar off the screen or under the menubar, 
           Window will abort with an error message. Error 
           messages can be examined by looking at 
           HyperCard’s global variable “the result” after 
           calling Window.

           Window is MultiFinder friendly and works with 
           HyperCard 1.2 or later. It supports multiple 
           displays on the Mac II as well. 

           Window takes a snapshot of the HyperCard card
           window and displays it like a MacPaint document.
           You can scroll up and down, etc. This is just
           for demonstration and amusement. You must supply
           your own code for displaying whatever you want
           to display in the window.
------------------------------------------------------------

To compile and link this file using MPW Pascal 2.0.2, select the following 
lines and press ENTER:

Pascal Window2.p
link   -o “Hard Disk”:HyperCard:”HyperCard Stacks”:Home 
       -rt XCMD=2000 -sn Main=Window 
       Window2.p.o {MPW}Libraries:Interface.o 
       {MPW}PLibraries:PasLib.o 
       -m ENTRYPOINT

Use other link files as necessary.

The above link directives install the XCMD resource into the Home stack. 
You can substitute the name of any stack you want; be sure to provide 
the correct pathname. Also, make sure the target stack already has a 
resource fork or it won’t work. You can create an empty resource fork 
in a stack
with ResEdit.
------------------------------------------------------------
*)

{$R-}

{$S Window}

UNIT DummyUnit;

INTERFACE

USES
  MemTypes,QuickDraw,OSIntf,ToolIntf,PasLibIntf,HyperXCmd;

PROCEDURE EntryPoint(paramPtr:XCmdPtr);

IMPLEMENTATION

TYPE Str31 = String[31];

     {Attach to refCon of a window}
     OffScrHandle = ^OffScrRecPtr;
     OffScrRecPtr = ^OffScrRecord;
     OffScrRecord = RECORD
                      gPort: GrafPtr; {offscreen grafPort}
                      docWidth: INTEGER;
                      docHeight: INTEGER;
                      blitRect: Rect;  {blit rectangle}
                      blitWidth: INTEGER;
                      blitHeight: INTEGER;
                      dhGraf: LONGINT;
                      dvGraf: LONGINT;
                    END;

     {Attach to refCon of a scroll bar}
     ScrollHandle = ^ScrollPtr;
     ScrollPtr = ^ScrollRecord;
     ScrollRecord = RECORD
                      heldDown: INTEGER; {see MyScroll}
                      goFast: BOOLEAN;
                    END;

PROCEDURE Window(paramPtr:XCmdPtr);FORWARD;

PROCEDURE EntryPoint(paramPtr:XCmdPtr);
BEGIN
  Window(paramPtr);
END;

FUNCTION Min(int1,int2:INTEGER): INTEGER;
{Return the smaller of two integers.}
BEGIN
  IF (int1 <= int2) THEN
    Min := int1
  ELSE
    Min := int2;
END; {Min}

PROCEDURE InitBlit(theWindow:WindowPtr);
{Initialize the blit rectangle so it is in the upper left
 corner of the offscreen bitmap and so it is the same size
 as the target window onscreen.}
VAR
  myOffScrHandle: OffScrHandle;
BEGIN
  myOffScrHandle := OffScrHandle(GetWRefCon(theWindow));
  MoveHHi(Handle(myOffScrHandle));
  HLock(Handle(myOffScrHandle));
  WITH myOffScrHandle^^ DO
  BEGIN
    dhGraf := 0;
    dvGraf := 0;

    {Make sure blit rectangle doesn’t hang off edges of
     offscreen bitmap, just in case window is larger.}

    blitWidth := Min((theWindow^.portRect.right - 15 -
                      theWindow^.portRect.left),docWidth);
    blitHeight := Min((theWindow^.portRect.bottom - 15 -
                       theWindow^.portRect.top),docHeight);
    SetRect(blitRect,gPort^.portBits.bounds.left,
                     gPort^.portBits.bounds.top,
                     gPort^.portBits.bounds.left + blitWidth,
                     gPort^.portBits.bounds.top + blitHeight);
  END;
  HUnlock(Handle(myOffScrHandle));
END; {InitBlit}

PROCEDURE InvalContents(theWindow:WindowPtr;
                        theOldSize:Rect);
{Perform “intelligent” window updating; i.e., if 
 the window is grown, invalidate for the update 
 event only the part that wasn’t visible before.}
 VAR
  myOffScrHandle: OffScrHandle;
  myWindowRect,windowRect: Rect;
  tallRect,wideRect: Rect;
  hWhiteSpace,vWhiteSpace: INTEGER;
  hBlitOffset,vBlitOffset: INTEGER;
  myDocWidth,myDocHeight: INTEGER;
  newDhGraf,newDvGraf: LONGINT;
  tempDH,tempDV: LONGINT;
BEGIN
  hBlitOffset := 0;
  vBlitOffset := 0;
  myWindowRect := theWindow^.portRect;
  myOffScrHandle := OffScrHandle(GetWRefCon(theWindow));
  MoveHHi(Handle(myOffScrHandle));
  HLock(Handle(myOffScrHandle));
  WITH myOffScrHandle^^ DO
  BEGIN
    tempDH := dhGraf;
    tempDV := dvGraf;
    myDocWidth := docWidth;
    myDocHeight := docHeight;
  END;

  {If we have scrolled to the bottom and/or right edges
   of the document and then grow the window, the document
   will “follow” the window so we don’t just expose a lot
   of white space. The same thing will happen if we zoom the
   window to a larger size. In either case, we need to 
   invalidate the entire content region.}

  {Is any white space being exposed in horizontal 
   direction?}

  hWhiteSpace := (myWindowRect.right -
                  myWindowRect.left - 15) -
                  (myDocWidth - tempDH);

  IF (hWhiteSpace > 0) THEN
  BEGIN
    {If so, how much?}

    newDhGraf := tempDH - hWhiteSpace;

    {Move the document that distance, but not beyond the 
     point where its left edge is flush with the left edge 
     of the window.}

    IF newDhGraf < 0 THEN
      newDhGraf := 0;

    {Adjust blit rectangle accordingly.}

    hBlitOffset := INTEGER(tempDH - newDhGraf);
    tempDH := newDhGraf;
  END; {IF hWhiteSpace}

  {Repeat for vertical direction.}

  vWhiteSpace := (myWindowRect.bottom -
                  myWindowRect.top - 15) -
                  (myDocHeight - tempDV);
  IF (vWhiteSpace > 0) THEN
  BEGIN
    newDvGraf := tempDV - vWhiteSpace;
    IF newDvGraf < 0 THEN
      newDvGraf := 0;
    vBlitOffset := INTEGER(tempDV - newDvGraf);
    tempDV := newDvGraf;
  END; {IF vWhiteSpace}

  {Save what we’ve learned to the data structure.}

  WITH myOffScrHandle^^ DO
  BEGIN
    dhGraf := tempDH;
    dvGraf := tempDV;
    blitWidth := Min((myWindowRect.right - myWindowRect.left - 15),
                     (myDocWidth - dhGraf));
    blitHeight := Min((myWindowRect.bottom - myWindowRect.top - 15),
                      (myDocHeight - dvGraf));
    SetRect(blitRect,blitRect.left - hBlitOffset,
                     blitRect.top - vBlitOffset,
                     blitRect.left + blitWidth - hBlitOffset,
                     blitRect.top + blitHeight - vBlitOffset);
    blitWidth := blitRect.right - blitRect.left;
    blitHeight := blitRect.bottom - blitRect.top;
  END;
  HUnlock(Handle(myOffScrHandle));
  
  {If the document has followed the window, invalidate entire
   content region.}
   
  IF ((hBlitOffset <> 0) OR (vBlitOffset <> 0)) THEN
  BEGIN
    EraseRect(theWindow^.portRect);
    InvalRect(theWindow^.portRect);
  END
  ELSE

  {Otherwise invalidate only the new white space.}

  BEGIN
    SetRect(tallRect,theOldSize.right,
                     theWindow^.portRect.top,
                     theWindow^.portRect.right,
                     theWindow^.portRect.bottom);
    SetRect(wideRect,theWindow^.portRect.left,
                     theOldSize.bottom,
                     theOldSize.right,
                     theWindow^.portRect.bottom);
    EraseRect(tallRect);
    EraseRect(wideRect);
    InvalRect(tallRect);
    InvalRect(wideRect);
  END; {IF hBlitOffset}
END; {InvalContents}

PROCEDURE DrawContents(theWindow:WindowPtr);
{Blasts contents of updated blit rectangle to screen.}
VAR
  myWindowRect,windowRect: Rect;
  myOffScrHandle: OffScrHandle;
BEGIN
  myOffScrHandle := OffScrHandle(GetWRefCon(theWindow));
  myWindowRect := theWindow^.portRect;
  MoveHHi(Handle(myOffScrHandle));
  HLock(Handle(myOffScrHandle));
  SetRect(windowRect,myWindowRect.left,
                     myWindowRect.top,
                     myWindowRect.left + myOffScrHandle^^.blitWidth,
                     myWindowRect.top + myOffScrHandle^^.blitHeight);
  ClipRect(windowRect);
  CopyBits(myOffScrHandle^^.gPort^.portBits,theWindow^.portBits,
           myOffScrHandle^^.blitRect,windowRect,srcCopy,NIL);
  HUnlock(Handle(myOffScrHandle));
END; {DrawContents}

PROCEDURE ScrollContents(theWindow:WindowPtr;
                           dh,dv:INTEGER);
VAR
  myOffScrHandle: OffScrHandle;
  leftSpace,topSpace: INTEGER;
  rightSpace,bottomSpace: INTEGER;
BEGIN
  myOffScrHandle := OffScrHandle(GetWRefCon(theWindow));
  MoveHHi(Handle(myOffScrHandle));
  HLock(Handle(myOffScrHandle));

  {Calculate space between edges of blitRect and edges 
   of the bitmap.}

  WITH myOffScrHandle^^ DO
  BEGIN
    leftSpace := gPort^.portBits.bounds.left - blitRect.left;
    topSpace := gPort^.portBits.bounds.top - blitRect.top;
    rightSpace := gPort^.portBits.bounds.right - blitRect.right;
    bottomSpace := gPort^.portBits.bounds.bottom - blitRect.bottom;
  END;
 
  {Then move blitRect, but not past the edge of the bitmap.}
 
  IF (dv = 0) THEN
    IF (dh > 0) THEN {moving to the right}
      IF (rightSpace > dh) THEN
        OffsetRect(myOffScrHandle^^.blitRect,dh,dv)
      ELSE
      BEGIN
        dh := rightSpace;
        OffsetRect(myOffScrHandle^^.blitRect,dh,dv);
      END
      ELSE IF (leftSpace > dh) THEN {moving to the left}
      BEGIN
        dh := leftSpace;
        OffsetRect(myOffScrHandle^^.blitRect,dh,dv);
      END
      ELSE
        OffsetRect(myOffScrHandle^^.blitRect,dh,dv)
  ELSE IF (dv > 0) THEN {moving down}
    IF (bottomSpace > dv) THEN
      OffsetRect(myOffScrHandle^^.blitRect,dh,dv)
    ELSE
    BEGIN
      dv := bottomSpace;
      OffsetRect(myOffScrHandle^^.blitRect,dh,dv);
    END
  ELSE IF (topSpace > dv) THEN {moving up}
  BEGIN
    dv := topSpace;
    OffsetRect(myOffScrHandle^^.blitRect,dh,dv);
  END
  ELSE
    OffsetRect(myOffScrHandle^^.blitRect,dh,dv);

  {Save actual distances moved to data structure.}
 
  WITH myOffScrHandle^^ DO
  BEGIN
    dhGraf := dhGraf + dh;
    dvGraf := dvGraf + dv;
  END;
  HUnlock(Handle(myOffScrHandle));

  {Shoot new contents of blit rectangle to screen.}

  DrawContents(theWindow);
END; {ScrollContents}

PROCEDURE MyScroll(theControl:ControlHandle;
                     partCode:INTEGER);
VAR
 myOffScrHandle: OffScrHandle;
 myScrollHandle: ScrollHandle;
 dh,dv: LONGINT;
 windowFull: LONGINT;
 myCtlValue: LONGINT;
 visHeight,visWidth: LONGINT;
 dontCare: LONGINT;
 tempDH,tempDV: LONGINT;
 startValue: INTEGER;
 myDocWidth,myDocHeight: INTEGER;
 direction: Str255;
 theWindow: WindowPtr;
BEGIN
  theWindow := theControl^^.contrlOwner;
  myOffScrHandle := OffScrHandle(GetWRefCon(theWindow));
  MoveHHi(Handle(myOffScrHandle));
  HLock(Handle(myOffScrHandle));

  {Make local copies of data structure items.}

  WITH myOffScrHandle^^ DO
  BEGIN
    tempDH := dhGraf;
    tempDV := dvGraf;
    myDocWidth := docWidth;
    myDocHeight := docHeight;
  END;
  HUnlock(Handle(myOffScrHandle));

  {Initialize more variables for our formulas.}
 
  visHeight := theWindow^.portRect.bottom - 15 - 
               theWindow^.portRect.top;
  visWidth := theWindow^.portRect.right - 15 -
              theWindow^.portRect.left;
  startValue := GetCtlValue(theControl);
  GetCTitle(theControl,direction);
  myScrollHandle := ScrollHandle(GetCRefCon(theControl));
  MoveHHi(Handle(myScrollHandle));
  HLock(Handle(myScrollHandle));

  {Implementation of two-speed, accelerating scroll bars.}

  WITH myScrollHandle^^ DO
  BEGIN
    heldDown := heldDown + 1;
    IF (heldDown > 2) THEN
      goFast := TRUE;
  END; {WITH}

  CASE partCode OF
    inUpButton:
    BEGIN
      {Don’t scroll up if already at top!}
      IF (startValue > 0) THEN
      BEGIN
        IF (direction = ‘MyVert’) THEN
        BEGIN
          IF (visHeight >= myDocHeight) THEN
          BEGIN
            setCtlValue(theControl,0);
            ScrollContents(theWindow,0,-tempDV);
          END
          ELSE
          BEGIN
            {Scroll up five pixels}
            dv := tempDV - 5;

            {Set new thumb value using Golden Ratio.}

            myCtlValue := LONGINT((dv * myDocHeight) DIV
                                  (myDocHeight - visHeight));
            ClipRect(theWindow^.portRect);
            setCtlValue(theControl,INTEGER(myCtlValue));

            {Update blit rectangle, blast document to screen.}

            ScrollContents(theWindow,0,-5);
          END; {IF visHeight...ELSE}
        END
        ELSE
        BEGIN
         {Repeat for horizontal direction.}
          IF (visWidth >= myDocWidth) THEN
          BEGIN
            setCtlValue(theControl,0);
            ScrollContents(theWindow,-tempDH,0);
          END
          ELSE
          BEGIN
            dh := tempDH - 5; 
            myCtlValue := LONGINT((dh * myDocWidth) DIV
                                  (myDocWidth - visWidth));
            ClipRect(theWindow^.portRect);
            setCtlValue(theControl,INTEGER(myCtlValue));
            ScrollContents(theWindow,-5,0);
          END; {IF visWidth...ELSE}
        END; {IF direction...ELSE}
      END; {IF startValue}
      IF (myScrollHandle^^.goFast = FALSE) THEN
        Delay(10,dontCare);
    END; {inUpButton}

    {Pattern is identical for down button, page up,
     and page down regions.}
 
    inDownButton:
    BEGIN
      IF (direction = ‘MyVert’) THEN
      BEGIN
        IF (startValue < myDocHeight) THEN
        BEGIN
          dv := tempDV + 5; 
          myCtlValue := LONGINT((dv * myDocHeight) DIV
                                (myDocHeight - visHeight));
          ClipRect(theWindow^.portRect);
          setCtlValue(theControl,INTEGER(myCtlValue));
          ScrollContents(theWindow,0,5);
        END; {IF startValue}
      END
      ELSE
      BEGIN
        IF (startValue < myDocWidth) THEN
        BEGIN
          dh := tempDH + 5; 
          myCtlValue := LONGINT((dh * myDocWidth) DIV
                                (myDocWidth - visWidth));
          ClipRect(theWindow^.portRect);
          setCtlValue(theControl,INTEGER(myCtlValue));
          ScrollContents(theWindow,5,0);
        END; {IF startValue}
      END; {IF direction... ELSE}
      IF (myScrollHandle^^.goFast = FALSE) THEN
        Delay(10,dontCare);
    END; {inDownButton}

    inPageUp:
    BEGIN
      IF (startValue > 0) THEN
      BEGIN
        IF (direction = ‘MyVert’) THEN
          BEGIN
            IF (visHeight >= myDocHeight) THEN
            BEGIN
              setCtlValue(theControl,0);
              ScrollContents(theWindow,0,-tempDV);
            END
            ELSE
            BEGIN
              windowFull := visHeight - 5;
              dv := tempDV - windowFull;
              IF (dv < 0) THEN
                dv := 0;
              myCtlValue := LONGINT((dv * myDocHeight) DIV
                                    (myDocHeight - visHeight));
              ClipRect(theWindow^.portRect);
              setCtlValue(theControl,INTEGER(myCtlValue));
              ScrollContents(theWindow,0,-windowFull);
            END; {IF visHeight...ELSE}
          END {IF direction}
          ELSE
          BEGIN
            IF (visWidth >= myDocWidth) THEN
            BEGIN
              setCtlValue(theControl,0);
              ScrollContents(theWindow,-tempDH,0);
            END
            ELSE
            BEGIN
              windowFull := visWidth - 5;
              dh := tempDH - windowFull;
              IF (dh < 0) THEN
                dh := 0;
              myCtlValue := LONGINT((dh * myDocWidth) DIV
                                    (myDocWidth - visWidth));
              ClipRect(theWindow^.portRect);
              setCtlValue(theControl,INTEGER(myCtlValue));
              ScrollContents(theWindow,-windowFull,0);
            END; {IF visWidth...ELSE}
          END; {IF direction... ELSE}
          IF (myScrollHandle^^.goFast = FALSE) THEN
            Delay(10,dontCare);
      END; {IF startValue}
    END; {inPageUp}

    inPageDown:
    BEGIN
      IF (direction = ‘MyVert’) THEN
      BEGIN
        IF (startValue < myDocHeight) THEN
        BEGIN
          windowFull := visHeight - 5;
          dv := tempDV + windowFull;
          myCtlValue := LONGINT((dv * myDocHeight) DIV
                                (myDocHeight - visHeight));
          ClipRect(theWindow^.portRect);
          setCtlValue(theControl,INTEGER(myCtlValue));
          ScrollContents(theWindow,0,windowFull);
        END; {IF startValue}
      END {IF direction}
      ELSE
      BEGIN
        IF (startValue < myDocWidth) THEN
        BEGIN
          windowFull := visWidth - 5;
          dh := tempDH + windowFull;
          myCtlValue := LONGINT((dh * myDocWidth) DIV
                                (myDocWidth - visWidth));
          ClipRect(theWindow^.portRect);
          setCtlValue(theControl,INTEGER(myCtlValue));
          ScrollContents(theWindow,windowFull,0);
        END; {IF startValue}
      END; {IF direction... ELSE}
      IF (myScrollHandle^^.goFast = FALSE) THEN
        Delay(10,dontCare);
    END; {inPageDown}

  END; {CASE partCode}
  HUnlock(Handle(myScrollHandle));
  ClipRect(theWindow^.portRect);
  {so TrackControl can unhilite arrows when mouse is released}
END; {MyScroll}

PROCEDURE Window(paramPtr:XCmdPtr);
CONST
  minParamCount     =  5;
  smallestHeight    =  100;
  smallestWidth     =  100;
  _WaitNextEvent    =  $A860;
  _Unimplemented    =  $A89F;
  active            =  0;
  inactive          =  255;
  MouseMovedEvt     =  $FA;
  SuspendResumeEvt  =  $01;
  SuspendEventMask  =  $1;
  ConvertScrapMask  =  $2;
  browseTool        =  6069;
  HCWidth           =  512;
  HCHeight          =  342;
  padding           =  16;
VAR
  toolVis,patVis:              BOOLEAN;
  msgVis,fatVis:               BOOLEAN;
  hasWaitNextEvent:            BOOLEAN;
  inBackGround,smallScreen:    BOOLEAN;
  DoneFlag,HaveEvent:          BOOLEAN;
  wTop,wLeft,wBottom,wRight:   INTEGER;
  partCode,controlCode:        INTEGER;
  largestHeight,largestWidth:  INTEGER;
  dummy,charCode:              INTEGER;
  screenWidth,screenHeight:    INTEGER;
  myDocWidth,myDocHeight:      INTEGER;
  eventPoint:                  Point;
  wRect,screenRect,dragRect:   Rect;
  winSizeLimits:               Rect;
  oldSize:                     Rect;
  newSize,dontCare:            LONGINT;
  envError:                    OSErr;
  cursorRgn:                   RgnHandle;
  hScroll,vScroll:             ControlHandle;
  whichControl:                ControlHandle;
  myWindow,whichWindow:        WindowPtr;
  fatBitsWindow:               WindowPtr;
  HCrefresh:                   Str31;
  wT,wL,wB,wR,wTitle:          Str255;
  toolStr,patStr,msgStr:       Str255;
  widthStr,heightStr:          Str255;
  myBits:                      BitMap;
  theEnv:                      SysEnvRec;
  myEvent:                     EventRecord;
  wRecord:                     WindowRecord;
  HCPort:                      GrafPtr;
  theOffScrHandle:             OffScrHandle;
  theScrollHandle:             ScrollHandle;
  myOffScr:                    OffScrRecord;
  myScrollRecord:              ScrollRecord;
 
  FUNCTION TrapAvailable(tNumber: INTEGER; tType:
                           TrapType):BOOLEAN;
  {Check to see if a given trap is implemented.}
  BEGIN
    TrapAvailable := NGetTrapAddress(tNumber, tType) <> 
                     GetTrapAddress(_Unimplemented);
  END; {TrapAvailable}

  FUNCTION CreateHScrollBar(theWindow:WindowPtr;
                           theValue,theMin,theMax:INTEGER;
                           theRefCon:LONGINT):ControlHandle;
 {Allocate and draw a horizontal scroll bar in theWindow.
   Return a controlHandle to the scroll bar.}
  VAR
    myWindowRect,hScrRect:  Rect;
  BEGIN
    SetPort(theWindow);
    myWindowRect := theWindow^.portRect;
    SetRect(hScrRect,myWindowRect.left -1,
                     myWindowRect.bottom - 15,
                     myWindowRect.right - 14,
                     myWindowRect.bottom + 1);
    CreateHScrollBar := NewControl(theWindow,hScrRect,
                                   ‘MyHoriz’,TRUE,
                                   theValue,theMin,theMax,
                                   scrollBarProc,theRefCon);
  END; {CreateHScrollBar}

  FUNCTION CreateVScrollBar(theWindow:WindowPtr;
                           theValue,theMin,theMax:INTEGER;
                           theRefCon:LONGINT):ControlHandle;
 {Allocate and draw a vertical scroll bar in theWindow.
   Return a controlHandle to the scroll bar.}
  VAR
    myWindowRect,vScrRect:  Rect;
  BEGIN
    SetPort(theWindow);
    myWindowRect := theWindow^.portRect;
    SetRect(vScrRect,myWindowRect.right -15,
                     myWindowRect.top - 1,
                     myWindowRect.right + 1,
                     myWindowRect.bottom - 14);
    CreateVScrollBar := NewControl(theWindow,vScrRect,
                                   ‘MyVert’,TRUE,
                                   theValue,theMin,theMax,
                                   scrollBarProc,theRefCon);
  END; {CreateVScrollBar}
 
  PROCEDURE InvalScroll(theWindow:WindowPtr);
  {Accumulate the rectangles occupied by theWindow’s
   horizontal and vertical scroll bars into the update
   region.}
  VAR
    theRect,tallRect,wideRect:  Rect;
  BEGIN
    SetPort(theWindow);
    theRect := theWindow^.portRect;
    ClipRect(theRect);

    {Accumulate tallRect, which is occupied by the vertical
     scroll bar }

    SetRect(tallRect,theRect.right-15,
                     theRect.top,
                     theRect.right,
                     theRect.bottom);
    EraseRect(tallRect);
    InvalRect(tallRect);

    {Accumulate wideRect, which is occupied by the 
     horizontal scroll bar }

    SetRect(wideRect,theRect.left,
                     theRect.bottom-15,
                     theRect.right,
                     theRect.bottom);
    EraseRect(wideRect);
    InvalRect(wideRect);
  END; {InvalScroll}

  PROCEDURE Deactivate(theWindow:WindowPtr);
  {Deactivate the scroll bars in theWindow in accordance
   with the human interface guidelines. This means we
   must erase everything enclosed by the control rec-
   tangles.}
  VAR
    theControl:      ControlHandle;
    theControlRect:  Rect;
  BEGIN
    {I always title my scroll bars ‘MyVert’ and ‘MyHoriz’
     so I can easily find them by walking a window’s
     control list.}
    theControl := WindowPeek(theWindow)^.controlList;
    WHILE (theControl <> NIL) DO
    BEGIN
      IF (theControl^^.contrlTitle = ‘MyVert’) OR
         (theControl^^.contrlTitle = ‘MyHoriz’) THEN
      BEGIN
        theControlRect := theControl^^.contrlRect;
        InsetRect(theControlRect,1,1);
        EraseRect(theControlRect);
      END; {IF}
      theControl := theControl^^.nextControl;
    END; {WHILE}
  END; {Deactivate}

  PROCEDURE HiliteScrollBars(theWindow:WindowPtr);
  VAR
    myWindowRect: Rect;
    visWidth,visHeight: INTEGER;
    myDocWidth,myDocHeight: INTEGER;
    myCtlValue: INTEGER;
    tempDH,tempDV: LONGINT;
    myOffScrHandle: OffScrHandle;
  BEGIN
    myOffScrHandle := OffScrHandle(GetWRefCon(theWindow));
    MoveHHi(Handle(myOffScrHandle));
    HLock(Handle(myOffScrHandle));

    {Make local copies of data structure items.}

    WITH myOffScrHandle^^ DO
    BEGIN
      tempDH := dhGraf;
      tempDV := dvGraf;
      myDocWidth := docWidth;
      myDocHeight := docHeight;
    END;

    HUnlock(Handle(myOffScrHandle));
    myWindowRect := theWindow^.portRect;
    visHeight := myWindowRect.bottom - 15 - myWindowRect.top;
    visWidth := myWindowRect.right - 15 - myWindowRect.left;
    SetPort(theWindow);
    ClipRect(myWindowRect);

    {Check to see if window is taller than contents. If
     so, unhilite vertical scroll bar. Otherwise, use
     Golden Ratio to draw vertical scroll bar with thumb 
     in right position.}

    IF ((visHeight >= myDocHeight) AND (tempDV = 0)) THEN
      HiliteControl(vScroll,INTEGER(inactive))
    ELSE
    BEGIN
      HiliteControl(vScroll,INTEGER(active));
      IF (visHeight >= myDocHeight) THEN
        SetCtlValue(vScroll,myDocHeight)
      ELSE
      BEGIN
        myCtlValue := LONGINT((tempDV * myDocHeight) DIV
                              (myDocHeight - visHeight));
        ClipRect(theWindow^.portRect);
        setCtlValue(vScroll,INTEGER(myCtlValue));
      END; {IF visHeight...ELSE}
    END;

    {Repeat for horizontal scroll bar.}

    IF ((visWidth >= myDocWidth) AND (tempDH = 0)) THEN
      HiliteControl(hScroll,INTEGER(inactive))
    ELSE
    BEGIN
      HiliteControl(hScroll,INTEGER(active));
      IF (visWidth >= myDocWidth) THEN
        SetCtlValue(hScroll,myDocWidth)
      ELSE
      BEGIN
        myCtlValue := LONGINT((tempDH * myDocWidth) DIV
                              (myDocWidth - visWidth));
        ClipRect(theWindow^.portRect);
        setCtlValue(hScroll,INTEGER(myCtlValue));
      END; {IF visWidth...ELSE}
    END; {IF visWidth...ELSE}

  END; {HiliteScrollBars}

  PROCEDURE MoveScrollBars(theWindow:WindowPtr);
  {Call this procedure after theWindow has changed size.
   MoveScrollBars erases theWindow’s scroll bars, resizes
   them, and redraws them.}
  VAR
    myWindowRect:       Rect;
    vScrRect,hScrRect:  Rect;
  BEGIN
    myWindowRect := theWindow^.portRect;
    SetRect(hScrRect,myWindowRect.left - 1,
                     myWindowRect.bottom - 15,
                     myWindowRect.right - 14,
                     myWindowRect.bottom + 1);
    SetRect(vScrRect,myWindowRect.right - 15,
                     myWindowRect.top - 1,
                     myWindowRect.right + 1,
                     myWindowRect.bottom - 14);
    SetPort(theWindow);
    ClipRect(myWindowRect);

    {Hide and resize the scroll bars to fit the new window 
     size.}

    HideControl(hScroll);
    HideControl(vScroll);

    MoveControl(hScroll,hScrRect.left,hScrRect.top);
    SizeControl(hScroll,(hScrRect.right - hScrRect.left),
                        (hScrRect.bottom - hScrRect.top));

    MoveControl(vScroll,vScrRect.left,vScrRect.top);
    SizeControl(vScroll,(vScrRect.right - vScrRect.left),
                        (vScrRect.bottom - vScrRect.top));
    HiliteScrollBars(theWindow);
    ShowControl(hScroll);
    ShowControl(vScroll);
  END; {MoveScrollBars}
 
  PROCEDURE ScrollWithThumb(theControl:ControlHandle;
                            theEventPoint:Point);
  VAR
    theWindow: WindowPtr;
    myOffScrHandle: OffScrHandle;
    visWidth,visHeight: INTEGER;
    myDocWidth,myDocHeight: INTEGER;
    startValue,endValue: INTEGER;
    dummy: INTEGER;
    amountToScroll: LONGINT;
    tempDH,tempDV: LONGINT;
    direction: Str255;
    BEGIN
      theWindow := theControl^^.contrlOwner;
      myOffScrHandle := OffScrHandle(GetWRefCon(theWindow));
      MoveHHi(Handle(myOffScrHandle));
      HLock(Handle(myOffScrHandle));

      {Make local copies of the data structure items.}

      WITH myOffScrHandle^^ DO
      BEGIN
        tempDH := dhGraf;
        tempDV := dvGraf;
        myDocWidth := docWidth;
        myDocHeight := docHeight;
      END;
      HUnlock(Handle(myOffScrHandle));
      GetCTitle(theControl,direction);
      visHeight := theWindow^.portRect.bottom -
                   theWindow^.portRect.top - 15;
      visWidth := theWindow^.portRect.right -
                  theWindow^.portRect.left - 15;

      {Record where thumb is before it is moved.}

      startValue := GetCtlValue(theControl);

      {Move it.}

      dummy := TrackControl(theControl,theEventPoint,NIL);

      {Record where thumb is released.}

      endValue := GetCtlValue(theControl);

      {Record the distance thumb was moved.}

      amountToScroll := LONGINT(endValue - startValue);

      IF (direction = ‘MyVert’) THEN
      BEGIN {moved vertical scroll bar}
        IF (endValue = 0) THEN {moved thumb to top}
          ScrollContents(theWindow,0,-tempDV)
        ELSE IF (endValue = myDocHeight) THEN
        {moved thumb to bottom}
          ScrollContents(theWindow,0,myDocHeight - tempDV)
        ELSE
        BEGIN
        {moved thumb somewhere in middle - use
           Golden Ratio to translate thumb movement
           into document movement.} 
          amountToScroll := LONGINT((amountToScroll * 
                                    (myDocHeight - 
                                     visHeight)) DIV
                                    myDocHeight);
          IF (ABS(amountToScroll) < 1) THEN
            {didn’t move thumb far enough to scroll -
             snap it back to starting position.}
            SetCtlValue(theControl,startValue)
          ELSE
            ScrollContents(theWindow,0,
                           INTEGER(amountToScroll));
          END;
        END
        ELSE  {moved horizontal scroll bar}
        BEGIN
        IF (endValue = 0) THEN
          ScrollContents(theWindow,-tempDH,0)
        ELSE IF (endValue = myDocWidth) THEN
          ScrollContents(theWindow,myDocWidth - tempDH,0)
        ELSE
        BEGIN
          amountToScroll := LONGINT((amountToScroll * 
                                    (myDocWidth - 
                                     visWidth)) DIV
                                    myDocWidth);
          IF (ABS(amountToScroll) < 1) THEN
            SetCtlValue(theControl,startValue)
          ELSE
            ScrollContents(theWindow,
                           INTEGER(amountToScroll),0);
        END;
     END;
  END; {ScrollWithThumb}

  FUNCTION WhichDevice(thePoint:Point):GDHandle;
  {For machines that support color QuickDraw and
   multiple screens, WhichDevice figures out which screen 
   thePoint is on and returns a GDHandle to that screen. 
   thePoint might be where the mouse was clicked, for 
   example. Thanks to Greg Marriott for this code.}
  VAR
    aDevice:   GDHandle;
    foundOne:  BOOLEAN;
  BEGIN
    aDevice := GetDeviceList;
    foundOne := FALSE;

    {Walk the device list until thePoint is contained
     in some device’s screen rectangle.}

    WHILE (aDevice <> NIL) AND NOT foundOne DO
    BEGIN
      IF PtInRect(thePoint,aDevice^^.gdRect) THEN
      BEGIN
        WhichDevice := aDevice;
        foundOne := TRUE;
      END;
      aDevice := aDevice^^.gdNextGD;
    END;
  END; {WhichDevice}

  FUNCTION MenuBarHeight: INTEGER;
  {Returns the height of the menubar in pixels, as read
   from the low memory global mBarHeight.}
  CONST
    mBarHeight = $BAA;
  VAR
    menuBarHeightPtr:  ^INTEGER;
  BEGIN
    menuBarHeightPtr := Pointer(mBarHeight);
    MenuBarHeight := menuBarHeightPtr^;
  END; {MenuBarHeight}

  FUNCTION OnAScreen(theRect:Rect):BOOLEAN;
  {OnAScreen returns FALSE if all of a window’s title
   bar is off the screen or if any part of it is under
   the menubar. The portRect of the window to be checked
   should be passed in theRect.}
  CONST
    titleBarHeight  =  18;
  VAR
    deskRgn:           RgnHandle;
    topLeft,topRight:  Point;
  BEGIN
    deskRgn := GetGrayRgn;
    topLeft.v := theRect.top - titleBarHeight;
    topLeft.h := theRect.left + titleBarHeight;
    topRight.v := topLeft.v;
    topRight.h := theRect.right - titleBarHeight;
    IF ((PtInRgn(topLeft,deskRgn)) OR 
    (PtInRgn(topRight,deskRgn))) THEN
      OnAScreen := TRUE
    ELSE
      OnAScreen := FALSE;
  END; {OnAScreen}

  PROCEDURE ZoomIt(theWindow:WindowPtr;partCode:INTEGER;
                   clickedWhere:Point);
  {ZoomIt supports more elegant window zooming on multiple
   screen systems. theWindow will zoom to fill whatever
   screen the zoom box was on when it was clicked. The
   window state toggles between original size and zoomed
   size, as usual. Thanks to Greg Marriott for this code.}
  CONST
    titleBarHeight  =  18;
  TYPE
    WStatePtr     =  ^WStateData;
    WStateHandle  =  ^WStatePtr;
  VAR
    oldRect,newRect:  Rect;
    maxHeight:        INTEGER;
  BEGIN
    oldRect := theWindow^.portRect;
    IF theEnv.hasColorQD THEN
    BEGIN
      newRect := WhichDevice(clickedWhere)^^.gdRect;
      IF WhichDevice(clickedWhere) = GetMainDevice THEN
        newRect.top := newRect.top + MenuBarHeight;
    END
      newRect := GetGrayRgn^^.rgnBBox;
    newRect.left := newRect.left + 2;
    newRect.top := newRect.top + titleBarHeight + 2;
    newRect.right := newRect.right - 3;
    newRect.bottom := newRect.bottom - 3;
    IF NOT EqualRect(oldRect,newRect) THEN
      WITH WindowPeek(theWindow)^ DO
        WStateHandle(dataHandle)^^.stdState := newRect;
    SetPort(theWindow);
    EraseRect(whichWindow^.portRect);
    InvalRect(whichWindow^.portRect);
    ZoomWindow(theWindow,partcode,FALSE);
  END; {ZoomIt}

  {$I XCmdGlue.inc}

  PROCEDURE Fail(errStr:Str255);
  {Fail returns errStr to HyperCard and exits the XCMD.
   errStr can then be checked by inspecting HyperCard’s
   global variable “the result.” See “XCMD’s for Hyper-
   Card” by Gary Bond (MIS Press, 1988) for more details.

   © 1988 by Gary Bond
   All rights reserved.
   You may use this code for NON-COMMERCIAL purposes.}
  BEGIN
    paramPtr^.returnValue := PasToZero(errStr);
    SysBeep(1);
    EXIT(Window);
  END; {Fail}

  PROCEDURE CheckParamCount;
  {CheckParamCount sees if the number of parameters
   passed to the XCMD matches the number expected. If
   not, we exit from the XCMD with an error message.
   See “XCMD’s for HyperCard” by Gary Bond (MIS Press,
   1988) for more details.

   © 1988 by Gary Bond
   All rights reserved.
   You may use this code for NON-COMMERCIAL purposes.}
  VAR
    numParams:  INTEGER;
  BEGIN
    numParams := paramPtr^.paramCount;
    IF(numParams <> minParamCount) THEN
      Fail(‘Form: HyperWindow “Window 
           Title”,top,left,bottom,right’);
  END;  {CheckParamCount}

  FUNCTION GetHCVersion: Str255;
  {Return a string containing the version of HyperCard
   being used; e.g., ‘1.2’}
  BEGIN
    ZeroToPas(EvalExpr(‘the version’)^,GetHCVersion);
  END; {GetHCVersion}

  PROCEDURE HideWindoids;
  {Get and save the visible state of the tool, pattern,
   message and fatbits windoids; then hide them if they
   are showing.}
  VAR
    toolH,patH,msgH,fatH:  Handle;
    PROCEDURE HideFatBits;
    {HyperCard does not have a built-in command for hiding
     and showing the fatbits windoid, so we have to do it
     ourselves. HideFatBits walks the window list until it
     finds a window with title “FatBits,” then hides it if
     the visible field of its WindowRecord is true. 
     HideFatBits also saves the WindowPtr to the fatbits
     windoid so we can use it later (e.g., to show the
     windoid again).}
    CONST
      windowList  =  $9D6; {Low memory global location.}
    VAR
      theWindow:     WindowPeek;
      theWindowPtr:  ^WindowPtr;
    BEGIN
      theWindowPtr := Pointer(windowList);
      theWindow := WindowPeek(theWindowPtr^);
      fatVis := FALSE;
      WHILE (theWindow <> NIL) DO
      BEGIN
        IF (theWindow^.titleHandle^^ = ‘FatBits’) THEN
        BEGIN
          fatBitsWindow := WindowPtr(theWindow);
          IF (theWindow^.visible = TRUE) THEN
          BEGIN
            fatVis := TRUE;
            HideWindow(fatBitsWindow);
            theWindow := NIL;
          END;
        END;
        IF (theWindow <> NIL) THEN
          theWindow := WindowPeek(theWindow)^.nextWindow;
      END; {WHILE}
    END; {HideFatBits}

  BEGIN {HideWindoids}

    {Get visible state of windoids.}

    toolH := EvalExpr(‘visible of tool window’);
    ZeroToPas(toolH^,toolStr);
    DisposHandle(toolH);
    toolVis := StrToBool(toolStr);

    patH := EvalExpr(‘visible of pattern window’);
    ZeroToPas(patH^,patStr);
    DisposHandle(patH);
    patVis := StrToBool(patStr);

    msgH := EvalExpr(‘visible of message window’);
    ZeroToPas(msgH^,msgStr);
    DisposHandle(msgH);
    msgVis := StrToBool(msgStr);

    {Hide the ones that are showing.}
 
    HideFatBits;
 
    IF toolVis THEN
      SendHCMessage(‘hide tool window’);
    IF patVis THEN
      SendHCMessage(‘hide pattern window’);
    IF msgVis THEN
      SendHCMessage(‘hide message window’);
  END; {HideWindoids}

  PROCEDURE ShowWindoids;
  {This routine assumes HideWindoids has been called
   before. ShowWindoids restores the visible state of
   the windoids to that saved by HideWindoids.}
  BEGIN
    IF toolVis THEN
      SendHCMessage(‘show tool window’);
    IF patVis THEN
      SendHCMessage(‘show pattern window’);
    IF msgVis THEN
      SendHCMessage(‘show message window’);

    {As in HideWindoids, we must take care of the FatBits
     windoid ourselves.}

    IF fatVis THEN
    BEGIN
      ShowWindow(fatBitsWindow);
      SelectWindow(fatBitsWindow);
    END;
  END; {ShowWindoids}

  PROCEDURE ToggleMenuBar;
  {Set the visible of the menubar to not the visible of
   the menubar.}
  BEGIN
    IF MenuBarHeight = 0 THEN
      SendHCMessage(‘show menubar’)
    ELSE
      SendHCMessage(‘hide menubar’);
  END; {ToggleMenuBar}

  PROCEDURE GetHCBitMap;
 VAR
 myDocHeight,myDocWidth:  INTEGER;
 HCRect,myHCRect:Rect;
 dummyGrafPtr:   GrafPtr;
 HCBits:BitMap;
 BEGIN
 MoveHHi(Handle(theOffScrHandle));
 HLock(Handle(theOffScrHandle));
 WITH theOffScrHandle^^ DO
 BEGIN
 myDocWidth := docWidth;
 myDocHeight := docHeight;
 END;
 myBits.rowBytes := (((myDocWidth - 1) DIV 16) + 1) * 2;
 myBits.baseAddr := NewPtr(myBits.rowBytes * myDocHeight);
 IF (myBits.baseAddr = NIL) THEN
 Fail(‘Could not allocate bitmap.’);
 HCRect := HCPort^.portRect;
 HCBits := HCPort^.portBits;
 SetRect(myBits.bounds,0, 0, 528, 358);
 
 dummyGrafPtr := GrafPtr(NewPtr(sizeOf(GrafPort)));
 IF (dummyGrafPtr = NIL) THEN
 BEGIN
 DisposPtr(myBits.baseAddr);
 Fail(‘Could not allocate offscreen grafPort.’);
 END;
 theOffScrHandle^^.gPort := dummyGrafPtr;
 OpenPort(theOffScrHandle^^.gPort);
 SetOrigin(0,0);
 SetPortBits(myBits);
 MovePortTo(0,0);
 PortSize(myDocWidth,myDocHeight);
 RectRgn(theOffScrHandle^^.gPort^.visRgn, theOffScrHandle^^.gPort^.portRect);
 RectRgn(theOffScrHandle^^.gPort^.clipRgn, theOffScrHandle^^.gPort^.portRect);
 EraseRect(theOffScrHandle^^.gPort^.portRect);
 SetRect(myHCRect,0, 0, 512, 342);
 CopyBits(HCBits,theOffScrHandle^^.gPort^.portBits, HCRect,myHCRect,srcCopy,NIL);
 HUnlock(Handle(theOffScrHandle));
  END; {GetHCBitMap}

  PROCEDURE AdjustCursor;
  {AdjustCursor changes cursorRgn to the region that 
   contains the cursor.  As soon as the cursor moves out of 
   cursorRgn, we get an event and can change the cursor and 
   cursorRgn again. cursorRgn is either the content region 
   of our window or the region containing everything BUT the 
   content region of our window.}
  VAR
    mousePt:        Point;
    myWinContRect:  Rect;
    myWinContRgn:   RgnHandle;
    deskRgn:        RgnHandle;
    handHdl:        CursHandle;
  BEGIN
    SetPort(myWindow);
    GetMouse(mousePt);
    LocalToGlobal(mousePt);
    myWinContRgn := NewRgn;

    {Calculate the “work region” of our window, which is its
     content region minus the scroll bars and grow icon. 
     This is the region within which we want the cursor to 
     change to HyperCard’s browse tool, and outside of which 
     we want it to be an arrow.}
    WITH WindowPeek(myWindow)^.contRgn^^.rgnBBox DO
      SetRect(myWinContRect,left, 
                            top, 
                            right - 15,
                            bottom - 15);
    RectRgn(myWinContRgn,myWinContRect);

    IF PtInRect(mousePt,myWinContRect) THEN
    BEGIN

      {The cursor is in the work region of our window. Set
       to browse tool.}

      handHdl := GetCursor(browseTool);
      IF (handHdl <> NIL) THEN
        SetCursor(handHdl^^)
      ELSE

        {Set to arrow if can’t find browse tool resource.}

        InitCursor;

      {Set the cursor region equal to our window’s work 
       region.}

      SetEmptyRgn(cursorRgn);
      CopyRgn(myWinContRgn,cursorRgn);
    END
    ELSE
    BEGIN
      {The cursor is outside our window. Set to arrow.}
      InitCursor;
      {Get the current desktop region.}
      deskRgn := GetGrayRgn;

      {Set cursorRgn to the desktop region’s bounding box.  
       It is important to add the menu bar area to cursorRgn 
       too.}

      SetRectRgn(cursorRgn, deskRgn^^.rgnBBox.left,
                            deskRgn^^.rgnBBox.top,
                            deskRgn^^.rgnBBox.right,
                            deskRgn^^.rgnBBox.bottom);
   
      {Punch out our window’s content region from the big 
       region.}

      DiffRgn(cursorRgn,myWinContRgn,cursorRgn);
    END;
    DisposeRgn(myWinContRgn);
  END; {AdjustCursor}

BEGIN {Main Program}
  {Check the HyperCard version. Must be 1.2 or greater.}
  IF GetHCVersion < ‘1.2’ THEN
    Fail(‘Sorry, must have HyperCard 1.2 or greater.’);

  {Save thy grafPort upon entering!}

  GetPort(HCPort);

  {Check and reset our environment.}

  CheckParamCount;
  FlushEvents(everyEvent,0);
  InitCursor;

  {Find out what kind of machine we’re running on.}

  envError := SysEnvirons(1,theEnv);
  IF (envError <> noErr) THEN
    Fail(‘SysEnvirons call failed.’);

  {Convert HyperTalk input parameters for use here.}

  ZeroToPas(paramPtr^.params[1]^,wTitle);
  ZeroToPas(paramPtr^.params[2]^,wT);
  ZeroToPas(paramPtr^.params[3]^,wL);
  ZeroToPas(paramPtr^.params[4]^,wB);
  ZeroToPas(paramPtr^.params[5]^,wR);

  wTop := INTEGER(StrToNum(wT));
  wLeft := INTEGER(StrToNum(wL));
  wBottom := INTEGER(StrToNum(wB));
  wRight := INTEGER(StrToNum(wR));

  {If window size parameters are too small or illegal, set
   the window to a predefined minimum size.}

  IF ((wRight - wLeft) < smallestWidth) THEN
    wRight := wLeft + smallestWidth;
  IF ((wBottom - wTop) < smallestHeight) THEN
    wBottom := wTop + smallestHeight;

  {Make sure the user is not trying to draw the window off
   the screen or under the menubar.}

  SetRect(wRect,wLeft,wTop,wRight,wBottom);
  IF NOT OnAScreen(wRect) THEN
    Fail(‘You are trying to draw your window off the 
         screen!’);

  {Get the bounds of the desktop.}

  screenRect := GetGrayRgn^^.rgnBBox;

  {If we have a small screen, make a note of it so we can
   hide the card window during context switches under
   MultiFinder.}

  ZeroToPas(EvalExpr(‘item 3 of the screenRect’)^,widthStr);
  screenWidth := INTEGER(StrToNum(widthStr));
  ZeroToPas(EvalExpr(‘item 4 of the screenRect’)^,
            heightStr);
  screenHeight := INTEGER(StrToNum(heightStr));

  IF (screenWidth = 512) AND (screenHeight = 342) THEN
    smallScreen := TRUE
  ELSE
    smallScreen := FALSE;

  HideWindoids;

  IF MenuBarHeight > 0 THEN
    menuWasHidden := FALSE
  ELSE
    menuWasHidden := TRUE;

  theOffScrHandle := OffScrHandle(NewHandle
                     (SizeOf(OffScrRecord)));
  IF MemError <> noErr THEN
    Fail(‘Out of memory.  Buy more.’);

  myDocWidth := HCWidth + padding;
  myDocHeight := HCHeight + padding;

 MoveHHi(Handle(theOffScrHandle));
 HLock(Handle(theOffScrHandle));

  WITH theOffScrHandle^^ DO
  BEGIN
    docWidth := myDocWidth;
 docHeight := myDocHeight;
  END;

  GetHCBitMap;

  {Draw the window!}

  myWindow := NewWindow(@wRecord,wRect,
                        wTitle,TRUE,zoomDocProc,
                        WindowPtr(-1),TRUE,1);
  IF (myWindow = NIL) THEN
  BEGIN
    ShowWindoids;
    Fail(‘Not enough memory to draw window.’);
  END
  ELSE
  BEGIN
    {This is for scrolling. Stay tuned....}
    SetWRefCon(myWindow,LONGINT(theOffScrHandle));
    InitBlit(myWindow);
    DrawGrowIcon(myWindow);
    theScrollHandle := ScrollHandle(NewHandle
                       (SizeOf(ScrollRecord)));
    IF MemError <> noErr THEN
      Fail(‘Out of memory.  Buy more.’);

    {Draw horizontal and vertical scroll bars in our 
     window.}

    hScroll := CreateHScrollBar(myWindow,0,0,
                                myDocWidth,
                                LONGINT(theScrollHandle));
    vScroll := CreateVScrollBar(myWindow,0,0,
                                myDocHeight,
                                LONGINT(theScrollHandle));
    HiliteScrollBars(myWindow);
    DrawContents(myWindow);
    SetRect(dragRect,screenRect.left + 4,
                     screenRect.top,
                     screenRect.right - 4,
                     screenRect.bottom - 4);
    largestHeight := screenRect.bottom - screenRect.top;
    largestWidth := screenRect.right - screenRect.left;
    SetRect(winSizeLimits,smallestWidth,
                          smallestHeight,
                          largestWidth,
                          largestHeight);
 
    HCRefresh := ‘Go to this card’;
    cursorRgn := NewRgn;
    inBackGround := FALSE;
    DoneFlag := FALSE;
    REPEAT

      {Call WaitNextEvent, if available. Otherwise call
       GetNextEvent.}

      IF hasWaitNextEvent THEN
        HaveEvent := WaitNextEvent(everyEvent,
                                   myEvent,30,cursorRgn)
      ELSE
      BEGIN
        HaveEvent := GetNextEvent(everyEvent,myEvent);
        AdjustCursor;
      END;
      IF HaveEvent THEN
      BEGIN
        IF (myEvent.what = app4Evt) THEN

          {Pre-process app4Evt’s fed to us by MultiFinder.}

          CASE BSR(myEvent.message,24) OF

            MouseMovedEvt:
              AdjustCursor;

            SuspendResumeEvt:
            BEGIN
        myEvent.what := activateEvt;

              {Resume event.}

              IF (BAND(myEvent.message,SuspendEventMask) <> 
              0) THEN
                inBackground := FALSE
              ELSE

                {Suspend event.}

                inBackground := TRUE;

              myEvent.message := LONGINT(myWindow);
            END; {SuspendResumeEvt}

          END; {CASE BSR}
      END; {IF HaveEvent}

      CASE myEvent.what OF

        mouseDown:
        BEGIN
          partCode := FindWindow(myEvent.where,whichWindow);
          IF (whichWindow = myWindow) THEN
          BEGIN

            {Deal with mouse hits to our window.}

            CASE partCode OF

              inDrag:
              BEGIN
                SelectWindow(whichWindow); {DragWindow bug}
                DragWindow(whichWindow,myEvent.where,
                           dragRect);
                SendCardMessage(HCrefresh);
                AdjustCursor;
              END;

              inGrow:
                IF StillDown THEN {GrowWindow bug}
                BEGIN
                  oldSize := whichWindow^.portRect;
                  newSize := GrowWindow(whichWindow,
                                        myEvent.where,
                                        winSizeLimits);
                  IF (newSize <> 0) THEN
                  BEGIN
                    InvalScroll(whichWindow);
                    SizeWindow(whichWindow,
                               LOWORD(newSize),
                               HIWORD(newSize),FALSE);
                    InvalContents(whichWindow,oldSize);
                    DrawGrowIcon(whichWindow);
                    MoveScrollBars(whichWindow);
                  END; {IF newSize}
                END; {IF StillDown}
              END; {inGrow}

              inZoomIn,inZoomOut:
              BEGIN
                IF (TrackBox(whichWindow,
                             myEvent.where,
                             partCode)) THEN
                BEGIN
                  InvalScroll(whichWindow);
                  ZoomIt(whichWindow,partCode,
                         myEvent.where);
                  InvalContents(whichWindow,oldSize);
                  DrawGrowIcon(whichWindow);
                  MoveScrollBars(whichWindow);
                END; {IF TrackBox}
              END; {inZoomIn,inZoomOut}

              inContent:
              BEGIN
                SetPort(whichWindow);
                eventPoint := myEvent.where;
                GlobalToLocal(eventPoint);
                controlCode := FindControl(eventPoint,
                                           whichWindow,
                                           whichControl);
                ClipRect(whichWindow^.portRect);
                IF (controlCode = inThumb) THEN
                  ScrollWithThumb(whichControl,eventPoint)
                ELSE IF (controlCode <> 0) THEN
                  dummy := TrackControl(whichControl,
                                        eventPoint,
                                        @MyScroll);
              END; {inContent}

              inGoAway:
              BEGIN
                IF (TrackGoAway(whichWindow,myEvent.where)) 
                THEN
                  DoneFlag := TRUE;
              END; {inGoAway}

            END; {CASE partCode}
          END; {IF whichWindow = myWindow}
        END; {mouseDown}
 
        activateEvt:
        BEGIN
          IF (WindowPtr(myEvent.message) = myWindow) THEN
          BEGIN
            SetPort(myWindow);
            ClipRect(myWindow^.portRect);
            DrawGrowIcon(myWindow);
            IF inBackground THEN
            BEGIN
              {We’ve been sent behind another application
               under MultiFinder, so deactivate the scroll
               bars and show the menubar if it was hidden.}
              Deactivate(myWindow);
              IF smallScreen THEN
                ShowHide(WindowPtr(HCPort),FALSE);
              IF MenuBarHeight = 0 THEN
            BEGIN
                SendHCMessage(‘show menubar’);
                menuWasHidden := TRUE;
              END
              ELSE
                menuWasHidden := FALSE;
            END
            ELSE
            BEGIN
              {We’ve been brought to the front under Multi-
               Finder, so reactivate the scroll bars and
               hide the menubar if it was hidden before.}
              ShowHide(WindowPtr(HCPort),TRUE);
              IF menuWasHidden THEN
                SendHCMessage(‘hide menubar’);
              DrawControls(myWindow);
              HiliteScrollBars(myWindow);
              FlushEvents(everyEvent,0);
            END; {IF inBackground}
          END; {IF WindowPtr}
        END; {activateEvt}

        updateEvt:
        BEGIN
          {Handle updates to our window.}
          IF (WindowPtr(myEvent.message) = myWindow) THEN
          BEGIN
            {Always do this stuff}
            SetPort(myWindow);
            BeginUpdate(myWindow);
            ClipRect(myWindow^.portRect);
            DrawGrowIcon(myWindow);

            {Always do this stuff under single Finder but
             only if in foreground under MultiFinder}

            IF NOT inBackground THEN
            BEGIN
              HiliteScrollBars(myWindow);
              DrawControls(myWindow);
              AdjustCursor;
            END;

            {Always do this stuff}

            DrawContents(myWindow);
            EndUpdate(myWindow);
          END {IF myEvent.message}

          {Handle updates to HyperCard’s card window.}

          ELSE IF (WindowPtr(myEvent.message) = 
          WindowPtr(HCPort)) THEN
          BEGIN
            SendHCMessage(HCRefresh);

            {Must zero out card window’s update region
             ourselves because the SendHCMessage call
             doesn’t do it, and we’ll wind up in an
             infinite loop if we don’t.}

            BeginUpdate(WindowPtr(HCPort));
            EndUpdate(WindowPtr(HCPort));
          END; {IF... ELSE}
        END; {updateEvt}

        keyDown:
        BEGIN
          IF (BitAnd(myEvent.modifiers,cmdKey) <> 0) THEN
          BEGIN
            charCode := BitAnd(myEvent.message,
                               charCodeMask);
           {Pressing command-spacebar toggles the menubar.}
            IF (CHR(charCode) = ‘ ‘) THEN
              ToggleMenuBar;
            {Pressing command-W closes the window and exits
             the XCMD.}
            IF (CHR(charCode) = ‘w’) THEN
              DoneFlag := True;
          END;
        END;
      END; {CASE myEvent.what}

    UNTIL (DoneFlag = True);

    {Clean up and get outta here!}
    DisposeRgn(cursorRgn);
    DisposHandle(Handle(theOffScrHandle));
    DisposHandle(Handle(theScrollHandle));
    CloseWindow(myWindow);
    SendCardMessage(HCrefresh);
    ShowWindoids;
    InitCursor;
    FlushEvents(everyEvent,0);
    {Restore thy grafPort}
    SetPort(HCPort);
  END; {IF myWindow...ELSE}
END; {Main}
END. {Window}

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Whitethorn Games combines two completely...
If you have ever gone fishing then you know that it is a lesson in patience, sitting around waiting for a bite that may never come. Well, that's because you have been doing it wrong, since as Whitehorn Games now demonstrates in new release Skate... | Read more »
Call of Duty Warzone is a Waiting Simula...
It's always fun when a splashy multiplayer game comes to mobile because they are few and far between, so I was excited to see the notification about Call of Duty: Warzone Mobile (finally) launching last week and wanted to try it out. As someone who... | Read more »
Albion Online introduces some massive ne...
Sandbox Interactive has announced an upcoming update to its flagship MMORPG Albion Online, containing massive updates to its existing guild Vs guild systems. Someone clearly rewatched the Helms Deep battle in Lord of the Rings and spent the next... | Read more »
Chucklefish announces launch date of the...
Chucklefish, the indie London-based team we probably all know from developing Terraria or their stint publishing Stardew Valley, has revealed the mobile release date for roguelike deck-builder Wildfrost. Developed by Gaziter and Deadpan Games, the... | Read more »
Netmarble opens pre-registration for act...
It has been close to three years since Netmarble announced they would be adapting the smash series Solo Leveling into a video game, and at last, they have announced the opening of pre-orders for Solo Leveling: Arise. [Read more] | Read more »
PUBG Mobile celebrates sixth anniversary...
For the past six years, PUBG Mobile has been one of the most popular shooters you can play in the palm of your hand, and Krafton is celebrating this milestone and many years of ups by teaming up with hit music man JVKE to create a special song for... | Read more »
ASTRA: Knights of Veda refuse to pump th...
In perhaps the most recent example of being incredibly eager, ASTRA: Knights of Veda has dropped its second collaboration with South Korean boyband Seventeen, named so as it consists of exactly thirteen members and a video collaboration with Lee... | Read more »
Collect all your cats and caterpillars a...
If you are growing tired of trying to build a town with your phone by using it as a tiny, ineffectual shover then fear no longer, as Independent Arts Software has announced the upcoming release of Construction Simulator 4, from the critically... | Read more »
Backbone complete its lineup of 2nd Gene...
With all the ports of big AAA games that have been coming to mobile, it is becoming more convenient than ever to own a good controller, and to help with this Backbone has announced the completion of their 2nd generation product lineup with their... | Read more »
Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »

Price Scanner via MacPrices.net

B&H has Apple’s 13-inch M2 MacBook Airs o...
B&H Photo has 13″ MacBook Airs with M2 CPUs and 256GB of storage in stock and on sale for up to $150 off Apple’s new MSRP, starting at only $849. Free 1-2 day delivery is available to most US... Read more
M2 Mac minis on sale for $100-$200 off MSRP,...
B&H Photo has Apple’s M2-powered Mac minis back in stock and on sale today for $100-$200 off MSRP. Free 1-2 day shipping is available for most US addresses: – Mac mini M2/256GB SSD: $499, save $... Read more
Mac Studios with M2 Max and M2 Ultra CPUs on...
B&H Photo has standard-configuration Mac Studios with Apple’s M2 Max & Ultra CPUs in stock today and on Easter sale for $200 off MSRP. Their prices are the lowest available for these models... Read more
Deal Alert! B&H Photo has Apple’s 14-inch...
B&H Photo has new Gray and Black 14″ M3, M3 Pro, and M3 Max MacBook Pros on sale for $200-$300 off MSRP, starting at only $1399. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8... Read more
Department Of Justice Sets Sights On Apple In...
NEWS – The ball has finally dropped on the big Apple. The ball (metaphorically speaking) — an antitrust lawsuit filed in the U.S. on March 21 by the Department of Justice (DOJ) — came down following... Read more
New 13-inch M3 MacBook Air on sale for $999,...
Amazon has Apple’s new 13″ M3 MacBook Air on sale for $100 off MSRP for the first time, now just $999 shipped. Shipping is free: – 13″ MacBook Air (8GB RAM/256GB SSD/Space Gray): $999 $100 off MSRP... Read more
Amazon has Apple’s 9th-generation WiFi iPads...
Amazon has Apple’s 9th generation 10.2″ WiFi iPads on sale for $80-$100 off MSRP, starting only $249. Their prices are the lowest available for new iPads anywhere: – 10″ 64GB WiFi iPad (Space Gray or... Read more
Discounted 14-inch M3 MacBook Pros with 16GB...
Apple retailer Expercom has 14″ MacBook Pros with M3 CPUs and 16GB of standard memory discounted by up to $120 off Apple’s MSRP: – 14″ M3 MacBook Pro (16GB RAM/256GB SSD): $1691.06 $108 off MSRP – 14... Read more
Clearance 15-inch M2 MacBook Airs on sale for...
B&H Photo has Apple’s 15″ MacBook Airs with M2 CPUs (8GB RAM/256GB SSD) in stock today and on clearance sale for $999 in all four colors. Free 1-2 delivery is available to most US addresses.... Read more
Clearance 13-inch M1 MacBook Airs drop to onl...
B&H has Apple’s base 13″ M1 MacBook Air (Space Gray, Silver, & Gold) in stock and on clearance sale today for $300 off MSRP, only $699. Free 1-2 day shipping is available to most addresses in... Read more

Jobs Board

Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Business Analyst | *Apple* Pay - Banco Popu...
Business Analyst | Apple PayApply now " Apply now + Apply Now + Start applying with LinkedIn Start + Please wait Date:Mar 19, 2024 Location: San Juan-Cupey, PR Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.