TweetFollow Us on Twitter

Real Window XCMD
Volume Number:5
Issue Number:2
Column Tag:HyperChat™

A Real Window XCMD

By Joe Zuffoletto, Cupertino, CA

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

Window XCMD

Part 1: Windows, XCMD’s, and MultiFinder

Joe has been at Apple a little over a year, where he works in the Software Quality Assurance department. His group develops automated software test tools. Before coming to Apple, He earned a BS in EE & CS from Princeton University. He has been programming the Mac for about 8 months and spends a lot of time experimenting with HyperCard. This article contains important windowing technology for dealing with gray regions, contirbuted by Scott Boyd and Greg Marriott.

Of Cards and Windows

For some reason, XCMD’s that put up windows intrigue me. I think it’s because windows imply event loops, and running an event loop in an XCMD is an interesting idea because it allows you to temporarily “steal the show” from HyperCard. Also, once you throw a window on the screen there’s a lot of neat stuff you can put inside it, like graphics too large for HyperCard’s card window, files generated by other applications, color pictures, and human interfaces for sophisticated tasks.

In this article I will present an XCMD that displays a document window with scroll bars. This XCMD is full-featured and very robust -- essentially a mini-Macintosh application running on top of HyperCard. It is MultiFinder aware, shows how to scroll bitmapped graphics documents, and also demonstrates how to support multiple screens on the Mac II. Finally, it demonstrates how to cope with some human interface issues when it comes to displaying windows on top of HyperCard.

Figure 1a: Before...

My discussion assumes you are somewhat familiar with the issues behind writing XCMD’s and XFCN’s for HyperCard. In particular, I assume you have a nodding acquaintance with the glue routines that perform special functions and facilitate communication between your XCMD and HyperCard. Plenty of introductory articles on the subject have appeared in MacTutor over the past few months, and Gary Bond has written an excellent book, XCMD’s for HyperCard (MIS Press, 1988), which gives you a head start in writing XCMD’s (Like Gary, I refer to XCMD’s and XFCN’s simply as XCMD’s).

Figure 1b: _After

However, even if you are not writing XCMD’s you might learn a lot of useful stuff by reading this article. My XCMD uses an event loop much like one you would find in a normal application, and I show you how to cope effectively with event handling and cursor tracking under MultiFinder. Also, if you’d like to see a rare example of scrolling code, that will be coming next month. In short, there’s something here for just about everyone.

One article will not hold all this material, so I’m splitting it into two parts. This month I will show you how to put the window up and deal with MultiFinder, multiple screens, and human interface issues. The window will have scroll bars but they won’t work. Next month I’ll breathe life into the scroll bars by demonstrating techniques for scrolling bitmapped graphics documents. As a newcomer to Mac programming I feel there is a critical shortage of example scrolling code, so I’d like to add some to the pool.

First Stop: The Drawing Board

I would like to start by describing the design of this fairly extensive XCMD, which I call Window.

The goal is to create a “full-featured” window -- one that is draggable, growable, zoomable, and contains scroll bars. Some other aspects of the design:

• Window is “semi-modal.” This means we cannot use HyperCard while the window is up, but we can switch to other applications under MultiFinder. Boo! Hiss! Yecch! Unfortunately, this “feature” is imposed on us by HyperCard, which, like all Macintosh applications, doesn’t like to share its event loop with strangers.

• Window is “MultiFinder aware;” i.e., the main event loop calls WaitNextEvent and handles suspend/resume events. We don’t need a SIZE resource because we’re running under the auspices of HyperCard and are using whatever space is available in HyperCard’s application heap. Window doesn’t use the clipboard, so it doesn’t need to convert its own private scrap when the user clicks on another open application. This feature could be added, however.

If you’d like more information about programming under MultiFinder, see the “Programmer’s Guide to MultiFinder,” an Apple publication available from APDA.

• Window supports multiple screens on the Mac II. This means the window can be dragged to any screen, grown or zoomed to the size of the largest screen, or grown to span adjacent screens. If the user clicks on the zoom box, the window zooms to whatever screen the zoom box is on at the time, instead of the default behavior of zooming to the main screen.

