home *** CD-ROM | disk | FTP | other *** search
/ Otherware / Otherware_1_SB_Development.iso / mac / developm / scnote / offsamp.016 / OffSample.p < prev    next >
Encoding:
Text File  |  1989-04-02  |  44.8 KB  |  1,544 lines

  1. {------------------------------------------------------------------------------
  2. #
  3. #    Apple Macintosh Developer Technical Support
  4. #
  5. #    Offscreen Buffer Sample Application
  6. #
  7. #    OffSample
  8. #
  9. #    OffSample.p        -    Pascal Source
  10. #
  11. #    Copyright ⌐ 1989 Apple Computer, Inc.
  12. #    All rights reserved.
  13. #
  14. #    Versions:    
  15. #                1.00                04/89
  16. #
  17. #    Components:
  18. #                OffSample.p            April 1, 1989
  19. #                OffSample.r            April 1, 1989
  20. #                OffSample.h            April 1, 1989
  21. #                POffSample.make        April 1, 1989
  22. #
  23. #    Requirements:
  24. #                Offscreen.p            April 1, 1989
  25. #                Offscreen.inc1.p    April 1, 1989
  26. #                UFailure.p            November 1, 1988
  27. #                UFailure.inc1.p        November 1, 1988
  28. #                UFailure.a            November 1, 1988
  29. #
  30. #    OffSample demonstrates the usage of the Offscreen
  31. #    unit. It shows how to use offscreen pixmaps and
  32. #    bitmaps to produce flicker-free updating with a
  33. #    minimum of re-structuring of code. OffSample attempts
  34. #    to reduce the amount of 'knowledge' that it has of
  35. #    the offscreen structure so as to minimize its
  36. #    dependence on that unit.
  37. #
  38. #    OffSample emphasizes using the Offscreen unit; it
  39. #    is not intended to be viewed as a complete application
  40. #    from which to base some larger effort. Instead, its
  41. #    method of using offscreen bitmaps and pixmaps should
  42. #    be studied and adapted to other applications that
  43. #    desire features such as flicker-free updating.
  44. #
  45. ------------------------------------------------------------------------------}
  46.  
  47.  
  48. PROGRAM OffSample;
  49.  
  50. USES
  51.     MemTypes, QuickDraw, Palettes, OSIntf, ToolIntf,
  52.     PackIntf, Picker, UFailure, Offscreen, Traps;
  53.  
  54. CONST
  55.  
  56.     kSysEnvironsVersion        = 1;
  57.  
  58.     kOSEvent                = app4Evt;        {event used by MultiFinder}
  59.     kSuspendResumeMessage    = 1;            {high byte of suspend/resume event message}
  60.     kResumeMask                = 1;            {bit of msg field for resume vs. suspend}
  61.     kNoEvents                = 0;            {no events mask}
  62.  
  63.     kMinHeap                = 66 * 1024;
  64.     
  65.     kMinSpace                = 49 * 1024;
  66.     
  67.     kExtremeNeg                = -32768;
  68.     kExtremePos                = 32767 - 1;    {required for old region bug}
  69.     
  70.     sErrStrings                = 128;            {error string STR#}
  71.     eStandardErr            = 1;
  72.     eWrongMachine            = 2;
  73.     eSmallSize                = 3;
  74.     eNoMemory                = 4;
  75.     
  76.     kNoBackBuff                = 128;
  77.     kNoEditBuff                = 129;
  78.     kTitle                    = 130;
  79.     kColorPrompt            = 131;
  80.     kNoWantBack                = 132;
  81.     kNoWantEdit                = 133;
  82.     
  83.     kCMoof                    = 128;
  84.     kGigantor                = 128;
  85.     k1bitGigantor            = 129;
  86.     
  87.     rMenuBar                = 128;            {application's menu bar}
  88.     rAboutAlert                = 128;            {about alert}
  89.     rUserAlert                = 129;            {error user alert}
  90.     rWindow                    = 128;            {application's window}
  91.  
  92.     mApple                    = 128;            {Apple menu}
  93.     iAbout                    = 1;
  94.  
  95.     mFile                    = 129;            {File menu}
  96.     iNew                    = 1;
  97.     iClose                    = 4;
  98.     iQuit                    = 12;
  99.  
  100.     mEdit                    = 130;            {Edit menu}
  101.     iUndo                    = 1;
  102.     iCut                    = 3;
  103.     iCopy                    = 4;
  104.     iPaste                    = 5;
  105.     iClear                    = 6;
  106.     
  107.     mShape                    = 131;            {Shape menu}
  108.     
  109.     mSpecial                = 132;            {Special menu}
  110.     iUseBack                = 1;
  111.     iUseEdit                = 2;
  112.     iPickColor                = 4;
  113.  
  114.     kDITop                    = $0050;
  115.     kDILeft                    = $0070;
  116.     
  117.     kNotDrawn                = -1;
  118.     kLastOne                = -2;
  119.     
  120.     kCursorDepth            = 2;
  121.     kMemoryPolite            = TRUE;
  122.     
  123.     kFramePenH                = 2;
  124.     kFramePenV                = 2;
  125.     
  126.     
  127. TYPE
  128.  
  129.     Shapes            = (kOval, kRegion, kRRect, kPoly, kRect, kICON, kPICT);
  130.  
  131.     ShapeRecord        = RECORD
  132.         next            : Shapes;        {when is it drawn?}
  133.         extent            : Rect;            {where is it?}
  134.     END;
  135.                     
  136.     ShapeArray        = ARRAY [Shapes] OF ShapeRecord;
  137.     
  138.     {An OffscreenRecord contains the WindowRecord for one of our sample windows,
  139.      as well as an offscreen handle for the background and an offscreen handle
  140.      for the background plus the shape being created. It also has an array of
  141.      shapes for this window, a pointer to the first shape, a pointer to the
  142.      shape being edited, and a record of the last state of the buffers. For a
  143.      similar example of extending a toolbox data structure, see how the Window
  144.      Manager and Dialog Manager add fields to the GrafPort and WindowRecord,
  145.      respectively.}
  146.      
  147.     OffscreenRecord    = RECORD
  148.         fWindow        : WindowRecord;        {window data structure for toolbox use}
  149.         fBackHandle    : Handle;            {offscreen pixmap that holds background}
  150.         fEditHandle    : Handle;            {pixmap for background and shape being created}
  151.         fShapes        : ShapeArray;        {the shapes for this window}
  152.         fFirst        : Shapes;            {who is first?}
  153.         fEdit        : Shapes;            {who is being edited?}
  154.         fHasBack    : BOOLEAN;            {did it have a background buffer last time?}
  155.         fHasEdit    : BOOLEAN;            {did it have a edit buffer last time?}
  156.     END;
  157.     OffscreenPeek    = ^OffscreenRecord;
  158.  
  159.  
  160. VAR
  161.     {The "g" prefix is used to emphasize that a variable is global.}
  162.  
  163.     gMac                : SysEnvRec;    {set up by Initialize}
  164.     gHasWaitNextEvent    : BOOLEAN;        {set up by Initialize}
  165.     gInBackground        : BOOLEAN;        {maintained by Initialize and DoEvent}
  166.     
  167.     gShape                : Shapes;        {current shape}
  168.     gUseBack            : BOOLEAN;        {create background offscreen flag}
  169.     gUseEdit            : BOOLEAN;        {create edit offscreen flag}
  170.     gCursor                : CCrsrHandle;    {there can be ONLY one}
  171.     gOughHandle            : Handle;        {offscreen handle for color cursor}
  172.     gPICT                : PicHandle;    {Gigantor}
  173.     gcicn                : CIconHandle;    {Moof!¬}
  174.     g1BitHandle            : Handle;        {for the color cursor mask}
  175.  
  176.  
  177. {$S Initialize}
  178. FUNCTION TrapAvailable(tNumber: INTEGER; tType: TrapType): BOOLEAN;
  179.  
  180. {Check to see if a given trap is implemented. This is only used by the
  181.  Initialize routine in this program, so we put it in the Initialize segment.
  182.  The recommended approach to see if a trap is implemented is to see if
  183.  the address of the trap routine is the same as the address of the
  184.  Unimplemented trap. Needs to be called after call to SysEnvirons so that
  185.  it can check if a ToolTrap is out of range of a pre-MacII ROM.}
  186.  
  187. BEGIN
  188.     IF (tType = ToolTrap) &
  189.         (gMac.machineType > envMachUnknown) &
  190.         (gMac.machineType < envMacII) THEN BEGIN        {it's a 512KE, Plus, or SE}
  191.         tNumber := BAND(tNumber, $03FF);
  192.         IF tNumber > $01FF THEN                            {which means the tool traps}
  193.             tNumber := _Unimplemented;                    {only go to $01FF}
  194.     END;
  195.     TrapAvailable := NGetTrapAddress(tNumber, tType) <>
  196.                         GetTrapAddress(_Unimplemented);
  197. END; {TrapAvailable}
  198.  
  199.  
  200. {$S Main}
  201. PROCEDURE GetGlobalRect (window: WindowPtr; VAR globalRect: Rect);
  202.  
  203. {Return the portRect of window in global coordinates.}
  204.  
  205. VAR
  206.     savePort    : GrafPtr;
  207.     
  208. BEGIN
  209.     GetPort(savePort);
  210.     SetPort(window);                    {so that the correct }
  211.     globalRect := window^.portRect;        { coordinate system is used}
  212.     WITH globalRect DO BEGIN
  213.         LocalToGlobal(topLeft);
  214.         LocalToGlobal(botRight);
  215.     END;
  216.     SetPort(savePort);
  217. END; {GetGlobalRect}
  218.  
  219.  
  220. {$S Main}
  221. FUNCTION IsDAWindow (window: WindowPtr): BOOLEAN;
  222.  
  223. {Check if a window belongs to a desk accessory.}
  224.  
  225. BEGIN
  226.     IF window = NIL THEN
  227.         IsDAWindow := FALSE
  228.     ELSE    {DA windows have negative windowKinds}
  229.         IsDAWindow := WindowPeek(window)^.windowKind < 0;
  230. END; {IsDAWindow}
  231.  
  232.  
  233. {$S Main}
  234. FUNCTION IsAppWindow (window: WindowPtr): BOOLEAN;
  235.  
  236. {Check to see if a window belongs to the application. If the window pointer
  237.  passed was NIL, then it could not be an application window. WindowKinds
  238.  that are negative belong to the system and windowKinds less than userKind
  239.  are reserved by Apple except for windowKinds equal to dialogKind, which
  240.  mean it is a dialog.}
  241.  
  242. BEGIN
  243.     IF window = NIL THEN
  244.         IsAppWindow := FALSE
  245.     ELSE    {application windows have windowKinds >= userKind (8)}
  246.         WITH WindowPeek(window)^ DO
  247.             IsAppWindow := (windowKind = userKind);
  248. END; {IsAppWindow}
  249.  
  250.  
  251. {$S Main}
  252. PROCEDURE FailNILMsg(p: UNIV Ptr; message: INTEGER);
  253.  
  254. {Check for NIL p and fail if so.}
  255.  
  256. BEGIN
  257.     IF p = NIL THEN
  258.         Failure(memFullErr, message);
  259. END; {FailNILMsg}
  260.  
  261.  
  262. {$S Main}
  263. PROCEDURE AlertUser(error: INTEGER; message: LongInt);
  264.  
  265. {Display an alert to inform the user of an error. Message acts as an 
  266.  index into a STR# resource of error messages. If no message is given,
  267.  i.e. = 0, then use a standard message. If error is not noErr then
  268.  display it as well.}
  269.  
  270. VAR
  271.     msg1, msg2    : Str255;
  272.     itemHit        : INTEGER;
  273. BEGIN
  274.     IF message = 0 THEN message := eStandardErr;
  275.     GetIndString(msg1, sErrStrings, message);
  276.     IF error = noErr THEN
  277.         msg2 := ''
  278.     ELSE
  279.         NumToString(error, msg2);
  280.     ParamText(msg1, msg2, '', '');
  281.     itemHit := Alert(rUserAlert, NIL);
  282. END; {AlertUser}
  283.  
  284.  
  285. {$S Main}
  286. FUNCTION DoCloseWindow(window: WindowPtr) : BOOLEAN;
  287.  
  288. {Close a window.}
  289.  
  290. {At this point, if there was a document associated with a
  291.  window, you could do any document saving processing if it is 'dirty'.
  292.  DoCloseWindow would return TRUE if the window actually closes, i.e.,
  293.  the user does not cancel from a save dialog. This result is handy when
  294.  the user quits an application, but then cancels a save of a document
  295.  associated with a window. We also added code to close the application
  296.  window since otherwise, the termination routines would never stop looping,
  297.  waiting for FrontWindow to return NIL.}
  298.  
  299. VAR
  300.     pal    : PaletteHandle;
  301.  
  302. BEGIN
  303.     DoCloseWindow := TRUE;
  304.     IF IsDAWindow(window) THEN
  305.         CloseDeskAcc(WindowPeek(window)^.windowKind);
  306.     IF IsAppWindow(window) THEN BEGIN
  307.         WITH OffscreenPeek(window)^ DO BEGIN
  308.             DisposeOffscreen(fBackHandle);
  309.             DisposeOffscreen(fEditHandle);
  310.         END;
  311.         IF gMac.hasColorQD THEN BEGIN
  312.             pal := GetPalette(window);            {We must handle this ourselves,}
  313.             DisposePalette(pal);                {since we may have done a GetPalette.}
  314.         END;
  315.         CloseWindow(window);                    {Since we provided our own storage.}
  316.         DisposPtr(Ptr(window));
  317.     END;
  318. END; {DoCloseWindow}
  319.  
  320.  
  321. {$S Main}
  322. PROCEDURE EfficientConcat2 (VAR string1, string2: Str255);
  323.  
  324. {Do a more efficient concat than CONCAT since we know
  325.  there are only two strings.}
  326.  
  327. VAR
  328.     len1, len2    : INTEGER;
  329.     
  330. BEGIN
  331.     len1 := LENGTH(string1);
  332.     IF len1 < 255 THEN BEGIN
  333.         len2 := LENGTH(string2);
  334.         IF len1 + len2 > 255 THEN
  335.             len2 := 255 - len1;
  336.         BlockMove(@string2[1], @string1[1 + len1], len2);
  337.         string1[0] := CHR(len1 + len2);
  338.     END;
  339. END; {EfficientConcat2}
  340.  
  341.  
  342. {$S Main}
  343. PROCEDURE AppendTitle (VAR title: Str255; id: INTEGER);
  344.  
  345. {Append the specified string resource data to the provided
  346.  string.}
  347.  
  348.  
  349. VAR
  350.     aString    : StringHandle;
  351.     
  352. BEGIN
  353.     aString := GetString(id);
  354.     IF aString <> NIL THEN BEGIN
  355.         HLock(Handle(aString));                {in case EfficientConcat2 is}
  356.         EfficientConcat2(title, aString^^);
  357.         HUnlock(Handle(aString));            {in a different segment}
  358.     END;
  359. END; {AppendTitle}
  360.  
  361.  
  362. {$S Main}
  363. PROCEDURE CheckTitle (window: WindowPtr; doCheck: BOOLEAN);
  364.  
  365. {Compare the prior state of the offscreen handles for
  366.  window and change its title to reflect the new state.}
  367.  
  368. VAR
  369.     aString                : StringHandle;
  370.     title                : Str255;
  371.     hasBack, hasEdit    : BOOLEAN;
  372.  
  373. BEGIN
  374.     IF IsAppWindow(window) THEN
  375.         WITH OffscreenPeek(window)^ DO BEGIN
  376.             hasBack := (GetMap(fBackHandle) <> NIL);
  377.             hasEdit := (GetMap(fEditHandle) <> NIL);
  378.             IF (NOT doCheck) |                    {set title regardless}
  379.             (fHasBack <> hasBack) |                {or if change}
  380.             (fHasEdit <> hasEdit) THEN BEGIN    {in buffers}
  381.                 fHasBack := hasBack;
  382.                 fHasEdit := hasEdit;
  383.                 title := '';
  384.                 aString := GetString(kTitle);
  385.                 IF aString <> NIL THEN
  386.                     title := aString^^;
  387.                     
  388.                 {If an offscreen handle is NIL, it means
  389.                  that the creation of that offscreen handle
  390.                  was disabled by the user. Once that is
  391.                  done, the buffer will never be created.}
  392.                 
  393.                 IF fBackHandle = NIL THEN
  394.                     AppendTitle(title, kNoWantBack)
  395.                 ELSE IF NOT hasBack THEN
  396.                     AppendTitle(title, kNoBackBuff);
  397.                 IF fEditHandle = NIL THEN
  398.                     AppendTitle(title, kNoWantEdit)
  399.                 ELSE IF NOT hasEdit THEN
  400.                     AppendTitle(title, kNoEditBuff);
  401.                 SetWTitle(window, title);
  402.             END;
  403.         END;
  404. END; {CheckTitle}
  405.  
  406.  
  407. {$S Main}
  408. PROCEDURE DrawShape (shape: Shapes; VAR extent: Rect);
  409.  
  410. {Draw the shape specified in the extent. Extent is a VAR
  411.  parameter because the region and polygon are generated
  412.  from the extent rect and the calculations might result
  413.  in a final shape larger than the original extent.}
  414.  
  415.     PROCEDURE DoRegion;
  416.     
  417.     {Generate a region based on the extent.}
  418.     
  419.     VAR
  420.         r        : Rect;
  421.         rHandle    : RgnHandle;
  422.         pHandle    : PolyHandle;
  423.         
  424.     BEGIN
  425.         r := extent;
  426.         rHandle := NewRgn;
  427.         OpenRgn;
  428.         
  429.         FrameRect(extent);
  430.         WITH r DO BEGIN
  431.             top := top + ((bottom - top) DIV 3);
  432.             bottom := top + ((bottom - top) DIV 2);
  433.         END;
  434.         FrameOval(r);
  435.         pHandle := OpenPoly;
  436.         WITH extent DO BEGIN
  437.             MoveTo(left, top);
  438.             LineTo(right, bottom);
  439.             LineTo(left + (right - left) DIV 2, bottom - (bottom - top) DIV 3);
  440.             LineTo(left, top);
  441.         END;
  442.         ClosePoly;
  443.         FramePoly(pHandle);
  444.         KillPoly(pHandle);
  445.         
  446.         CloseRgn(rHandle);
  447.         extent := rHandle^^.rgnBBox;        {in case bigger than original rect}
  448.         IF gMac.hasColorQD THEN
  449.             PaintRgn(rHandle)
  450.         ELSE
  451.             FillRgn(rHandle, black);
  452.         ForeColor(blackColor);
  453.         FrameRgn(rHandle);
  454.         DisposeRgn(rHandle);
  455.     END; {DoRegion}
  456.     
  457.     PROCEDURE DoPoly;
  458.     
  459.     {Generate a polygon based on the extent.}
  460.     
  461.     VAR
  462.         pHandle    : PolyHandle;
  463.         
  464.     BEGIN
  465.         pHandle := OpenPoly;
  466.         WITH extent DO BEGIN
  467.             MoveTo(left + (right - left) DIV 2, top);
  468.             LineTo(right, bottom);
  469.             LineTo(left, top + (bottom - top) DIV 3);
  470.             LineTo(right, top + (bottom - top) DIV 3);
  471.             LineTo(left, bottom);
  472.             LineTo(left + (right - left) DIV 2, top);
  473.         END;
  474.         ClosePoly;
  475.         extent := pHandle^^.polyBBox;        {in case bigger than original rect}
  476.         IF gMac.hasColorQD THEN
  477.             PaintPoly(pHandle)
  478.         ELSE
  479.             FillPoly(pHandle, ltGray);
  480.         ForeColor(blackColor);
  481.         FramePoly(pHandle);
  482.         KillPoly(pHandle);
  483.     END; {DoPoly}
  484.     
  485. BEGIN
  486.     PenNormal;
  487.     PenSize(kFramePenH, kFramePenV);
  488.     CASE shape OF
  489.         kOval: BEGIN
  490.             IF gMac.hasColorQD THEN
  491.                 PaintOval(extent)
  492.             ELSE
  493.                 FillOval(extent, white);
  494.             ForeColor(blackColor);
  495.             FrameOval(extent);
  496.         END;
  497.         kRegion:
  498.             DoRegion;
  499.         kRRect: BEGIN
  500.             IF gMac.hasColorQD THEN
  501.                 PaintRoundRect(extent, 16, 16)
  502.             ELSE
  503.                 FillRoundRect(extent, 16, 16, gray);
  504.             ForeColor(blackColor);
  505.             FrameRoundRect(extent, 16, 16);
  506.         END;
  507.         kPoly:
  508.             DoPoly;
  509.         kRect: BEGIN
  510.             IF gMac.hasColorQD THEN
  511.                 PaintRect(extent)
  512.             ELSE
  513.                 FillRect(extent, dkGray);
  514.             ForeColor(blackColor);
  515.             FrameRect(extent);
  516.         END;
  517.         kICON:
  518.             IF gMac.hasColorQD THEN
  519.                 PlotCIcon(extent, gcicn)
  520.             ELSE BEGIN 
  521.                 HLock(Handle(gcicn));
  522.                 WITH gcicn^^ DO BEGIN
  523.                 {We cannot call PlotCIcon when Color QD is not
  524.                  present, but we can still use the color icon
  525.                  data.}
  526.                     iconMask.baseAddr := @iconMaskData;
  527.                     iconBMap.baseAddr := Ptr(ORD(@iconMaskData) + 128);
  528.                     CopyMask(iconBMap, iconMask, thePort^.portBits,
  529.                                 iconBMap.bounds, iconMask.bounds, extent);
  530.                 END;
  531.                 HUnlock(Handle(gcicn));
  532.             END;
  533.         kPICT:
  534.             DrawPicture(gPICT, extent);
  535.     END;
  536. END; {DrawShape}
  537.  
  538.  
  539. {$S Main}
  540. FUNCTION GimmeBlackAndWhite(rgb: RGBColor; VAR position: LONGINT): BOOLEAN;
  541.  
  542. {This is a search proc that returns white only if the color is really white;
  543.  otherwise it returns black. It is used to generate the mask for the color
  544.  cursor. It boldly assumes that it is being called for a 1 bit deep map.}
  545.  
  546. BEGIN
  547.     WITH rgb DO
  548.         IF (red = $FFFF) & (green = $FFFF) & (blue = $FFFF) THEN
  549.             position := 0                    {return white if it╒s white}
  550.         ELSE
  551.             position := 1;                    {else return black for all other colors}
  552.     GimmeBlackAndWhite := TRUE;
  553. END; {GimmeBlackAndWhite}
  554.  
  555.  
  556. {$S Main}
  557. PROCEDURE SetObjCursor (window: WindowPtr);
  558.  
  559. {Build the color cursor. Note that this routine is only called
  560.  in a Color QD environment, so it doesn't have to make the
  561.  check. Note also that the cursors could have all been 'pre-
  562.  built', thus making things more efficient, but this example
  563.  shows that dynamic cursors can be implemented via pixmaps.}
  564.  
  565. VAR
  566.     colors            : CTabHandle;
  567.     pal                : PaletteHandle;
  568.     rgb                : RGBColor;
  569.     bounder, extent    : Rect;
  570.     buffNotNeeded    : BOOLEAN;
  571.     naughtyBits        : BitMap;
  572.     oneBitPMap        : BitMapPtr;
  573.  
  574. BEGIN
  575.     SetRect(bounder, 0, 0, 16, 16);
  576.     pal := GetPalette(window);
  577.     GetEntryColor(pal, ORD(gShape) + 2, rgb);    {get the color used for the shape}
  578.     
  579.     DisposeOffscreen(gOughHandle);                {get rid of old color table}
  580.     colors := CTabHandle(NewHandleClear(SIZEOF(ColorTable)));
  581.     FailNILMsg(colors, eNoMemory);
  582.     colors^^.ctTable[0].rgb := rgb;                {stuff in the color we want}
  583.     
  584.     FailOSErr(NewOffscreen(bounder, kCursorDepth, colors,
  585.                     NOT kMemoryPolite, buffNotNeeded,
  586.                     gOughHandle));
  587.     DisposHandle(Handle(colors));
  588.     
  589.     HLock(Handle(gCursor));
  590.     WITH gCursor^^ DO BEGIN
  591.         crsrMap := PixMapHandle(RecoverHandle(Ptr(GetMap(gOughHandle))));
  592.         crsrData := GetBitsHandle(gOughHandle);
  593.         IF crsrData = NIL THEN BEGIN            {no handle to bits available-punt}
  594.             SetCursor(arrow);
  595.             Exit(SetObjCursor);
  596.         END;
  597.         BeginOffscreenDrawing(gOughHandle, NIL);
  598.         IF NOT (gShape IN [kICON, kPICT]) THEN BEGIN
  599.             SetPt(crsrHotSpot, 0, 0);
  600.             RGBForeColor(rgb);
  601.             extent := bounder;
  602.             InsetRect(extent, 3, 1);            {squeeze it a bit}
  603.             DrawShape(gShape, extent);            {draw the cursor shape}
  604.             PenNormal;
  605.             ForeColor(blackColor);                {draw hot spot}
  606.             MoveTo(0, 0);
  607.             LineTo(0, 1);
  608.         END ELSE BEGIN                            {use a plain cursor for icon/pict}
  609.             SetPt(crsrHotSpot, 2, 2);
  610.             PenNormal;
  611.             ForeColor(blackColor);
  612.             MoveTo(0, 0);
  613.             LineTo(4, 4);
  614.             MoveTo(4, 0);
  615.             LineTo(0, 4);
  616.         END;
  617.         EndOffscreenDrawing(gOughHandle);
  618.         WITH naughtyBits DO BEGIN                {build 1-bit image and mask}
  619.             bounds := bounder;
  620.             baseAddr := @crsr1Data;
  621.             rowBytes := 2;
  622.             CopyBits(BitMapPtr(crsrMap^)^, naughtyBits,
  623.                         bounder, bounder, srcCopy, NIL);
  624.                         
  625.             oneBitPMap :=  GetMap(g1BitHandle);
  626.             oneBitPMap^.baseAddr := @crsrMask;
  627.             AddSearch(@GimmeBlackAndWhite);
  628.             CopyBits(BitMapPtr(crsrMap^)^, oneBitPMap^,
  629.                         bounder, bounder, srcCopy, NIL);
  630.             DelSearch(@GimmeBlackAndWhite);
  631.         END;
  632.         crsrXValid := 0;
  633.         crsrID := GetCTSeed;
  634.     END;
  635.     HUnlock(Handle(gCursor));
  636. END; {SetObjCursor}
  637.  
  638.  
  639. {$S Main}
  640. FUNCTION GetInvalExtent (window: WindowPtr; shape: Shapes) : Rect;
  641.  
  642. {Return the shape's extent, adjusted for the pensize of the frame.}
  643.  
  644. VAR
  645.     r    : Rect;
  646.     
  647. BEGIN
  648.     r := OffscreenPeek(window)^.fShapes[shape].extent;
  649.     WITH r DO BEGIN
  650.         right := right + kFramePenH;
  651.         bottom := bottom + kFramePenV;
  652.     END;
  653.     GetInvalExtent := r;
  654. END; {GetInvalExtent}
  655.  
  656.  
  657. {$S Main}
  658. PROCEDURE ChangeColor (window: WindowPtr);
  659.  
  660. {Display the Color Picker dialog. Note that this is
  661.  only called in Color QD environments.}
  662.  
  663. VAR
  664.     pal                    : PaletteHandle;
  665.     inColor, outColor    : RGBColor;
  666.     where                : Point;
  667.     r                    : Rect;
  668.     aString                : StringHandle;
  669.     prompt                : Str255;
  670.  
  671. BEGIN
  672.     pal := GetPalette(window);
  673.     WITH OffscreenPeek(window)^ DO BEGIN
  674.         GetEntryColor(pal, ORD(gShape) + 2, inColor);
  675.         SetPt(where, kDILeft, kDITop);
  676.         aString := GetString(kColorPrompt);
  677.         IF aString <> NIL THEN
  678.             prompt := aString^^
  679.         ELSE
  680.             prompt := '';
  681.         IF GetColor(where, prompt, inColor, outColor) THEN BEGIN
  682.             SetEntryColor(pal, ORD(gShape) + 2, outColor);
  683.             ActivatePalette(window);
  684.             SetObjCursor(window);
  685.             IF NOT (fShapes[gShape].next = Shapes(kNotDrawn)) THEN BEGIN
  686.                 r := GetInvalExtent(window, gShape);
  687.                 SetPort(window);
  688.                 InvalRect(r);
  689.             END;
  690.         END;
  691.     END;
  692. END; {ChangeColor}
  693.  
  694.  
  695. {$S Main}
  696. PROCEDURE DoNewWindow;
  697.  
  698. {We will allocate our own window storage instead of letting the Window
  699.  Manager do it for two reasons. One, GetNewWindow locks the 'WIND' resource
  700.  handle before calling NewWindow and this can lead to heap fragmentation
  701.  in low memory situations. Two, it takes just as much time for NewWindow
  702.  to get the memory as it does for us to get it. Three, there are THREE
  703.  reasons we will allocate our own window storage instead of letting the
  704.  Window Manager do it. One, GetNewWindow locks etc. etc. Two, it takes
  705.  just as much time, etc. etc. And three, this way we can allocate larger records
  706.  where the extra space can be used to connect other, related data structures.
  707.  Four, there is no fourth reason.}
  708.  
  709. VAR
  710.     p                : Ptr;
  711.     window            : WindowPtr;
  712.     noBuffsPlease    : BOOLEAN;
  713.     title            : Str255;
  714.     shape            : Shapes;
  715.     emptyRect        : Rect;
  716.  
  717. BEGIN
  718.     p := NewPtr(SIZEOF(OffscreenRecord));
  719.     FailNILMsg(p, eNoMemory);
  720.     window := NIL;
  721.     IF gMac.hasColorQD THEN
  722.         window := GetNewCWindow(rWindow, p, WindowPtr(-1))
  723.     ELSE
  724.         window := GetNewWindow(rWindow, p, WindowPtr(-1));
  725.     FailNILMsg(window, eNoMemory);
  726.     
  727.     WITH OffscreenPeek(window)^ DO BEGIN
  728.         fBackHandle := NIL;
  729.         fEditHandle := NIL;
  730.         fHasBack := FALSE;
  731.         fHasEdit := FALSE;
  732.         IF gUseBack THEN
  733.             IF NewOffscreenForWindow(window, noBuffsPlease, fBackHandle) = noErr THEN;
  734.         IF gUseEdit THEN
  735.             IF NewOffscreenForWindow(window, noBuffsPlease, fEditHandle) = noErr THEN;
  736.         SetRect(emptyRect, 0, 0, 0, 0);
  737.         FOR shape := kOval TO kPICT DO BEGIN
  738.             fShapes[shape].next := Shapes(kNotDrawn);
  739.             fShapes[shape].extent := emptyRect;
  740.         END;
  741.         fFirst := Shapes(kNotDrawn);
  742.         fEdit := Shapes(kNotDrawn);
  743.     END;
  744.     
  745.     CheckTitle(window, FALSE);
  746.     IF gMac.hasColorQD THEN
  747.         SetObjCursor(window);
  748. END; {DoNewWindow}
  749.  
  750.  
  751. {$S Initialize}
  752. PROCEDURE Initialize;
  753.  
  754. {Set up the whole world, including global variables, Toolbox managers,
  755.  and menus. We also create one application window at this time.
  756.  Since window storage is non-relocateable, how and when to allocate space
  757.  for windows is very important so that heap fragmentation does not occur.
  758.  Window storage can differ widely amongst applications depending on how many
  759.  windows are created and disposed. If a failure occurs here, we will consider
  760.  that the application is in such bad shape that we should just exit. Your error
  761.  handling may differ, but the checks should still be made.}
  762.  
  763. TYPE
  764.     crsColors = ARRAY[0..2] OF ColorSpec;
  765.  
  766. VAR
  767.     menuBar            : Handle;
  768.     ignoreError        : OSErr;
  769.     total, contig    : LongInt;
  770.     ignoreResult    : BOOLEAN;
  771.     event            : EventRecord;
  772.     count            : INTEGER;
  773.     fi                : FailInfo;
  774.     colors            : CTabHandle;
  775.     bounder            : Rect;
  776.     buffNotNeeded    : BOOLEAN;
  777.  
  778.     PROCEDURE HandleErr(error: INTEGER; message: LongInt);
  779.     BEGIN
  780.         IF error > 0 THEN
  781.             AlertUser(0, error)
  782.         ELSE
  783.             AlertUser(error, message);
  784.         ExitToShell;
  785.     END; {HandleErr}
  786.  
  787. BEGIN
  788.     gInBackground := FALSE;
  789.  
  790.     InitGraf(@thePort);
  791.     InitFonts;
  792.     InitWindows;
  793.     InitMenus;
  794.     TEInit;
  795.     InitDialogs(NIL);
  796.     InitCursor;
  797.     
  798.     InitOffscreen;
  799.  
  800.     {Call OpenDriver('.MPP', refnum) at this point to initialize AppleTalk,
  801.      if you are using it.}
  802.      
  803.     {NOTE -- It is no longer necessary, and actually unhealthy, to check
  804.      PortBUse and SPConfig before opening AppleTalk. The drivers are capable
  805.      of checking for port availability themselves.}
  806.     
  807.     {This next bit of code is necessary to allow the default button of our
  808.      alert to be outlined.}
  809.      
  810.     FOR count := 1 TO 3 DO
  811.         ignoreResult := EventAvail(everyEvent, event);
  812.  
  813.     CatchFailures(fi, HandleErr);
  814.  
  815.     {Ignore the error returned from SysEnvirons; even if an error occurred,
  816.      the SysEnvirons glue will fill in the SysEnvRec. You can save a redundant
  817.      call to SysEnvirons by calling it after initializing AppleTalk.}
  818.      
  819.     ignoreError := SysEnvirons(kSysEnvironsVersion, gMac);
  820.     
  821.     {Make sure that the machine has at least 128K ROMs. If it doesn't, exit.}
  822.     
  823.     IF gMac.machineType < 0 THEN
  824.         Failure(0, eWrongMachine);
  825.     
  826.     {Move TrapAvailable call to after SysEnvirons so that we can tell
  827.      in TrapAvailable if a tool trap value is out of range.}
  828.      
  829.     gHasWaitNextEvent := TrapAvailable(_WaitNextEvent, ToolTrap);
  830.  
  831.     {First check the size of the application heap against a value
  832.      that you have determined is the smallest heap the application can reasonably
  833.      work in. This number should be derived by examining the size of the heap that
  834.      is actually provided by MultiFinder when the minimum size requested is used.
  835.      The derivation of the minimum size requested from MultiFinder is described
  836.      in Sample.h. The check should be made because the preferred size can end up
  837.      being set smaller than the minimum size by the user. This extra check acts to
  838.      insure that your application is starting from a solid memory foundation.}
  839.      
  840.     IF ORD(GetApplLimit) - ORD(ApplicZone) < kMinHeap THEN
  841.         Failure(0, eSmallSize);
  842.     
  843.     {Next, make sure that enough memory is free for your application to run. It
  844.      is possible for a situation to arise where the heap may have been of required
  845.      size, but a large scrap was loaded which left too little memory. To check for
  846.      this, call PurgeSpace and compare the result with a value that you have determined
  847.      is the minimum amount of free memory your application needs at initialization.
  848.      This number can be derived several different ways. One way that is fairly
  849.      straightforward is to run the application in the minimum size configuration
  850.      as described previously. Call PurgeSpace at initialization and examine the value
  851.      returned. However, you should make sure that this result is not being modified
  852.      by the scrap's presence. You can do that by calling ZeroScrap before calling
  853.      PurgeSpace. Make sure to remove that call before shipping, though.}
  854.      
  855.     PurgeSpace(total, contig);
  856.     IF total < kMinSpace THEN
  857.         Failure(0, eNoMemory);
  858.  
  859.     {The extra benefit to waitng until after the Toolbox Managers have been initialized
  860.      before checking memory is that we can now give the user an alert to tell him what
  861.      happened. Although it is possible that the memory situation could be worsened by
  862.      displaying an alert, MultiFinder would gracefully exit the application with
  863.      an informative alert if memory became critical. Here we are acting more
  864.      in a preventative manner to avoid future disaster from low-memory problems.}
  865.  
  866.     menuBar := GetNewMBar(rMenuBar);        {read menus into menu bar}
  867.     FailNILMsg(menuBar, eNoMemory);
  868.     SetMenuBar(menuBar);                    {install menus}
  869.     DisposHandle(menuBar);
  870.     AddResMenu(GetMHandle(mApple), 'DRVR');    {add DA names to Apple menu}
  871.     DrawMenuBar;
  872.     gShape := kOval;
  873.     gUseBack := TRUE;
  874.     gUseEdit := TRUE;
  875.     gOughHandle := NIL;
  876.     
  877.     {Get the 'Moof' icon. If the environment supports Color QD,
  878.      we'll get the color icon. If Color QD is not supported,
  879.      we'll still get the color icon, but use it differently.}
  880.      
  881.     IF gMac.hasColorQD THEN BEGIN
  882.         gcicn := GetCIcon(kCMoof);
  883.         FailNILMsg(gcicn, eNoMemory);
  884.     END ELSE BEGIN
  885.         gcicn := CIconHandle(GetResource('cicn', kCMoof));
  886.         FailNILMsg(gcicn, eNoMemory);
  887.     END;
  888.     
  889.     {If Color QD is supported, we'll get an 8-bit PICT of
  890.      Gigantor. If it isn't supported, we'll get a PICT
  891.      that looks better in non-color ports/}
  892.      
  893.     IF gMac.hasColorQD THEN
  894.         gPICT := GetPicture(kGigantor)
  895.     ELSE
  896.         gPICT := GetPicture(k1bitGigantor);
  897.     FailNILMsg(gPICT, eNoMemory);
  898.     
  899.     {If Color QD is supported, we'll set up a color cursor
  900.      that will be modified later. Otherwise, nothing
  901.      happens. We'll also set up a 1-bit offscreen to
  902.      make a cursor mask.}
  903.      
  904.     IF gMac.hasColorQD THEN BEGIN
  905.         gCursor := CCrsrHandle(NewHandleClear(SIZEOF(CCrsr)));
  906.         FailNILMsg(gCursor, eNoMemory);
  907.         MoveHHi(Handle(gCursor));
  908.         HLock(Handle(gCursor));
  909.         WITH gCursor^^ DO BEGIN
  910.             crsrType := $8001;
  911.             crsrXData := NewHandle(0);
  912.         END;
  913.         HUnlock(Handle(gCursor));
  914.         colors := CTabHandle(NewHandleClear(SIZEOF(ColorTable)));
  915.         FailNILMsg(colors, eNoMemory);
  916.         SetRect(bounder, 0, 0, 16, 16);
  917.         
  918.         {For this one bit deep offscreen guy (used to make cursor masks)
  919.          we pass in a zeroed but otherwise unitialized color table. Since
  920.          the map╒s ctable will only have B&W anyway, it doesn╒t matter.}
  921.          
  922.         FailOSErr(NewOffscreen(bounder, 1, colors,
  923.                         NOT kMemoryPolite, buffNotNeeded,
  924.                         g1BitHandle));
  925.         DisposHandle(Handle(colors));
  926.     END;
  927.  
  928.     DoNewWindow;                            {create a new window right away}
  929. END; {Initialize}
  930.  
  931.  
  932. {$S Main}
  933. PROCEDURE Terminate;
  934.  
  935. {Clean up the application and exits. We close all of the windows so that
  936.  they can update their documents, if any.
  937.  If we find out that a cancel has occurred, we won't exit to the
  938.  shell, but will return instead.}
  939.  
  940. VAR
  941.     aWindow    : WindowPtr;
  942.     closed    : BOOLEAN;
  943.  
  944. BEGIN
  945.     closed := TRUE;
  946.     REPEAT
  947.         aWindow := FrontWindow;                    {get the current front window}
  948.         IF aWindow <> NIL THEN
  949.             closed := DoCloseWindow(aWindow);    {close this window}
  950.     UNTIL (NOT closed) | (aWindow = NIL);        {do all windows}
  951.     IF closed THEN
  952.         ExitToShell;                            {exit if no cancellation}
  953. END; {Terminate}
  954.  
  955.  
  956. {$S Main}
  957. PROCEDURE AdjustMenus;
  958.  
  959. {Enable and disable menus based on the current state.
  960.  The user can only select enabled menu items. We set up all the menu items
  961.  before calling MenuSelect or MenuKey, since these are the only times that
  962.  a menu item can be selected. Note that MenuSelect is also the only time
  963.  the user will see menu items. This approach to deciding what enable/
  964.  disable state a menu item has the advantage of concentrating all the decision-
  965.  making in one routine, as opposed to being spread throughout the application.
  966.  Other application designs may take a different approach that may or may not be
  967.  just as valid.}
  968.  
  969. VAR
  970.     window            : WindowPtr;
  971.     menu            : MenuHandle;
  972.  
  973. BEGIN
  974.     window := FrontWindow;
  975.  
  976.     menu := GetMHandle(mFile);
  977.     IF IsDAWindow(window) |
  978.         IsAppWindow(window) THEN            {we can allow DAs to be closed from the menu}
  979.         EnableItem(menu, iClose)
  980.     ELSE
  981.         DisableItem(menu, iClose);
  982.  
  983.     menu := GetMHandle(mEdit);
  984.     IF IsDAWindow(window) THEN BEGIN        {a desk accessory might need the edit menu}
  985.         EnableItem(menu, iUndo);
  986.         EnableItem(menu, iCut);
  987.         EnableItem(menu, iCopy);
  988.         EnableItem(menu, iPaste);
  989.         EnableItem(menu, iClear);
  990.     END ELSE BEGIN                            {but we know we do not}
  991.         DisableItem(menu, iUndo);
  992.         DisableItem(menu, iCut);
  993.         DisableItem(menu, iCopy);
  994.         DisableItem(menu, iClear);
  995.         DisableItem(menu, iPaste);
  996.     END;
  997.  
  998.     menu := GetMHandle(mSpecial);
  999.     IF gMac.hasColorQD & IsAppWindow(window) THEN
  1000.         EnableItem(menu, iPickColor)        {color can change only if we are top}
  1001.     ELSE
  1002.         DisableItem(menu, iPickColor);
  1003. END; {AdjustMenus}
  1004.  
  1005.  
  1006. {$S Main}
  1007. PROCEDURE DoMenuCommand(menuResult: LONGINT);
  1008.  
  1009. {This is called when an item is chosen from the menu bar (after calling
  1010.  MenuSelect or MenuKey). It performs the right operation for each command.
  1011.  It is good to have both the result of MenuSelect and MenuKey go to
  1012.  one routine like this to keep everything organized.}
  1013.  
  1014. VAR
  1015.     menuID            : INTEGER;        {the resource ID of the selected menu}
  1016.     menuItem        : INTEGER;        {the item number of the selected menu}
  1017.     int                : INTEGER;
  1018.     str                : Str255;
  1019.     ignore            : BOOLEAN;
  1020.  
  1021. BEGIN
  1022.     menuID := HiWrd(menuResult);    {use built-ins (for efficiency)...}
  1023.     menuItem := LoWrd(menuResult);    {to get menu item number and menu number}
  1024.     CASE menuID OF
  1025.         mApple:
  1026.             CASE menuItem OF
  1027.                 iAbout:                {bring up alert for About}
  1028.                     int := Alert(rAboutAlert, NIL);
  1029.                 OTHERWISE BEGIN        {all non-About items in this menu are DAs}
  1030.                     GetItem(GetMHandle(mApple), menuItem, str);
  1031.                     int := OpenDeskAcc(str);
  1032.                 END;
  1033.             END;
  1034.         mFile:
  1035.             CASE menuItem OF
  1036.                 iNew:
  1037.                     DoNewWindow;
  1038.                 iClose:
  1039.                     ignore := DoCloseWindow(FrontWindow); {we don't care if cancelled}
  1040.                 iQuit:
  1041.                     Terminate;
  1042.             END;
  1043.         mEdit:                        {call SystemEdit for DA editing & MultiFinder}
  1044.             ignore := SystemEdit(menuItem-1);    {since we don't do any editing}
  1045.         mShape: 
  1046.             IF gShape <> Shapes(menuItem - 1) THEN BEGIN
  1047.                 CheckItem(GetMHandle(mShape), ORD(gShape) + 1, FALSE);
  1048.                 gShape := Shapes(menuItem - 1);        {the shape is the item}
  1049.                 CheckItem(GetMHandle(mShape), ORD(gShape) + 1, TRUE);
  1050.                 IF gMac.hasColorQD & IsAppWindow(FrontWindow) THEN
  1051.                     SetObjCursor(FrontWindow);
  1052.             END;
  1053.         mSpecial:
  1054.             CASE menuItem OF
  1055.                 iUseBack: BEGIN
  1056.                     gUseBack := NOT gUseBack;
  1057.                     CheckItem(GetMHandle(mSpecial), iUseBack, gUseBack);
  1058.                 END;
  1059.                 iUseEdit: BEGIN
  1060.                     gUseEdit := NOT gUseEdit;
  1061.                     CheckItem(GetMHandle(mSpecial), iUseEdit, gUseEdit);
  1062.                 END;
  1063.                 iPickColor:
  1064.                     ChangeColor(FrontWindow);
  1065.             END;
  1066.     END;
  1067.     HiliteMenu(0);                    {unhighlight what MenuSelect (or MenuKey) hilited}
  1068. END; {DoMenuCommand}
  1069.  
  1070.  
  1071. {$S Main}
  1072. PROCEDURE GoThroughShapes (PROCEDURE WhatToDo(shape: Shapes); window: WindowPtr);
  1073.  
  1074. {Go through the list of shapes for window and
  1075.  call WhatToDo, passing the shape we are on
  1076.  each time.}
  1077.  
  1078. VAR
  1079.     theShape    : Shapes;
  1080.     
  1081. BEGIN
  1082.     WITH OffscreenPeek(window)^ DO
  1083.         IF ORD(fFirst) <> kNotDrawn THEN BEGIN
  1084.             theShape := fFirst;
  1085.             REPEAT
  1086.                 WhatToDo(theShape);
  1087.                 theShape := fShapes[theShape].next;
  1088.             UNTIL ORD(theShape) = kLastOne;
  1089.         END;
  1090. END; {GoThroughShapes}
  1091.  
  1092.  
  1093. {$S Main}
  1094. PROCEDURE DrawAllShapes (window: WindowPtr; doEdit: BOOLEAN);
  1095.  
  1096. {Draw either the currently edited shape or all the shapes
  1097.  in the window's list. Called by DrawWindow.}
  1098.  
  1099. VAR
  1100.     area    : Rect;
  1101.     
  1102.     PROCEDURE AndDrawThem (shape: Shapes);
  1103.     
  1104.     VAR
  1105.         r    : Rect;
  1106.         
  1107.     BEGIN
  1108.         WITH OffscreenPeek(window)^ DO
  1109.             IF shape <> fEdit THEN BEGIN
  1110.                 IF SectRect(OffscreenPeek(window)^.fShapes[shape].extent, area, r)
  1111.                     THEN BEGIN
  1112.                     IF gMac.hasColorQD THEN
  1113.                         PmForeColor(ORD(shape) + 2);
  1114.                     DrawShape(shape, OffscreenPeek(window)^.fShapes[shape].extent);
  1115.                 END;
  1116.             END;
  1117.     END;
  1118.  
  1119. BEGIN
  1120.     SetPort(window);
  1121.     IF doEdit THEN BEGIN
  1122.         WITH OffscreenPeek(window)^ DO                {draw edit shape}
  1123.             IF ORD(fEdit) <> kNotDrawn THEN BEGIN
  1124.                 IF gMac.hasColorQD THEN
  1125.                     PmForeColor(ORD(fEdit) + 2);
  1126.                 DrawShape(fEdit, OffscreenPeek(window)^.fShapes[fEdit].extent);
  1127.             END;
  1128.     END ELSE BEGIN
  1129.         area := window^.visRgn^^.rgnBBox;
  1130.         GoThroughShapes(AndDrawThem, window);
  1131.     END;
  1132. END; {DrawAllShapes}
  1133.  
  1134.  
  1135. {$S Main}
  1136. PROCEDURE DrawWindow(window: WindowPtr);
  1137.  
  1138. {The core application window updating routine. Understands about Offscreen
  1139.  setup, (in this case, two nested offscreen buffers), and what needs to
  1140.  be drawn, in this case, a whole bunch of shapes. Called from two routines,
  1141.  DoUpdate and DoContentClick. The way it works is first, by calling
  1142.  BeginUpdateOffscreen on fEditHandle, the drawing is redirected to
  1143.  the 'edit' offscreen pixmap. Next, if any drawing needs to be done
  1144.  in the 'background' pixmap, then by calling BeginOffscreenDrawing on
  1145.  fBackHandle, drawing is further redirected. All the shapes that exist
  1146.  but are not the one being edited (i.e., the background) are drawn here
  1147.  and the EndOffscreenDrawing causes the redirecting to cease. Then the
  1148.  pixmap is copybitsed into the next outer layer of drawing, whether that
  1149.  is the 'edit' offscreen pixmap or the window itself. There, the shape
  1150.  being edited is drawn. Finally, EndUpdateOffscreen is called to cease
  1151.  that layer of redirection and copybits the 'edit' offscreen to the window.
  1152.  The way this is designed, it all still works if either or both of the
  1153.  offscreen pixmaps is missing.}
  1154.  
  1155. VAR
  1156.     globalRect    : Rect;
  1157.     drawNeeded    : BOOLEAN;
  1158.     backMap        : BitMapPtr;
  1159.     
  1160. BEGIN
  1161.     GetGlobalRect(window, globalRect);
  1162.     WITH OffscreenPeek(window)^ DO BEGIN
  1163.         IF CheckBoundsOffscreen(fEditHandle, globalRect, drawNeeded) <> noErr THEN {do nada};
  1164.         SetPort(window);
  1165.         BeginUpdateOffscreen(fEditHandle, window);    {this sets up the visRgn}
  1166.         
  1167.         IF CheckBoundsOffscreen(fBackHandle, globalRect, drawNeeded) <> noErr THEN 
  1168.                                                     {do nada};
  1169.         IF drawNeeded THEN BEGIN                    {draw if updating needs to be done}
  1170.             BeginOffscreenDrawing(fBackHandle, window);
  1171.             EraseRect(window^.portRect);            {clear out any garbage that might}
  1172.             DrawAllShapes(window, FALSE);            {be left behind and draw the}
  1173.             EndOffscreenDrawing(fBackHandle);        {'background'}
  1174.         END;
  1175.         backMap := GetMap(fBackHandle);
  1176.         IF backMap <> NIL THEN BEGIN
  1177.             ForeColor(blackColor);
  1178.             BackColor(whiteColor);                    {so funny colorization doesn't happen}
  1179.             WITH window^ DO BEGIN
  1180.                 CopyBits(backMap^, portBits, portRect, portRect, srcCopy, NIL);
  1181.                 ValidRectOffscreen(fBackHandle, NIL, portRect);
  1182.             END;
  1183.         END;
  1184.         DrawAllShapes(window, TRUE);                {only draw the edited shape}
  1185.         
  1186.         EndUpdateOffscreen(fEditHandle, window);
  1187.         CheckTitle(window, TRUE);                    {buffers may have changed}
  1188.     END;
  1189. END; {DrawWindow}
  1190.  
  1191.  
  1192. {$S Main}
  1193. PROCEDURE DoContentClick (window: WindowPtr; event: EventRecord);
  1194.  
  1195. {This is called when a mouse-down event occurs in the content of a window.
  1196.  Other applications might want to call FindControl, TEClick, etc., to
  1197.  further process the click. In Offsample, a user click in the content
  1198.  region means a shape is to be added or changed.}
  1199.  
  1200. VAR
  1201.     oldRect, newRect        : Rect;
  1202.     anchorPt, oldPt, nextPt    : Point;
  1203.     lastShape                : Shapes;
  1204.     first                    : BOOLEAN;
  1205.     
  1206.     PROCEDURE AndReorderThem (shape: Shapes);
  1207.     
  1208.     {Remove the edited shape from the linked list of shapes.}
  1209.     
  1210.     BEGIN
  1211.         WITH OffscreenPeek(window)^ DO
  1212.             IF shape <> gShape THEN
  1213.                 lastShape := shape
  1214.             ELSE
  1215.                 IF fFirst = shape THEN
  1216.                     fFirst := fShapes[shape].next
  1217.                 ELSE
  1218.                     fShapes[lastShape].next := fShapes[shape].next;
  1219.     END; {AndReorderThem}
  1220.  
  1221. BEGIN
  1222.     IF IsAppWindow(window) THEN
  1223.         WITH OffscreenPeek(window)^ DO BEGIN
  1224.             anchorPt := event.where;
  1225.             GlobalToLocal(anchorPt);
  1226.             oldPt := anchorPt;
  1227.             
  1228.             {If the shape being edited existed previously, we need
  1229.              to invalidate its old position so that it gets
  1230.              'erased'.}
  1231.              
  1232.             IF ORD(fShapes[gShape].next) <> kNotDrawn THEN BEGIN
  1233.                 oldRect := GetInvalExtent(window, gShape);
  1234.                 InvalRectOffscreen(fBackHandle, NIL, oldRect);
  1235.                 InvalRectOffscreen(fEditHandle, window, oldRect);
  1236.             END;
  1237.             fEdit := gShape;                            {flag this shape as edited}
  1238.             lastShape := Shapes(kLastOne);
  1239.             GoThroughShapes(AndReorderThem, window);
  1240.             IF ORD(lastShape) <> kLastOne THEN
  1241.                 fShapes[lastShape].next := gShape        {make edited shape last}
  1242.             ELSE
  1243.                 fFirst := gShape;                        {or if only shape, first}
  1244.             fShapes[gShape].next := Shapes(kLastOne);
  1245.             Pt2Rect(anchorPt, anchorPt, oldRect);
  1246.             first := TRUE;                                {indicate first time though loop}
  1247.             WHILE WaitMouseUp DO BEGIN                    {while the mouse is down╔}
  1248.                 GetMouse(nextPt);
  1249.                 IF first | (NOT EqualPt(oldPt, nextPt)) THEN BEGIN
  1250.                     first := FALSE;                        {no longer first time through loop}
  1251.                     oldPt := nextPt;
  1252.                     CASE gShape OF
  1253.                         kOval,                            {build a rectangle for these}
  1254.                         kRegion,                        {from the anchor point and}
  1255.                         kRRect,                            {the current point}
  1256.                         kPoly,
  1257.                         kRect:
  1258.                             Pt2Rect(anchorPt, nextPt, newRect);
  1259.                         kICON:                            {rect from current position}
  1260.                             WITH nextPt, newRect DO BEGIN
  1261.                                 top := v;
  1262.                                 left := h;
  1263.                                 bottom := top + 32;
  1264.                                 right := left + 32;
  1265.                             END;
  1266.                         kPICT:                            {rect from current position}
  1267.                             WITH nextPt, newRect DO BEGIN
  1268.                                 newRect := gPICT^^.picFrame;
  1269.                                 OffsetRect(newRect, -left, -top);
  1270.                                 OffsetRect(newRect, h, v);
  1271.                             END;
  1272.                     END;
  1273.                     fShapes[gShape].extent := newRect;
  1274.                     UnionRect(newRect, oldRect, oldRect);
  1275.                     
  1276.                     {In the case of the 'stretchable' shapes whose extents are
  1277.                      built from the anchor point and the current point, doing
  1278.                      a UnionRect is pretty close to being as efficient as doing
  1279.                      a UnionRgn with two regions that are shaped like the old
  1280.                      and new extents. However, a case can be made for using
  1281.                      regions for the icon and the picture since they move around
  1282.                      instead of 'stretching'. The effect of extra, unnecessary
  1283.                      invalidation is, of course, most noticeable when there is
  1284.                      no edit offscreen and the icon/picture is moved around
  1285.                      rapidly. Changing the code to use regions is LEFT AS AN
  1286.                      EXERCISE FOR THE READER, Ha-Ha-Ha.}
  1287.                      
  1288.                     InvalRectOffscreen(fEditHandle, window, oldRect);
  1289.                     DrawWindow(window);
  1290.                     oldRect := GetInvalExtent(window, gShape);
  1291.                 END;
  1292.             END;
  1293.             fEdit := Shapes(kNotDrawn);
  1294.             oldRect := GetInvalExtent(window, gShape);
  1295.             InvalRectOffscreen(fBackHandle, NIL, oldRect);
  1296.         END;
  1297. END; {DoContentClick}
  1298.  
  1299.  
  1300. {$S Main}
  1301. PROCEDURE DoUpdate(window: WindowPtr);
  1302.  
  1303. {This is called when an update event is received for a window.
  1304.  It calls DrawWindow to draw the contents of an application window.}
  1305.  
  1306. BEGIN
  1307.     IF IsAppWindow(window) THEN
  1308.         DrawWindow(window);
  1309. END; {DoUpdate}
  1310.  
  1311.  
  1312. {$S Main}
  1313. PROCEDURE DoActivate(window: WindowPtr; becomingActive: BOOLEAN);
  1314.  
  1315. {This is called when a window is activated or deactivated.}
  1316.  
  1317. BEGIN
  1318.     IF IsAppWindow(window) THEN
  1319.         IF gMac.hasColorQD & becomingActive THEN
  1320.             SetObjCursor(window);
  1321. END; {DoActivate}
  1322. {$S Main}
  1323. PROCEDURE GetGlobalMouse(VAR mouse: Point);
  1324.  
  1325. {Get the global coordinates of the mouse. When you call OSEventAvail
  1326.  it will return either a pending event or a null event. In either case,
  1327.  the where field of the event record will contain the current position
  1328.  of the mouse in global coordinates and the modifiers field will reflect
  1329.  the current state of the modifiers. Another way to get the global
  1330.  coordinates is to call GetMouse and LocalToGlobal, but that requires
  1331.  being sure that thePort is set to a valid port.}
  1332.  
  1333. VAR
  1334.     event    : EventRecord;
  1335.     
  1336. BEGIN
  1337.     IF OSEventAvail(kNoEvents, event) THEN;    {we aren't interested in any events}
  1338.     mouse := event.where;                    {just the mouse position}
  1339. END;
  1340.  
  1341.  
  1342. {$S Main}
  1343. PROCEDURE AdjustCursor(mouse: Point; region: RgnHandle);
  1344.  
  1345. {Change the cursor's shape, depending on its position. This also calculates the region
  1346.  where the current cursor resides (for WaitNextEvent). If the mouse is ever outside of
  1347.  that region, an event is generated, causing this routine to be called. This
  1348.  allows us to change the region to the region the mouse is currently in. If
  1349.  there is more to the event than just ╥the mouse moved╙, we get called before the
  1350.  event is processed to make sure the cursor is the right one. In any (ahem) event,
  1351.  this is called again before we fall back into WNE.}
  1352.  
  1353. VAR
  1354.     window                : WindowPtr;
  1355.     arrowRgn            : RgnHandle;
  1356.     shapeRgn            : RgnHandle;
  1357.     globalPortRect        : Rect;
  1358.     
  1359.  
  1360. BEGIN
  1361.     window := FrontWindow;    {we only adjust the cursor when we are in front}
  1362.     IF (NOT gInBackground) AND (NOT IsDAWindow(window)) THEN BEGIN
  1363.         {calculate regions for different cursor shapes}
  1364.         arrowRgn := NewRgn;
  1365.         shapeRgn := NewRgn;
  1366.  
  1367.         {start with a big, big rectangular region}
  1368.         SetRectRgn(arrowRgn, kExtremeNeg, kExtremeNeg,
  1369.                             kExtremePos, kExtremePos);
  1370.  
  1371.         {calculate shapeRgn}
  1372.         IF IsAppWindow(window) THEN BEGIN
  1373.             SetPort(window);            {make a global version of the portRect}
  1374.             IF gMac.hasColorQD THEN
  1375.                 WITH CGrafPtr(window)^ DO
  1376.                     SetOrigin(-portPixMap^^.bounds.left, -portPixMap^^.bounds.top)
  1377.             ELSE
  1378.                 WITH window^.portBits.bounds DO
  1379.                     SetOrigin(-left, -top);
  1380.             globalPortRect := window^.portRect;
  1381.             RectRgn(shapeRgn, globalPortRect);
  1382.             SectRgn(shapeRgn, window^.visRgn, shapeRgn);
  1383.             SetOrigin(0, 0);
  1384.         END;
  1385.  
  1386.         {subtract other regions from arrowRgn}
  1387.         DiffRgn(arrowRgn, shapeRgn, arrowRgn);
  1388.  
  1389.         {change the cursor and the region parameter}
  1390.         IF PtInRgn(mouse, shapeRgn) THEN BEGIN
  1391.             IF gMac.hasColorQD THEN
  1392.                 SetCCursor(gCursor)
  1393.             ELSE
  1394.                 SetCursor(GetCursor(crossCursor)^^);
  1395.             CopyRgn(shapeRgn, region);
  1396.         END ELSE BEGIN
  1397.             SetCursor(arrow);
  1398.             CopyRgn(arrowRgn, region);
  1399.         END;
  1400.  
  1401.         {get rid of our local regions}
  1402.         DisposeRgn(arrowRgn);
  1403.         DisposeRgn(shapeRgn);
  1404.     END;
  1405. END; {AdjustCursor}
  1406.  
  1407.  
  1408. {$S Main}
  1409. PROCEDURE DoEvent(event: EventRecord);
  1410.  
  1411. {Do the right thing for an event. Determine what kind of event it is, and call
  1412.  the appropriate routines.}
  1413.  
  1414. VAR
  1415.     part, err    : INTEGER;
  1416.     window        : WindowPtr;
  1417.     ignore        : BOOLEAN;
  1418.     key            : CHAR;
  1419.     aPoint        : Point;
  1420.     fi            : FailInfo;
  1421.     
  1422.     PROCEDURE HandleErr(error: INTEGER; message: LongInt);
  1423.     BEGIN
  1424.         IF error > 0 THEN
  1425.             AlertUser(0, error)
  1426.         ELSE
  1427.             AlertUser(error, message);
  1428.         EXIT(DoEvent);
  1429.     END; {HandleErr}
  1430.  
  1431. BEGIN
  1432.     CatchFailures(fi, HandleErr);
  1433.     CASE event.what OF
  1434.         mouseDown: BEGIN
  1435.             part := FindWindow(event.where, window);
  1436.             CASE part OF
  1437.                 inMenuBar: BEGIN            {process the menu command}
  1438.                     AdjustMenus;
  1439.                     DoMenuCommand(MenuSelect(event.where));
  1440.                 END;
  1441.                 inSysWindow:                {let the system handle the mouseDown}
  1442.                     SystemClick(event, window);
  1443.                 inContent:
  1444.                     IF window <> FrontWindow THEN BEGIN
  1445.                         SelectWindow(window);
  1446.                         {DoEvent(event);}    {use this line for "do first click"}
  1447.                     END ELSE
  1448.                         DoContentClick(window, event);
  1449.                 inDrag:                        {pass screenBits.bounds to get all gDevices}
  1450.                     DragWindow(window, event.where, screenBits.bounds);
  1451.                 inGrow:;
  1452.                 inZoomIn, inZoomOut:;
  1453.                 inGoAway:
  1454.                     IF TrackGoAway(window, event.where) THEN
  1455.                         ignore := DoCloseWindow(window);
  1456.             END;
  1457.         END;
  1458.         keyDown, autoKey: BEGIN                {check for menukey equivalents}
  1459.             key := CHR(BAnd(event.message, charCodeMask));
  1460.             IF BAnd(event.modifiers, cmdKey) <> 0 THEN    {Command key down}
  1461.                 IF event.what = keyDown THEN BEGIN
  1462.                     AdjustMenus;            {enable/disable/check menu items properly}
  1463.                     DoMenuCommand(MenuKey(key));
  1464.                 END;
  1465.         END;                                {call DoActivate with the window and...}
  1466.         activateEvt:                        {TRUE for activate, FALSE for deactivate}
  1467.             DoActivate(WindowPtr(event.message), BAnd(event.modifiers, activeFlag) <> 0);
  1468.         updateEvt:                          {call DoUpdate with the window to update}
  1469.             DoUpdate(WindowPtr(event.message));
  1470.         diskEvt:
  1471.             IF HiWrd(event.message) <> noErr THEN BEGIN
  1472.                 SetPt(aPoint, kDILeft, kDITop);
  1473.                 err := DIBadMount(aPoint, event.message);
  1474.             END;
  1475.         kOSEvent:
  1476.             CASE BAnd(BRotL(event.message, 8), $FF) OF    {high byte of message}
  1477.                 kSuspendResumeMessage: BEGIN
  1478.                     gInBackground := BAnd(event.message, kResumeMask) = 0;
  1479.                     DoActivate(FrontWindow, NOT gInBackground);
  1480.                 END;
  1481.             END;
  1482.     END;
  1483.     Success(fi);
  1484. END; {DoEvent}
  1485.  
  1486.  
  1487. {$S Main}
  1488. PROCEDURE EventLoop;
  1489.  
  1490. {Get events forever, and handle them by calling DoEvent.
  1491.  Get the events by calling WaitNextEvent, if it's available, otherwise
  1492.  by calling GetNextEvent. Also call AdjustCursor each time through the loop.}
  1493.  
  1494. VAR
  1495.     cursorRgn    : RgnHandle;
  1496.     gotEvent    : BOOLEAN;
  1497.     event        : EventRecord;
  1498.     mouse        : Point;
  1499.  
  1500. BEGIN
  1501.     cursorRgn := NewRgn;                {we╒ll pass WNE an empty region the 1st time thru}
  1502.     REPEAT
  1503.         IF gHasWaitNextEvent THEN BEGIN    {put us 'asleep' forever under MultiFinder}
  1504.             GetGlobalMouse(mouse);        {since we might go to sleep}
  1505.             AdjustCursor(mouse, cursorRgn);
  1506.             gotEvent := WaitNextEvent(everyEvent, event, MAXLONGINT, cursorRgn);
  1507.         END ELSE BEGIN
  1508.             SystemTask;                    {must be called if using GetNextEvent}
  1509.             gotEvent := GetNextEvent(everyEvent, event);
  1510.         END;
  1511.         IF gotEvent THEN BEGIN
  1512.             AdjustCursor(event.where, cursorRgn);    {make sure we have the right cursor}
  1513.             DoEvent(event);
  1514.         END;
  1515.     UNTIL FALSE;                        {loop forever; we quit through an ExitToShell}
  1516. END; {EventLoop}
  1517.  
  1518.  
  1519. PROCEDURE _DataInit; EXTERNAL;
  1520.  
  1521. {This routine is part of the MPW runtime library. This external
  1522.  reference to it is done so that we can unload its segment, %A5Init.}
  1523.  
  1524. {$S Main}
  1525. BEGIN
  1526.     UnloadSeg(@_DataInit);    {note that _DataInit must not be in Main!}
  1527.     
  1528.     MoreMasters;
  1529.     MoreMasters;
  1530.     MoreMasters;            {prepare for handles used by Offscreen}
  1531.     
  1532.     {If you have stack requirements that differ from the default,
  1533.      then you could use SetApplLimit to increase StackSpace at 
  1534.      this point, before calling MaxApplZone.}
  1535.      
  1536.     MaxApplZone;            {expand the heap so code segments load at the top}
  1537.  
  1538.     InitSignals;
  1539.     Initialize;                {initialize the program}
  1540.     UnloadSeg(@Initialize);    {note that Initialize must not be in Main!}
  1541.  
  1542.     EventLoop;                {call the main event loop}
  1543. END.
  1544.