home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Snippets / BeachBall / BeachBall.c next >
Encoding:
C/C++ Source or Header  |  1993-04-10  |  12.6 KB  |  448 lines  |  [TEXT/KAHL]

  1. /******************************************************************
  2.  
  3. BeachBall.c
  4. Routines for asynchronous spinning beach-ball cursor
  5.  
  6. Based on & expanded from a code sample in Scott Knaster's excellent
  7. book, "Macintosh Programming Secrets", full of tons of truly
  8. useful & informative stuff.
  9.  
  10.  
  11. **************
  12.  
  13. ©1993 Peter Vanags, Electronic Ink; portions ©1992 Scott Knaster & Keith Rollin
  14. All rights reserved.
  15.  
  16. Distributed by:
  17.  
  18. Electronic Ink
  19. 72 Caselli Avenue
  20. San Francisco, CA 94114
  21.  
  22. AppleLink: INK.LINK
  23. CompuServe: 70401,3202
  24. Internet: ink.link@applelink.apple.com
  25.  
  26. ***************
  27.  
  28. ABOUT THE BEACH BALL ROUTINES
  29.  
  30. These routines can be used either synchronously (without the VBL task)
  31. or asynchronously. To use them in their intended manner (async):
  32.  
  33.     StartBeachBall (bbAcurID,TRUE,0); //starts the beach ball spinning
  34.     
  35.     ...complete whatever processes you need to...
  36.     
  37.     ReverseBeachBall (); //reverses the spin direction
  38.     
  39.     ...complete some more processes....
  40.     
  41.     StopBeachBall(); //stops the beach ball spinning
  42.     
  43.  
  44. Additionally, you can set the third argument to StartBeachBall to
  45. a non-zero value (say, 300 for 5 seconds' timeOut) and the beach
  46. ball will stop spinning after that many ticks have elapsed:
  47.  
  48.     #define timeOut 300    //set a standard timeout of 300 ticks (5 secs)
  49.     
  50.     StartBeachBall (bbAcurID,TRUE,timeOut); //starts the beach ball spinning
  51.     
  52.     ...do something...
  53.     
  54.     KeepSpinning (timeOut); //reset the timeOut counter
  55.     
  56.     ...do some more stuff...
  57.     
  58.     StopBeachBall(); //stops the beach ball spinning
  59.     
  60.  
  61. This is to prevent the beach ball from spinning merrily away as your
  62. program hangs.
  63.  
  64.  
  65.  
  66. To use the BeachBall routines synchronously, call them this way:
  67.  
  68.     StartBeachBall (bbAcurID,FALSE,0); //sets up the beach ball structures
  69.     
  70.     for (c=0;c<limit;c++) {     //run some type of loop
  71.  
  72.         SpinBeachBall();        //call this as often as you like
  73.         
  74.         ...do something...
  75.     
  76.     }
  77.     
  78.     StopBeachBall (); disposes of the beach ball structures
  79.     
  80.     
  81. ******************************************************************/
  82.  
  83.  
  84. #include <retrace.h>
  85. #include <traps.h>
  86. #include "BeachBall.h"
  87.  
  88.         
  89. //timing values for beach ball
  90. #define bbArrowTime        10        //ticks before we switch to a watch
  91. #define bbWatchTime        90        //ticks before the watch becomes a beachball
  92. #define bbSpinTime         8        //ticks between beachball spins
  93. #define bbRetryTime        5        //time to wait before re-trying (when CrsrBusy is true)
  94. #define bbSuspendTime    50        //time between re-checks while spinning is suspended
  95. #define    vblTaskKey        666        //a "key" value to check whether SpinBeachBall is VBL or not
  96.  
  97.  
  98. typedef struct {
  99.     unsigned short    numCursors;
  100.     unsigned short    index;
  101.     CursHandle        cursors[];
  102. } Acur,*AcurPtr,**AcurHandle;
  103.  
  104. typedef struct {
  105.     VBLTask            theTask;            //VBL task record, empty if we're not using a VBL task
  106.     short            key;                //a "key" value to detect whether SpinBeachBall is a VBL call or not
  107.     long            vblCountDown;        //tickCount at which we'll stop spinning
  108.     long            a5;                    //the a5 value of the current application
  109.     short            cursorPhase;        //phase of the cursor: suspended, arrow, watch or beachball
  110.     short            spinIncrement;        //value is either 1 or -1, to determine which way the ball spins
  111.     AcurHandle        theAcur;            //handle to the animated cursor
  112.     CursHandle        watchHandle;        //handle to the watch cursor, since we can't move memory in VBL
  113.     GrafPtr            startWindow;        //the front window when we started
  114. } BeachBallTask,*BeachBallTaskPtr;
  115.  
  116. //cursor phases
  117. enum {suspendedPhase,arrowPhase,watchPhase,bbPhase};
  118.  
  119. //low-memory globals
  120. char CrsrBusy    : 0x08cd;
  121. char MBState    : 0x0172;
  122.  
  123.  
  124. //some 'acur' utility routines...
  125. static AcurHandle InitAcur (short resID);
  126. static void LockAcur (AcurHandle theAcur);
  127. static void UnlockAcur (AcurHandle theAcur);
  128. static void DisposeAcur (AcurHandle theAcur);
  129.  
  130. //..and two global variables.
  131. static BeachBallTaskPtr    bbTask = nil;
  132. static long    lastTicks = 0;
  133.  
  134.  
  135. /******************************************************************
  136.  
  137. StartBeachBall sets things rolling. Call this routine
  138. with vblInstall set to TRUE at the start of a long
  139. operation to have automatic, asynchronous user feedback
  140. about the operation.
  141.  
  142. if vblInstall is FALSE, you'll nead to call SpinBeachBall()
  143. repeatedly from your routines to keep the ball rolling.
  144.  
  145. vblTimeOut is measured in ticks. If vblInstall is TRUE, and
  146. vblTimeOut is non-zero, KeepSpinning() must be called before the
  147. number of ticks in vblTimeOut have elapsed.
  148.  
  149. If KeepSpinning() is not called, the cursor stops spinning
  150. (freezes in whatever state it's in). This is so the cursor doesn't
  151. keep spinning merrily away when your program hangs. If you want
  152. to be sloppy (like me) or your programs never hang (not like me),
  153. set vblTimeOut to 0, and don't worry about making calls to
  154. KeepSpinning(). 
  155.  
  156. The beachball routine automatically adapts to the duration of your
  157. operations. Very short operations (<10 ticks) will not switch cursors.
  158. Operations up to about 2 seconds will get a wristwatch.
  159. After about 2 seconds, the cursor will change to a spinning beach ball.
  160. So to be safe, bracket ANY operation that might take a long time with
  161. calls to StartBeachBall and StopBeachBall. Remember that an
  162. instantaneous operation on the Quadra you program with
  163. might take a few seconds or longer on a Mac Classic!
  164.  
  165. ******************************************************************/
  166.  
  167. OSErr StartBeachBall (short acurID, Boolean vblInstall, long vblTimeOut) {
  168.  
  169.     if (bbTask = (BeachBallTaskPtr) NewPtrClear (sizeof(BeachBallTask))) {
  170.  
  171.         //initialize the generic fields
  172.         
  173.         bbTask->key = vblTaskKey;
  174.         bbTask->cursorPhase = arrowPhase;
  175.         bbTask->spinIncrement = 1;
  176.         bbTask->watchHandle = GetCursor(watchCursor);
  177.         bbTask->startWindow = FrontWindow();
  178.         
  179.         if (vblTimeOut)
  180.             bbTask->vblCountDown = Ticks + vblTimeOut;
  181.         else
  182.             bbTask->vblCountDown = 0;
  183.  
  184.         if (!(bbTask->theAcur = InitAcur (acurID)))  {
  185.             DisposePtr (bbTask);
  186.             bbTask = nil;
  187.             return ResError();
  188.         }
  189.  
  190.         bbTask->theTask.qType = vType;
  191.         bbTask->theTask.vblAddr = (ProcPtr) SpinBeachBall;
  192.         bbTask->theTask.vblCount = bbArrowTime;
  193.         bbTask->theTask.vblPhase = 0;
  194.         bbTask->a5 = (long) CurrentA5;
  195.  
  196.         if (vblInstall)
  197.             VInstall ((QElemPtr) bbTask);
  198.         else    
  199.             lastTicks = Ticks; //set up a global so we can manually decrement
  200.     }
  201.     else
  202.         return MemError();
  203.  
  204.     return noErr;
  205. }
  206.  
  207.  
  208. /******************************************************************
  209.  
  210. KeepSpinning() keeps the beachball spinning if vblTimeOut is non-zero.
  211.  
  212. ******************************************************************/
  213.  
  214. void KeepSpinning (long vblTimeOut) {
  215.     if (bbTask->vblCountDown)
  216.         bbTask->vblCountDown = Ticks + vblTimeOut;
  217. }
  218.  
  219.  
  220. /******************************************************************
  221.  
  222. ReverseBeachBall() spins the beach ball the other way.
  223.  
  224. ******************************************************************/
  225.  
  226. void ReverseBeachBall (void) {
  227.     bbTask->spinIncrement *= -1;
  228. }
  229.  
  230.  
  231. /******************************************************************
  232.  
  233. SuspendBeachBall and ResumeBeachBall are macros to optimize the 
  234. VBL task's performance
  235.  
  236. ******************************************************************/
  237.  
  238. #define SUSPEND_BEACH_BALL(theBBTask)  \
  239.     if (theBBTask && theBBTask->cursorPhase) { \
  240.         theBBTask->cursorPhase = suspendedPhase; \
  241.         theBBTask->theTask.vblCount = bbSuspendTime; \
  242.         SetCursor (&arrow); \
  243.     } \
  244.  
  245.  
  246. #define RESUME_BEACH_BALL(theBBTask)  \
  247.     if (theBBTask && !theBBTask->cursorPhase) { \
  248.         theBBTask->cursorPhase = bbPhase; \
  249.         theBBTask->theTask.vblCount = bbSpinTime; \
  250.     } \
  251.  
  252.  
  253.  
  254. /******************************************************************
  255.  
  256. SpinBeachBall is meant to be called as a VBL task, so we
  257. have to make sure to set the a5 world properly.
  258.  
  259. But SpinBeachBall can also be called from your own routines
  260. repeatedly, if you decide not to use it as a VBL task. It
  261. should work fine in either case.
  262.  
  263. While the beachball is spinning, we need to keep on the lookout
  264. for alert dialogs and mouseclicks. In both instances, we need
  265. to suspend the beach ball spinning until the dialog is
  266. dismissed or the mouse button is released. These actions mainly
  267. apply to the VBL usage.
  268.  
  269. If you don't use the VBL version, be sure to set the cursor to an
  270. arrow when you put up alert dialogs. You don't have to worry about this
  271. for the VBL version.
  272.  
  273. SpinBeachBall checks the status of the mouse button and the front
  274. window. If the mouse button is down, or the front window
  275. is not the same window as it was at the time StartBeachBall
  276. was called (and that window is a dialog-type), beach ball spinning
  277. will be suspended.
  278.  
  279. ******************************************************************/
  280.  
  281. void SpinBeachBall (void) {
  282.     long                oldA5;
  283.     BeachBallTaskPtr    theBBTask;
  284.     GrafPtr                w;
  285.     Boolean                isVBL;
  286.     
  287.     //this assembly code puts a0 (which points to our VBLTask record)
  288.     //into a local variable, so we can access our beach ball data
  289.     asm {
  290.         move.l A0,theBBTask
  291.     }
  292.     
  293.     //check the "key" value to see if a0 was pointing to
  294.     //our VBL task record, or just a random address
  295.     
  296.     if (isVBL = (theBBTask && (theBBTask->key == vblTaskKey)))
  297.         oldA5 = SetA5 (theBBTask->a5);
  298.     else {
  299.         //not a VBL Task
  300.         theBBTask=bbTask;
  301.         //Manually decrement & check the VBL count
  302.         if ((theBBTask->theTask.vblCount -= (Ticks - lastTicks)) > 0) {
  303.             lastTicks = Ticks;
  304.             goto exit;
  305.         }
  306.     }
  307.  
  308.     if (!CrsrBusy) { //make sure we can change the cursor now
  309.     
  310.         //check if the timeOut has elapsed
  311.         if (theBBTask->vblCountDown && (Ticks > theBBTask->vblCountDown)) {
  312.             theBBTask->theTask.vblCount = bbSuspendTime;
  313.             goto exit;
  314.         }
  315.             
  316.         //check if we should suspend beach ball spinning
  317.         if (((MBState == 0) && theBBTask->cursorPhase) ||
  318.             (((w=FrontWindow()) != theBBTask->startWindow) &&
  319.             (w && (((WindowPeek)w)->windowKind == dialogKind)))) {
  320.             SUSPEND_BEACH_BALL(theBBTask);
  321.         }
  322.         else if (MBState && !theBBTask->cursorPhase)
  323.             RESUME_BEACH_BALL(theBBTask);
  324.  
  325.         if (theBBTask->cursorPhase) {
  326.             switch (theBBTask->cursorPhase) {
  327.                 case arrowPhase:
  328.                     SetCursor(*theBBTask->watchHandle);
  329.                     theBBTask->theTask.vblCount = bbWatchTime;
  330.                     theBBTask->cursorPhase = watchPhase;
  331.                     break;
  332.                 case watchPhase:
  333.                     //set the phase & fall thru to the default action
  334.                     theBBTask->cursorPhase = bbPhase;
  335.                 default:
  336.                     (**(theBBTask->theAcur)).index += theBBTask->spinIncrement;
  337.                     (**(theBBTask->theAcur)).index %= (**(theBBTask->theAcur)).numCursors;
  338.                     //the next two lines turn negative increments around, but leave positive increments alone
  339.                     (**(theBBTask->theAcur)).index += (**(theBBTask->theAcur)).numCursors;
  340.                     (**(theBBTask->theAcur)).index %= (**(theBBTask->theAcur)).numCursors;
  341.                     SetCursor (*(**(theBBTask->theAcur)).cursors[(**(theBBTask->theAcur)).index]);
  342.                     theBBTask->theTask.vblCount = bbSpinTime;
  343.                     break;
  344.             }
  345.         }
  346.         else 
  347.             theBBTask->theTask.vblCount = bbSuspendTime;
  348.  
  349.     }
  350.     else   //the cursor can't be changed now, so try again in a little while...
  351.         theBBTask->theTask.vblCount = bbRetryTime;
  352.  
  353. exit:
  354.     if (isVBL)
  355.         SetA5 (oldA5);
  356.  
  357. }
  358.  
  359.  
  360. /******************************************************************
  361.  
  362. StopBeachBall stops the spinning and disposes of all the
  363. beachball structures
  364.  
  365. ******************************************************************/
  366.  
  367. void StopBeachBall (void) {
  368.     if (bbTask) {
  369.         VRemove ((QElemPtr)bbTask);
  370.         DisposeAcur (bbTask->theAcur);
  371.         DisposePtr (bbTask);
  372.         bbTask = nil;
  373.         SetCursor(&arrow);
  374.     }
  375. }
  376.  
  377.  
  378.  
  379. /******************************************************************
  380.  
  381. Animated cursor ('acur') utilities
  382. These utilities deal with loading an 'acur' and its related
  383. 'CURS' resources, locking & unlocking them in memory,
  384. and proper disposal
  385.  
  386. ******************************************************************/
  387.  
  388. static AcurHandle InitAcur (short resID) {
  389.     short         cursorCount;
  390.     CursHandle    *workPtr;
  391.     AcurHandle    theAcur = (AcurHandle) GetResource('acur',resID);
  392.     
  393.     if (theAcur) {
  394.         DetachResource (theAcur);
  395.         cursorCount = (*theAcur)->numCursors;
  396.         (*theAcur)->index = 0;
  397.         
  398.         HLock(theAcur);
  399.         workPtr = (*theAcur)->cursors;
  400.         while (cursorCount--)
  401.             if (!(*workPtr++ = (CursHandle) GetResource ('CURS',*(short*)workPtr))) {
  402.                 HUnlock (theAcur);
  403.                 DisposeHandle (theAcur);
  404.                 return nil;
  405.             }
  406.         HUnlock (theAcur);
  407.     }
  408.     return theAcur;
  409. }
  410.  
  411. static void LockAcur (AcurHandle theAcur) {
  412.     short        cursorCount;
  413.     CursHandle    *workPtr;
  414.     
  415.     cursorCount = (*theAcur)->numCursors;
  416.     
  417.     HLockHi (theAcur);
  418.     workPtr = (*theAcur)->cursors;
  419.     while (cursorCount--)
  420.         HLockHi(*workPtr++);
  421. }
  422.  
  423. static void UnlockAcur (AcurHandle theAcur) {
  424.     short        cursorCount;
  425.     CursHandle    *workPtr;
  426.     
  427.     cursorCount = (*theAcur)->numCursors;
  428.     
  429.     workPtr = (*theAcur)->cursors;
  430.     while (cursorCount--)
  431.         HUnlock(*workPtr++);
  432.     HUnlock (theAcur);
  433. }
  434.  
  435. static void DisposeAcur (AcurHandle theAcur) {
  436.     short        cursorCount;
  437.     CursHandle    *workPtr;
  438.     
  439.     cursorCount = (*theAcur)->numCursors;
  440.     
  441.     HLock(theAcur);
  442.     workPtr = (*theAcur)->cursors;
  443.     while (cursorCount--)
  444.         ReleaseResource(*workPtr++);
  445.     HUnlock(theAcur);
  446.     DisposeHandle (theAcur);
  447. }
  448.