• Window accounts for human interface anomalies introduced by HyperCard. This means it deals sensibly with HyperCard’s windoids and with the current state of the menubar, which may or may not be visible when Window is invoked. As we’ll see, inattention to the state of the menubar can get you into trouble under MultiFinder!

• The HyperTalk call is:

 Window title,top,left,bottom,right

where title is a string (enclosed in double quotes), and the other parameters are integers specifying the window’s screen location in global coordinates; i.e., (0,0) is the top left corner of the screen (or of the main device on a Mac II), with coordinates increasing as you move down and right.

• Window won’t allow you to draw a window whose title bar is off the screen or under the menubar.

• Command-spacebar toggles the menubar off and on, just like in HyperCard. If the menubar is visible and the user clicks the mouse outside the window, the menubar flashes unless the click causes MultiFinder to switch to another open application.

• Clicking on the window’s close box ends the XCMD. Command-W, which closes windows in the Finder and many other applications, is also supported for this purpose.

For now, the window we put up will be empty, but will demonstrate all of the features described above. Next month I’ll add contents to the window when I talk about scrolling.

Plan of Attack

Now that we know the main features of the XCMD, it’s time to think about what steps to take to implement them.

Window, of course, will have an event loop, much like a normal Macintosh application. Because of its semi-modal nature, however, Window will not respond to mouse clicks inside HyperCard’s card window or menubar. Instead, it will just flash the menubar.

Incidentally, we could put up our own menubar and turn Window into a true mini-application, and then restore the menubar to HyperCard’s on exit. As soon as someone thinks of a useful mini-application that runs as an XCMD they will do this, but for now let’s keep things simple and ignore the menubar.

By the way, because we’re ignoring the menubar, what will happen under MultiFinder when the user clicks on the application icon in the upper righthand corner? Will we switch applications? Yes! This is the other side of Window’s semi-modal nature. The only thing that prevents MultiFinder from switching in this situation is if the frontmost window is of the dBoxProc type, which MultiFinder associates with modal dialog boxes. Our window is of type zoomDocProc, so MultiFinder will switch. MultiFinder will also switch if we click on a visible window belonging to a different application.

I want to avoid adding extra resources (besides the XCMD itself) for the user to copy into his or her stack, so I’ll use NewWindow to build my window on the fly instead of pulling it out of a resource with GetNewWindow. This is no big deal because the window is just a standard document window with a zoom box.

As with writing desk accessories, we can have no global variables. This should be no problem.

Early Frustrations

If you have ever tried to put a window on top of HyperCard with an XCMD, you probably experienced all kinds of interesting and unexpected problems. You will therefore recognize some of the obstacles I encountered in early development, which I will now describe to you -- solutions included.

The first problem is a complete show-stopper with the following symptoms: The XCMD successfully puts up some kind of window -- a dialog box, document window, or whatever -- but then bombs consistently on exit. Inspecting the code reveals no obvious problems. What on earth could possibly be wrong? The answer is contained in the following Fundamental Law: Thou shalt save the current grafPort upon entering thy XCMD and restore it upon leaving.

When an XCMD starts, the current grafPort is always HyperCard’s card window, and HyperCard expects things to be no different when the XCMD ends. If you set the port to something different, say to a window you put up or to a printing grafPort, and then don’t set it back, HyperCard gets very confused and dances La Bomba.

Let’s now assume the grafPort problem is solved and we have a window up that can be dragged, grown, and zoomed. The next problem pertains to update events; namely, the HyperCard window and the little windoids don’t seem to be getting any. Instead, when we drag our window on top of them and move it away, big white blotches remain. But under MultiFinder, when we drag our window over other applications and move it away, they update normally. What’s going on here?

The answer to this question is fairly interesting, and the described behavior under MultiFinder adds additional insight. First of all, it should be clear why the other applications update normally: It’s because MultiFinder sends them update events whenever our window’s movements corrupt their displays. Well then, shouldn’t MultiFinder be sending HyperCard update events for the same reason? Yes, it should, and indeed it is, but update events for HyperCard are being intercepted by our XCMD’s event loop!

At this point we have two choices. We can either throw up our hands and live with the white blotches, or we can figure out a way to pass update events to HyperCard. Following the second path is best, of course, but before you dive into your MultiFinder documentation to figure out how to fake an app4 event, think for a minute about the HyperTalk callback routines. These are routines like ZeroToPas, GetFieldByName, SendHCMessage, StrToNum -- hey, wait a minute! SendHCMessage! That sounds promising.... As you may know, SendHCMessage delivers messages from XCMD’s to HyperCard (and its cousin, SendCardMessage, delivers messages from XCMD’s to the current card). Maybe we can do something like this when we get an update event:

{1}

VAR
  myWindow:  WindowPtr;
  HCPort:    GrafPtr;
BEGIN
  GetPort(HCPort);
  .
  .
  CASE myEvent.what OF
    .
    .
    updateEvt:
    BEGIN
      IF WindowPtr(myEvent.message) =
         myWindow) THEN
      BEGIN
        SetPort(myWindow);
        BeginUpdate(myWindow);
        DoUpdate(myWindow);
        EndUpdate(myWindow);
      END
      ELSE IF
      WindowPtr(myEvent.message)
      = WindowPtr(HCPort)) THEN
        SendHCMessage(‘updateEvt’);
    END;

This is the right idea, but SendHCMessage takes only legal HyperTalk commands as input parameters! Which HyperTalk command(s) will send an update event to the card window? It turns out that going to any card (including the current card) will do the trick. Since we don’t want to switch cards on the user in the middle of our XCMD, let’s replace the bogus SendHCMessage command above with

{2}

SendHCMessage(‘go to this card’);

and, like magic, both our window and the card window will get update events when they need them. But what about the windoids? They still suffer from white-blotchitis.

Egads! Human Interface Issues!

This is where we can be good boys and girls and think about Human Interface Issues. Every Mac programmer should do this at least once in his or her lifetime, and here’s a wonderful opportunity to get it over with. We know our window is semi-modal, which implies it is on top of every other window in our layer, including the windoids. We also know that the user can’t use any of the windoids while our XCMD is running because we don’t know how to process events in them. Despite all this, the windoids remain hilited and appear to be as active and available as ever -- a potentially confusing situation for the user. So why don’t we hide them?

I happen to think this is a Very Good Idea, and I would like to propose that all HyperCard authors, be they XCMD writers or HyperTalk scripters, observe the following guidelines when creating their products:

• Always hide windoids upon entering event-driven XCMD’s or XFCN’s.

• Please don’t hide windoids from within HyperTalk scripts unless absolutely necessary. Leave the menubar alone, too.

• Whenever you hide windoids, get and save their current state (i.e., which ones are visible, where they are located on the screen, and which tools/patterns are selected) and return them to that state when you are finished. The same goes for the menubar.

If I make only one contribution to the Macintosh programming community before I die I hope it is the italicized guideline above. I spend a fair amount of time customizing my HyperCard environment, and I have all the windoids displayed right where I want them. I just can’t stand it when someone’s script comes along, blows them all away for no apparent reason, and then leaves it to me to put them back. Verrry rude. I also can’t stand it when people take the menubar away from me and never restore it, especially on a Mac II.

For the purpose of writing event-driven XCMD’s, we can satisfy the first and third guidelines with the following code:

{3}

CONST
  mBarHeight = $BAA;  {global}
VAR
  toolVis,menuVis:  BOOLEAN;
  toolH:  Handle;
  menuBarHeight: INTEGER;
  menuBarHeightPtr:  ^INTEGER;
BEGIN
  {Get and save current state of 
   windoid}
  toolH := EvalExpr(‘visible of tool 
           window’);
  ZeroToPas(toolH^,toolStr);
  DisposHandle(toolH);
  toolVis := StrToBool(toolStr);
  IF toolVis THEN
    SendHCMessage(‘hide tool
                   window’);

  {Get and save current state of 
   menuBar}
  menuBarHeightPtr :=
    Pointer(mBarHeight);
  menuBarHeight := menuBarHeightPtr^;
  IF menuBarHeight > 0 THEN
    menuVis := TRUE
  ELSE
    menuVis := FALSE;
  .
  {“Meat” of XCMD goes here”}
  .
  {Restore windoid when finished}
  IF toolVis THEN
    SendHCMessage(‘show tool 
                   window’);
  {Restore menubar if you changed it}

Of course, we would repeat the tool windoid code for the pattern and message windoids. You don’t need to worry about the locations of the windoids because HyperCard “remembers” their positions between hide and show commands.

HyperCard has one other windoid called FatBits that appears whenever you go into fatbits mode, but there are no HyperTalk commands to hide it or show it! Fortunately, Bill Atkinson names all his windoids, and the HyperCard people tell me the names won’t change, so all we have to do is walk the window list until we find the window called “FatBits,” then hide it if appropriate. I accomplish this in the HideFatBits procedure, which is nested in HideWindoids. Once HideFatBits finds the FatBits window, it saves the WindowPtr to it so we can show it again (if necessary) in the RestoreWindoids routine.

Aack! More Human Interface Issues!!

OK, as long as we’re hiding the windoids, why not hide the card window? And since we’re ignoring the menubar, we might as well get rid of it too, right?

I don’t have strong opinions about either of these ideas. Concerning the card window, I prefer to leave it up for four reasons. First, the current card may contain information we want to see while we are looking at the contents of the window. Second, leaving the card window showing gives us a visual reminder of who owns the window when we are running under MultiFinder, because clicking on either window will bring HyperCard’s layer to the front (with our window on top, of course). Third, it gets a little annoying to watch the card window disappear and reappear every time we invoke Window. And fourth, since larger human interface concerns are not at stake here (as they are with the windoids), I’m content to let the user decide in his or her HyperTalk script whether or not the card window will be left showing.

I solve the menubar problem by allowing the user to toggle it with command-space, just like in HyperCard.

Dashing through the Code...

Once I got past the initial obstacles and human interface issues, coding the XCMD was fairly straightforward. In this section I’ll zip through the program listing and give you some idea of what’s going on. Remember that I’m going to cover the scrolling of bitmaps next month, so all the scrolling and bitmap routines are stubbed out.

Take a look at the Main Program. The first thing we do (after checking the HyperCard version and saving thy grafPort) is make sure the number of input parameters sent to us is correct by calling CheckParamCount. CheckParamCount tells us nothing about the parameter types; if they’re mixed up or wrong we could be in trouble.

Assuming the input parameters are cool, we use the standard battery of ZeroToPas and StrToNum calls to convert them to useable form for our program. Then we hide the windoids and draw the window with scroll bars.

Next we enter the main event loop, where we call WaitNextEvent (if available) and pre-process suspend/resume events if MultiFinder feeds them to us. I say “pre-process” because all we do here is set a flag telling us if we are running in the background or not. This information is used later when activate and update events are handled.

A Short Digression:

Cursor Tracking with WaitNextEvent

While we’re talking about WaitNextEvent, let me say a few words about cursor tracking. Imagine a routine called AdjustCursor which does the following:

1. Get the mouse location in global coordinates;

2. Get the location and size of my window;

3. If the mouse lies within my window’s content region (minus the scroll bars), set the cursor to HyperCard’s browse tool, and set a region called cursorRgn to region A (see Figure 2a);

4. If the mouse lies outside region A, set the cursor to arrow, and set cursorRgn to region B (see Figure 2b).

Figure 2a

In the old days (before WaitNextEvent), Macintosh programmers would call a routine similar to AdjustCursor on every pass through the event loop because that was the easiest way to make sure the correct cursor was showing over various parts of a program’s human interface. This technique, although reliable, is inefficient because lots of time is spent crunching through AdjustCursor, even when the cursor is not moving.

WaitNextEvent has an interesting feature that makes cursor tracking more efficient. If I call the hypothetical AdjustCursor routine and then pass a handle to cursorRgn as the last input parameter to WaitNextEvent, I will get a mouse-moved event from MultiFinder when the cursor leaves cursorRgn, and then I can call AdjustCursor again to update the cursor and redefine cursorRgn for next time. This saves me from needlessly updating the cursor on every pass through the event loop.

This is very efficient, but using WaitNextEvent’s cursor tracking feature solves only part of the cursor tracking problem. Let me illustrate with an example. Assume I’m using the WaitNextEvent feature, and let’s say my window is small and I’m going to zoom it to fill the screen. When I move to the zoom box from region A I’ll get a mouse-moved event. Then I’ll call AdjustCursor to switch cursorRgn to region B and the cursor to arrow. So far, so good.

Now if I zoom the window and don’t move the cursor, the cursor will be sitting somewhere over my window’s contents, where it should become the browse tool cursor. But it’s still the arrow! Why? Because the old region B is still in effect (instead of a new region A), and a new region A won’t be created until I cross into the old region A again! To solve this problem, I can call AdjustCursor immediately after zooming the window to account for the window’s new size. But I’ll experience the same problem after dragging and growing the window, so now I have to call AdjustCursor in these cases as well. Of course, I could just call AdjustCursor when I handle update events, but I still need to call it immediately after dragging the window because dragging doesn’t always generate an update event in my layer. I think you can see my point -- WaitNextEvent’s cursor tracking feature is helpful because it saves you from calling AdjustCursor on every pass through the event loop, but there are a few special cases, mostly related to window movement and sizing, that you must handle yourself. Also, if you are calling GetNextEvent instead of WaitNextEvent, you must still use the old cursor tracking technique (or a more efficient technique of your own invention).

You might have noticed while using HyperCard that the cursor is kind of flaky. In particular, it is sometimes the arrow when it should be the browse tool, but if you move it outside of the card window and then back again, the browse tool reappears! You can probably guess my theory as to why this happens....

Figure 2b.

The Main Event

OK, we’ve pre-processed our MultiFinder events and adjusted our cursor. Now we can handle events in our window. Not too many surprises here -- I’ll zip through each event and summarize what’s happening.

MOUSE DOWN:

Flash the menubar (when it is visible) if the mouse click is outside our window; otherwise:

inDrag:

• Drag the window. If you haven’t read the Window Manager chapter of Inside Macintosh, Volume V, now’s a great time to do it. Notice I obtain the dragRect (the rectangle within which we can drag the window) by calling GetGrayRgn, which replaces old-style references to the QuickDraw global screenBits.bounds and automatically takes multiple screens into account for us.

• Update the card window (must do it explicitly because there is no update event in our layer)

• Adjust the cursor region.

inGrow:

• Grow the window. The GrowWindow routine has been modified for multiple screen systems; again, see the Window Manager chapter of Inside Macintosh, volume V for details.

• Move the scroll bars

inZoomIn,inZoomOut:

• Zoom the window according to the rules implemented n the ZoomIt procedure.

inContent:

• Deal with scroll bars, if appropriate; otherwise ignore.

inGoAway:

• Exit event loop and XCMD!

ACTIVATE EVENT:

If inBackground is true, then we’ve just been sent behind another application under MultiFinder. We deactivate our scroll bars and show the menubar if it was hidden. MultiFinder will not take care of the menubar for us, and it’s terribly unkind to send the user off to another application without a menubar!

Similarly, when we return, inBackground will be false. We reactivate our scroll bars and return the menubar to whatever state it was in when we left.

UPDATE EVENT:

If inBackground is true, then we have been fed an update event by MultiFinder because the user’s mousing around in another application has corrupted our window, HyperCard’s card window, or both.

If our window is corrupted, we redraw our deactivated scroll bars and the other stuff in our window.

If HyperCard’s window is corrupted, we update Hyper-Card’s window without updating ours. We also call BeginUpdate and EndUpdate to empty the HyperCard window’s update region because SendHCMessage alone doesn’t do this, and we’ll get into an infinite loop of update events if we don’t do it ourselves.

If inBackground is false, then we have been fed an update event because our window has either been grown or zoomed.

If our window is corrupted, we redraw our activated scroll bars and the other stuff in the window. Then we adjust the cursor.

If HyperCard’s window is corrupted, we update it as described above.

KEYDOWN:

The only other event that concerns us is keyDown. If we get command-space, we toggle the menubar off and on, just like in HyperCard. If we get command-W, we close the window and leave the XCMD.

Sneak Preview

That does it for putting up the window, building an event loop, and dealing with MultiFinder. Next month I’ll describe the routines and data structures for scrolling and offscreen bitmaps, which are stubbed out in this month’s code listing. I’ll show you how to set up an offscreen bitmap, then I’ll demonstrate a technique called blitting that produces flicker-free, high-speed scrolling.

Thanks, Everybody!

At this point I would like to mention a few people whose ideas and assistance were of great help to me in this glorious project. At the top of the list is “Mr. Toolbox” himself, Greg Marriott, who responded to most of my questions with an understanding nod and a correct answer. Greg also supplied the code for zooming the window on multiple screens.

Not far behind is “Mr. Window Manager,” Scott T Boyd, who taught me how to live without the QuickDraw globals.

[MacTutor readers will be very familar with both Scott Boyd and Greg Marriott as contributors and friends of MacTutor particularly during their "MacHax days". -Ed]

Thanks to Steve Maller for teaching me how to update HyperCard’s window, and to Ginger Jernigan, who took time to review the manuscript.

Thanks to Gary Bond for his excellent book on writing XCMD’s, and for the time he took to review the manuscript.

And, gosh, I almost forgot: Thanks, Bill!

(*

© 1988, Apple Computer, Inc.
All rights reserved. Publication rights granted to MacTutor.

Window1.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. 

           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 Window1.p
link   -o “Hard Disk”:HyperCard:”HyperCard Stacks”:Home 
       -rt XCMD=2000 -sn Main=Window 
       Window1.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];

     OffScrHandle = ^OffScrRecPtr; 
       {Attach to refCon of a window}
     OffScrRecPtr = ^OffScrRecord;
     OffScrRecord = RECORD
                      {Next month!}
                    END;

     ScrollHandle = ^ScrollPtr;
       {Attach to refCon of a control}
     ScrollPtr = ^ScrollRecord;
     ScrollRecord = RECORD
                      {Next month!}
                    END;

PROCEDURE Window(paramPtr:XCmdPtr);FORWARD;

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

FUNCTION Min(int1,int2:INTEGER): INTEGER;
BEGIN
  {Next month!}
END; {Min}

PROCEDURE InitBlit(theWindow:WindowPtr);
BEGIN
  {Next month!}
END; {InitBlit}

PROCEDURE InvalContents(theWindow:WindowPtr;
                        theOldSize:Rect);
BEGIN
  {More to come next month!}
  EraseRect(theWindow^.portRect);
  InvalRect(theWindow^.portRect);
END; {InvalContents}

PROCEDURE DrawContents(theWindow:WindowPtr);
BEGIN
  {Next month!}
END; {DrawContents}

PROCEDURE ScrollContents(theWindow:WindowPtr;dh,dv:INTEGER);
BEGIN
  {Next month!}
END; {ScrollContents}

PROCEDURE MyScroll(theControl:ControlHandle;partCode:INTEGER);
BEGIN
  {Next month!}
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);
  BEGIN
    {Reactivate the scroll bars; e.g., when we resume
     under MultiFinder. More next month!}
    HiliteControl(vScroll,INTEGER(active));
    HiliteControl(hScroll,INTEGER(active));
  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);
  BEGIN
    {Next month!}
  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. Publication rights granted MacTutor
   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. Publication rights granted MacTutor
   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;  
  BEGIN
    {Next month!}
  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;
  {This is for scrolling and will be explained next month.}
  theOffScrHandle := OffScrHandle(NewHandle
                     (SizeOf(OffScrRecord)));
  IF MemError <> noErr THEN
    Fail(‘Out of memory.  Buy more.’);

  myDocWidth := HCWidth + padding;
  myDocHeight := HCHeight + padding;
  WITH theOffScrHandle^^ DO
  BEGIN
    {Next month!}
  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

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom 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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.