TweetFollow Us on Twitter

Cursor Animation
Volume Number:7
Issue Number:3
Column Tag:C Workshop

Related Info: Vert. Retrace Mgr

Animated Color Cursors

By Richard Lesh, Bridgeton, MO

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

Animated Color Cursors

[Richard Lesh is a free-lance programmer who has been programming in C since 1983 and has been doing development on the Macintosh for the past four years. He has done work in a number of fields including numerical methods and analysis, computer graphics, artificial intelligence and neural networks. You can contact him on AppleLink at CALICO or through INTERNET at CALICO@APPLELINK.APPLE.COM]

Introduction

Since the introduction of HyperCard, more and more programs have been sporting animated cursors. Using animated cursors is an attractive way to provide visual feedback to the user that the computer is diligently working on a time consuming task. The standard wrist watch cursor does not provide this type of dynamic feedback to assure the user that things are proceeding normally. This article will present a new method of implementing animated cursors that supports both monochrome and color cursors. Also presented is a simple demo program that exercises the cursor control routines using a number of monochrome and color cursors.

Background

In the past there have been two approaches to implementing animated cursors. The first type is seen in HyperCard, the Finder and in the MPW library cursor control routines. Using this technique, the programmer simply intersperses frequent calls to a routine that sets the cursor to the next cursor in the animation sequence. It typically results in cursor animation that is not smooth, i.e. jerky. The second and less popular method is to set up a VBL task that changes the cursor at a constant rate, thus providing smooth animation. More about these techniques and VBL tasks will be presented later in this article. Both of these techniques have shortcomings that the technique presented here attempts to overcome.

As a general rule of thumb, there are a number of issues that any animated cursor technique should address:

1. It should provide feedback that the computer is executing a task normally.

2. It should provide smooth animation of the cursor.

3. It should support both monochrome (CURS) and color (crsr) cursors.

Each of these issues will now be discussed and the methodology for addressing each will be presented.

Visual Feedback

The primary reason for using animated cursors is to give some reassuring feedback to the user that everything is proceeding normally during a lengthy task that otherwise would not provide visual feedback. In order to set up a sequence of animated cursors, one must first create separate cursor resources for each ‘frame’ of the animation. Each ‘frame’ differs slightly from the next and when displayed in rapid succession they give the illusion of motion. Figure 1 shows an example of the monochrome (CURS) Beach Ball cursor resources commonly seen in HyperCard.

Figure 1. Beach Ball cursors (CURS)

In addition to creating the necessary cursor resources, a resource must be created to store the information about the sequencing of the cursor ‘frames’. This resource is supported by the latest versions of ResEdit and is given the type name ‘acur’ (animated cursor). Figure 2 shows the ‘acur’ resource, as displayed by ResEdit, needed to animate the Beach Ball cursors.

The ‘acur’ resource contains a count of the number of cursor resources in the animation sequence followed by the resource IDs of the cursors in the order that they will appear in the sequence. The counter ‘Number of “Frames”’ is not maintained automatically for you; you must make sure to update it after adding additional cursor ‘frames’. To insert new ‘frames’ in the resource one merely selects the ‘*****’ string and then chooses ‘Insert New Field’ from the Resource menu in ResEdit to insert a space for the new cursor resource ID. The “Frame Counter” field is simply a filler and its value is unimportant. This value will, however, be used to keep track of the current frame after the ‘acur’ resource has been loaded and the cursor animation begins.

Smooth Animation

MPW and HyperCard both use a cursor animation method that requires the execution of specific commands to cause the animation to proceed to the next frame. In the MPW cursor control library these functions are called SpinCursor() and RotateCursor(). Each are similar in function but take different arguments. In HyperCard, the command ‘set cursor to busy’ performs the same function. This technique requires the programmer to sprinkle cursor commands generously throughout the code of a function that may cause a noticeable delay to the user. Not only does this destroy the readability of a code segment but it is difficult in practice to space the calls to SpinCursor() or RotateCursor() at appropriate locations that will generate smooth animation of the cursor. It is more common for the cursor to display somewhat random, jerky motion. This is undesirable and can be overcome by setting up a vertical blanking (VBL) task that will automatically change the cursor to the next cursor in the sequence at a predetermined time interval. The programmer now only has to start the cursor animation before a time consuming task and then remember to stop the animation when the task completes.

Figure 2. ‘acur’ Resource

VBL Tasks

Those who are not familiar with VBL tasks can refer to the “Vertical Retrace Manager” chapter of Inside Macintosh, Vol. II. In short, the Macintosh video circuitry generates an interrupt, known as the vertical blanking interrupt, 60 times a second. This interrupt signals applications that the beam of the display tube is travelling from the bottom right of the screen back to the top left. Since pixels are not being refreshed by the electron beam during this short interval, it is a good time for applications to draw to the screen without fear of the screen being partially updated before the drawing operation is finished. This facilitates smooth, flicker-free animation. Since the VBL interrupt occurs at constant intervals, it is also a good time to do periodic tasks such as incrementing the tick count, handling cursor movement and posting mouse and keyboard events, all of which the system does for you during VBL interrupts. One can take advantage of the VBL interrupt by introducing a user defined task into the VBL queue to be executed periodically.

There are two routines needed to setup VBL tasks, one to insert the task into the VBL queue, InstallVBLTask(), and one to remove the task when it is no longer needed, RemoveVBLTask(). These routines require the definition of the VBLTask structure defined in VRetraceMgr.h. To install a task one needs to pass a pointer to the procedure to be executed at interrupt time and the interval to wait between executions of the procedure. The VBL task procedure must be declared as a Pascal void function with no arguments. These two functions are very simple and are presented below.

/* 1 */

VBLTask *InstallVBLTask(ProcPtr proc, short ticks)
{
 OSErr err;
 VBLTask *taskPtr;
 
 taskPtr=(VBLTask *)NewPtr(sizeof(VBLTask));
 if (taskPtr){
 taskPtr->qType=vType;
 taskPtr->vblAddr=proc;
 taskPtr->vblCount=ticks;
 taskPtr->vblPhase=0;
 err=VInstall((QElemPtr)taskPtr);
 if (err!=noErr){
 DisposPtr(taskPtr);
 taskPtr=NULL;
 }
 }
 return(taskPtr);
}

void RemoveVBLTask(VBLTask *taskPtr)
{
 VRemove((QElemPtr)taskPtr);
 DisposPtr(taskPtr);
}

While the VBL technique is easy to implement and has been implemented in a number of commercial packages, by itself it is not an acceptable approach. This is because the user can no longer have confidence that the program is working properly when the cursor is still spinning. By using a VBL task to separate the cursor animation from the execution of the program, situations can occur where the program has crashed but the cursor keeps spinning away. This problem can be overcome by combining both techniques, thereby creating a function, such as our new SpinCursor(), that instead of setting the cursor to the next cursor in the ‘acur’ sequence, simply stores a duration value in a global variable that is then accessed by a VBL task. The VBL task smoothly animates the cursor but is constantly decrementing the count stored in the global duration variable. If SpinCursor() is not periodically called to reset the duration variable, the VBL task will eventually ‘time out’ and the cursor animation will cease. In this implementation SpinCursor() is called with an integer argument that specifies the number of seconds to spin the cursor before it will ‘time out’. The function CursorSpeed() can be called to set the number of ticks that occur between each frame of the animation.

The VBL task that actually changes the cursor in these cursor routines is called SpinCursorTask(). The ticks parameter passed to InstallVBLTask() specifies the number of ticks (1/60th of a second) to wait between successive calls to the task. This ticks parameter is then stored in the vblCount field of the VBLTask structure. This value is decremented once during each VBL interrupt, and when it reaches zero, the procedure passed in the proc argument is called. The VBL task is then responsible for resetting the value of the vblCount field so that it will be called again after the appropriate interval. The VBL task can access the original value of ticks because it is defined and stored in the global gSpeed by a call to the procedure SetCursorSpeed(). Notice that our VBL task procedure not only resets the vblCount field so that it will be called again, but it also decrements the global variable gSpinCycles and changes the cursor to the next cursor in the animation sequence. The global variable gSpinCycles is set only by the SpinCursor() procedure and when gSpinCycles reaches zero the cursor has ‘timed out’ and will no longer be animated by the VBL task. In order to access global variables like gSpinCycles and gSpeed, the calls to SetCurrentA5() and SetA5() are necessary to ensure the proper contents of the A5 register. The older routines, SetUpA5() and RestoreA5(), are no longer recommended (See Technical Note #208).

/* 2 */

static pascal void SpinCursorTask()
{
 long oldA5;

 oldA5=SetCurrentA5();
 gCursorTask->vblCount=gSpeed;
 if (gSpinCycles){
 gSpinCycles--;
 (*gCurrentHdl)->index++;
 (*gCurrentHdl)->index%=(*gCurrentHdl)->n;
 if (gColorCursor) 
 SetCCursor( );
 else
 SetCursor( );
 }
 SetA5(oldA5);
}

Color Cursors

Unlike HyperCard and the MPW cursor control library, the functions presented here support the use of color cursors (crsr). The initialization function InitCursorCtrl() will first attempt to load the color cursors (crsr) with the IDs specified in the ‘acur’ handle passed to it. If the color cursors are not found or can not be loaded, it then attempts to load the monochrome cursors (CURS) with those same IDs. If these cursors can not be found or otherwise loaded, the static wrist watch cursor will be displayed. InitCursorCtrl() is normally called prior to SpinCursor() to define which cursors should be used. It can be called repeatedly to switch between one set of animated cursors and another. If SpinCursor() is called before a call to InitCursorCtrl(), the ‘acur’ resource with ID 128 will be loaded along with its cursors and set up as the current animated cursor list.

Unfortunately, the toolbox call that sets the cursor to a color cursor, SetCCursor(), can move or purge memory. It does so because the call uses the ‘crsr’ resource as a template and then creates pixmaps to represent the cursor on each monitor connected to the Macintosh. This is unfortunate because VBL tasks are not allowed to call Memory Manager routines. This is a serious potential problem with the VBL approach. Since the SetCCursor() call needs to set up these pixmaps only once, when color cursors are loaded via the InitCursorCtrl() call, the cursor is hidden and each cursor is then passed to SetCCursor() so that these pixmaps can be set up. This seems to eliminate the problem of calling Memory Manager routines from within a VBL task because the subsequent calls to SetCCursor() made by the VBL task no longer need to set up pixmaps since they have already been allocated. In any event, this is not a problem with monochrome cursors because SetCursor() does not move or purge memory.

How to Use These Routines

These routines are very simple to use. The module is set up through a call to InitCursorCtrl(). This routine is passed a handle to an ‘acur’ resource in memory and is typically called like this:

/* 3 */

InitCursorCtrl((acurHandle)GetResource(‘acur’,ID));

It can be passed the value NULL, and it will attempt to load and use the ‘acur’ resource with ID 128. This routine is automatically called by SpinCursor() with a NULL argument if the module has not been previously set up by an explicit call to InitCursorCtrl().

Cursor animation is accomplished through frequent calls to SpinCursor(). It is passed an integer value that specifies the number of seconds that the cursor should spin before it stops. You can make frequent calls to SpinCursor() with small values like one or two seconds throughout your routine, or you can preface your routine with a single call that may spin the cursor for 30 seconds or so. The maximum value allowable is 546 seconds or about nine minutes. A large value like this should never be used because the user would not know that the program has malfunctioned until nine minutes later. In general, values less than 60 seconds should be used. The duration values passed in SpinCursor() do not accumulate but simply set the duration counter, gSpinCycles, to the value passed. A call to SpinCursor() with a value of 10 seconds followed by another call to SpinCursor() with a value of two seconds will cause the cursor to spin for only two seconds after the second call.

The SpinCursor() routine will set up the VBL task if it is not already running. When you need to stop the cursor animation, make a call to StopCursor(). This call must be used even if the cursor has ‘timed out’ because the cursor remains set to the last ‘frame’ in the animated cursor list that it was set to when it ‘timed out’. StopCursor() removes the VBL task but does not reset the cursor to the standard arrow cursor. After the call to StopCursor() you are free to change the cursor or reset it through a call to InitCursor(). One could wait until the cursor has ‘timed out’ and then explicitly set the cursor to a new value, but this would still leave the ‘timed out’ VBL task in the VBL queue. While this is not harmful and since the VBL task will no longer try to change the cursor, this practice is workable but not encouraged. Without an explicit call to StopCursor() you can not be sure that the animation has terminated. StopCursor() should be called before the application is switched out under MultiFinder. Even though the VBL task won’t be called while the application is switched out, update events cause a minor switch to occur which causes the VBL task to run for a short period of time while you are still in the background. Likewise, if your application accepts background NULL events you must call StopCursor() when you receive the suspend event (major switch) because the application is switched back in temporarily (minor switch) during NULL events and the VBL task will cause the foreground application’s cursor to change. InitCursorCtrl() calls StopCursor() before it changes the ‘acur’ list and loads the new cursors. It may or may not reset the cursor to the standard arrow cursor depending on whether color cursors have been loaded.

The speed of the animation can be controlled through calls to SetCursorSpeed(). It takes as an argument the number of ticks that must elapse between ‘frames’ in the animation. A value of 60 will cause the cursor to change every second, while a value of four will cause it to change 15 times a second. Finally, QuitCursorCtrl() closes down the cursor control routines, disposes of cursors, and removes the VBL task if needed.

One note of caution is needed: DO NOT make any calls to SetCCursor() or SetCursor() unless the cursor animation has been stopped via a call to StopCursor(). Since the VBL task can be executed during your call, this would cause a re-entrant condition that neither SetCursor() or SetCCursor() handle very well. You will end up with cursor fragments littered all over your screen and possibly a system crash if this warning is not heeded.

The demo program Cursor Test is provided to illustrate the use of these routines. It has a ‘Cursors’ menu and a ‘Speed’ menu that allow you to control the cursor and rotation speed. The ‘File’ menu contains the the choices ‘Start Cursor’ and ‘Stop Cursor’ which start and stop the cursor animation. When you select ‘Start Cursor’ the cursor will rotate for five seconds by making the call SpinCursor(5). The menu will remain hilighted for six seconds to simulate some time consuming task. Since the cursor was instructed to rotate for five seconds, it will ‘time out’ one second before the simulated task completes. The ‘Stop Cursor’ menu item calls StopCursor() and then re-initializes the cursor to the standard arrow cursor.

Summary

The advantages of this technique are primarily ease of use for the programmer and high quality visual feedback for the end user. The routines presented work on QuickDraw machines and Color QuickDraw machines without any changes to the programming interface. All one needs to do is to define an ‘acur’ resource, and the corresponding monochrome (‘CURS’) and color (‘crsr’) cursor resources. The routines will take care of displaying the correct cursors. Within application code itself one only needs to call SpinCursor() on a casual basis and the rest is automatic. The end user sees high quality animated color cursors with smooth motion that will stop if anything goes wrong with the program. Unfortunately there is still the potential problem with the calls to SetCCursor() from within a VBL task; however, preloading the color cursors should resolve the problem.

Acknowledgements

Thanks to Brian Zuc for his help in getting me started with the VBL manager and Steve Fisher for his helpful comments on this article.

[Due to space limitations, only one animated cursor is listed. Those, and a number of others, are included on the source code disk for this issue.-ed]

Listing: Cursor Test Π

Cursor Test.c
CursorCtrl.c
MacTraps
SANE
Listing: CursorCtrl.c

/**********************************************************
Copyright (c) 1990 Richard Lesh, All Rights Reserved 
**********************************************************/

#include <MacHeaders>
#include <Color.h>
#include <VRetraceMgr.h>

/**********************************************************
Module: Cursor Control

This module provides support for the use of dynamic cursors
which give better feedback to the user that the program is
working properly.  This module has an interface similar to
the MPW version CursorCtrl.c but does not necessarily
function in an identical manner.  (MPW does not support
color cursors).  This version uses a VBL task to smooth out
the cursor animation.  It does not, hoever, allow the cursor
to continue spinning in the event of a system crash.  The
application must execute SpinCursor on a regular basis to
keep the cursor spinning.  RotateCursor is not supported in
this version.  InitCursorCtrl is called to initialize the
cursors or to change them to a new set of cursors.
StopCursor is called to halt the cursor animation so that
it can then be set to the arrow or other application defined
cursors.

Routines:
InitCursorCtrl() -- Initializes the module & loads cursors.
QuitCursorCtrl() -- Cleans up when animated cursors are no
                    longer needed.
SpinCursor()     -- Starts or continues cursor animation.
StopCursor()     -- Stops the cursor animation.
SetCursorSpeed() -- Sets the speed of cursor rotation.
**********************************************************/

/**********************************************************
Definitions
**********************************************************/
#ifndef NULL
#define NULL 0L
#endif

/**********************************************************
TypeDefs and Enums
**********************************************************/
/* Animated cursor control structure 'acur' */
typedef struct {
 short n;
 short index;
 union {
 Handle cursorHdl;
 short resID;
 }frame[1];
}acurRec,*acurPtr,**acurHandle;

/**********************************************************
Private Globals
**********************************************************/
/* Current 'acur' resource handle */
static acurHandle gCurrentHdl=NULL;
/* True if using color cursors */
static Boolean gColorCursor=FALSE;
/* VBL task record for the cursor */
static VBLTask *gCursorTask=NULL;
/* Number of cycles to spin cursor */
static short gSpinCycles=0;
/* Number of ticks between changes */
static short gSpeed=10;
/* TRUE after SpinCursor() and FALSE after StopCursor() */
static Boolean isCursorRunning=FALSE;

/**********************************************************
Prototypes
**********************************************************/
/* Functions to be exported */
void InitCursorCtrl(acurHandle h);
void QuitCursorCtrl(void);
void SpinCursor(short cycles);
void StopCursor(void);
void SetCursorSpeed(short newSpeed);

/* Functions to be used internally */
void DisposCursors(void);
Boolean GetMonoCursors(acurHandle h);
Boolean GetColorCursors(acurHandle h);
pascal void SpinCursorTask(void);

Boolean hasColorQD(void);
VBLTask *InstallVBLTask(ProcPtr proc,short ticks);
void RemoveVBLTask(VBLTask *taskPtr);

/**********************************************************
Routine:InitCursorCtrl(acurHandle resHdl)

This routine initializes the CursorCtrl module using the
'acur' resource indicated via the argument resHdl.  If
resHdl is NULL then the 'acur' resource with ID=128 is
loaded. If the machine has ColorQD, InitCursorCtrl first
attempts to load the color cursor 'crsr' resources with the
IDs indicated in the 'acur' resource.  If this fails or if
the machine does not have ColorQD, InitCursorCtrl attempts
to load the normal cursor 'CURS' resources with the IDs
indicated in the 'acur' resource.  If this action fails,
all subsequent calls to SpinCursor will simply set the
cursor to the watch cursor.

InitCursorCtrl should be called as follows:

InitCursorCtrl((acurHandle)GetResource('acur',200));

If GetResource is unable to find the 'acur' resource it
returns NULL which can be handled by InitCursorCtrl.
InitCursorCtrl will mark the resource as unpurgable and
will be responsible for releasing the all cursor storage
and the 'acur' resource when called again with a new 'acur'
handle.  If the new handle is the same as the current
handle, nothing will be done.  Since 'crsr' resources are
just templates for a color cursor, you should make sure
that all 'crsr' resources are marked purgable in the
resource file since GetCCursor does not release these
resources.
**********************************************************/

void InitCursorCtrl(acurHandle h)
{
 short i,j;
 Boolean useColorCursors;

 useColorCursors=hasColorQD();
 
 if (!h) h=(void *)GetResource('acur', 128);
 if (h && h!=gCurrentHdl){
 HNoPurge(h);
 MoveHHi(h);
 HLock(h);
 
/**********************************************************
Get new cursors.
**********************************************************/
 StopCursor();
 if (useColorCursors)
 useColorCursors=GetColorCursors(h);
 if (!useColorCursors && !GetMonoCursors(h)) return;

 DisposCursors();
 gCurrentHdl=h;
 gColorCursor=useColorCursors;
 (*h)->index=0;
 }
}

/**********************************************************
Routine:QuitCursorCtrl()

Shuts down the cursor control module.
**********************************************************/

void QuitCursorCtrl()
{
 DisposCursors();
 if (gCursorTask) RemoveVBLTask(gCursorTask);
 gCursorTask=NULL;
}

/**********************************************************
Routine:DisposCursors()

Disposes the cursors pointed to in the 'acur' structure.
**********************************************************/

void DisposCursors()
{
 register short i,j;
 
 StopCursor();
 if (gCurrentHdl){
 j=(*gCurrentHdl)->n;
 if (gColorCursor)
 for (i=0;i<j;i++)
 DisposCCursor((*gCurrentHdl)->frame[i].cursorHdl);
 else
 for (i=0;i<j;i++)
 DisposHandle((*gCurrentHdl)->frame[i].cursorHdl);
 ReleaseResource(gCurrentHdl);
 gCurrentHdl=NULL;
 }
}

/**********************************************************
Routine:GetMonoCursors(acurHandle h)

This is an internal routine that loads the normal cursors
('CURS') from the resource file. In the 'acur' resource,
the resource ID of the cursor is stored in the frame union.
When the cursor has been loaded, its handle is then stored
in the frame union.  The function returns TRUE if it was
successful at loading in all the cursors and FALSE if there
was an error.
**********************************************************/

static Boolean GetMonoCursors(acurHandle h)
{
 short i,j;
 CursHandle cursHdl;

 if (h){
 j=(*h)->n;
 for (i=0;i<j;i++){
 cursHdl=GetCursor((*h)->frame[i].resID);
 if (cursHdl==NULL){
 for (j=0;j<i;j++)
 DisposHandle((*h)->frame[j].cursorHdl);
 return(FALSE);
 }else{
 DetachResource(cursHdl);
 (*h)->frame[i].cursorHdl=(Handle)cursHdl;
 }
 }
 }
 return(TRUE);
}

/**********************************************************
Routine:GetColorCursors(acurHandle h)

This is an internal routine that loads the color cursors
('crsr') from the resource file. In the 'acur' resource,
the resource ID of the cursor is stored in the frame union.
When the cursor has been loaded, its handle is then stored
in the frame union.  The function returns TRUE if it was
successful at loading in all the cursors and FALSE if there
was an error.  The 'crsr' resources should be set purgable.
**********************************************************/

static Boolean GetColorCursors(acurHandle h)
{
 short i,j;
 CCrsrHandle cursHdl;
 Boolean result=TRUE;

 if (h){
 j=(*h)->n;
 HideCursor();
 for (i=0;i<j;i++){
 cursHdl=GetCCursor((*h)->frame[i].resID);
 if (cursHdl==NULL){
 for (j=0;j<i;j++)
 DisposCCursor((*h)->frame[j].cursorHdl);
 result=FALSE;
 break;
 }else{
 (*h)->frame[i].cursorHdl=(Handle)cursHdl;
 SetCCursor((*h)->frame[i].cursorHdl);
 }
 }
 InitCursor();
 }
 return(result);
}

/**********************************************************
Routine: SpinCursor(short seconds)

This routine sets gSpinCycles to seconds*60/gSpeed which is
the number of times that the cursor should spin before
stopping. The seconds parameter should therefore be set to
just slightly longer than the time estimated to execute the
code up to the next SpinCursor call.  When the gSpinCycles
value counts down to zero the cursor will no longer spin
and the user will thereby be notified that something is
awry. If there is no current 'acur' resource, this routine
will set the cursor the the watch cursor.
**********************************************************/

void SpinCursor(short seconds)
{
 static long counter=0;
 
 if (gCurrentHdl==0) InitCursorCtrl(NULL);
 if (gCurrentHdl){
 if (!gCursorTask)
 gCursorTask=InstallVBLTask((ProcPtr)SpinCursorTask,
 gSpeed);
 if (gCursorTask){
 if (gSpinCycles==0){
 if (gColorCursor) 
 SetCCursor((*gCurrentHdl)->frame[
 (*gCurrentHdl)->index].cursorHdl);
 else 
 SetCursor(*(*gCurrentHdl)->frame[
 (*gCurrentHdl)->index].cursorHdl);
 }
 gSpinCycles=seconds*60/gSpeed;
 }
 }else
 SetCursor(*GetCursor(watchCursor));
 isCursorRunning=TRUE;
}

/**********************************************************
Routine:SpinCursorTask()

This is the VBL task routine.  If the gSpinCycles global is
not zero it will decrement gSpinCycles and then advance the
cursor to the next one specified by the 'acur' resource in
gCurrentHdl.  This routine does call SetCCursor which can
call the memory manager.  The results could be severe if
the cursors had not be preloaded.  Since application VBL
tasks are only called when the application is in the
foreground, the global CurrentA5 used by SetCurrentA5()
will be the correct value.
**********************************************************/

static pascal void SpinCursorTask()
{
 long oldA5;

 oldA5=SetCurrentA5();
 gCursorTask->vblCount=gSpeed;
 if (gSpinCycles){
 gSpinCycles--;
 (*gCurrentHdl)->index++;
 (*gCurrentHdl)->index%=(*gCurrentHdl)->n;
 if (gColorCursor) 
 SetCCursor((*gCurrentHdl)->frame[(*gCurrentHdl)
 ->index].cursorHdl);
 else
 SetCursor(*(*gCurrentHdl)->frame[(*gCurrentHdl)
 ->index].cursorHdl);
 }
 SetA5(oldA5);
}

/**********************************************************
Routine:StopCursor()

This routine will stop the cursor animation by setting the
gSpinCycles global to zero.  This must be called before the
application changes the cursor to a normal cursor because
the VBL task will continue to set the cursor to the
animated cursors until the gSpinCycles count has run out.
**********************************************************/

void StopCursor()
{
 gSpinCycles=0;
 isCursorRunning=FALSE;
 if (gCursorTask) RemoveVBLTask(gCursorTask);
 gCursorTask=NULL;
}

/**********************************************************
Routine:SetCursorSpeed(short newSpeed)

This routine sets the number of ticks that must occur
before the cursor will change to the next one in the
animation sequence.
**********************************************************/

void SetCursorSpeed(short newSpeed)
{
 if (newSpeed>0 && newSpeed<=60)
 gSpeed=newSpeed;
}

/**********************************************************
Routine: Boolean hasColorQD()

Predicate that returns TRUE if the current machine supports
Color Quickdraw.
**********************************************************/

#define SysEnvironsVersion 2
Boolean hasColorQD()
{
 OSErr theErr;
 SysEnvRec theWorld;
 
 theErr=SysEnvirons(SysEnvironsVersion,&theWorld);

 if (theErr == 0 && theWorld.hasColorQD) return(TRUE);
 else return(FALSE);
}

/**********************************************************
Routine: VBLTask *InstallVBLTask(ProcPtr proc,short ticks)

This routine installs the VLBTask pointed to by the ProcPtr
which will begin execution after the number of ticks
specified.  A pointer to a VBL task record is returned.
NULL is returned if the task could not be set up.
**********************************************************/

VBLTask *InstallVBLTask(ProcPtr proc,short ticks)
{
 OSErr err;
 VBLTask *taskPtr;
 
 taskPtr=(VBLTask *)NewPtr(sizeof(VBLTask));
 if (taskPtr){
 taskPtr->qType=vType;
 taskPtr->vblAddr=proc;
 taskPtr->vblCount=ticks;
 taskPtr->vblPhase=0;
 err=VInstall((QElemPtr)taskPtr);
 if (err!=noErr){
 DisposPtr(taskPtr);
 taskPtr=NULL;
 }
 }
 return(taskPtr);
}

/**********************************************************
Routine: void RemoveVBLTask(VBLTask *taskPtr)

Removes the VBL task specified by taskPtr that was
installed using InstallVBLTask.  It also disposes of the
memory set up in that call.
**********************************************************/

void RemoveVBLTask(VBLTask *taskPtr)
{
 VRemove((QElemPtr)taskPtr);
 DisposPtr(taskPtr);
}
Listing: Cursor Test.c

/**********************************************************
Copyright (c) 1990 Richard Lesh, All Rights Reserved 
**********************************************************/

/**********************************************************
Program: Cursor Test

Demonstrates the use of animated cursors and exercises the
Cursor Control module.
**********************************************************/

#include <MacHeaders>
#include <SANE.h>

/**********************************************************
Definitions
**********************************************************/
#ifndef NULL
#define NULL 0L
#endif

#define ABOUT_DLOG 128

#define APPLE_MENU 128
#define FILE_MENU129
#define EDIT_MENU130
#define CURSOR_MENU131
#define SPEED_MENU 132

/**********************************************************
TypeDefs and Enums
**********************************************************/

/* Animated cursor control structure 'acur' */
typedef struct {
 short n;
 short index;
 union {
 Handle cursorHdl;
 short resID;
 }frame[1];
}acurRec,*acurPtr,**acurHandle;

/* Menu commands */
enum {mAbout=1};
enum {mStart=1,mStop,mQuit=4};
enum {mUndo=1,mCut=3,mCopy,mPaste,mClear};

/**********************************************************
Private Globals
**********************************************************/
static short gCursorNum;
static short gSpeedNum;

/**********************************************************
Prototypes
**********************************************************/
void main(void);
void InitMgrs(void);
void FillMenus(void);
void EventLoop(void);
void DoMenu(long command);
void DoOpen(void);
void DoClose(void);

void InitCursorCtrl(acurHandle h);
void QuitCursorCtrl(void);
void SpinCursor(short cycles);
void StopCursor(void);
void SetCursorSpeed(short newSpeed);

void Wait(short);

/**********************************************************
Routine: main()

Entry point for the application.
**********************************************************/

void main()
{
 short i;

 MaxApplZone();
 for (i=0;i<10;i++)
 MoreMasters();
 InitMgrs();
 FillMenus();
 DoOpen();
 EventLoop();
 DoClose();
}

/**********************************************************
Routine: InitMgrs()

This routine initializes all the appropriate managers.
**********************************************************/

void InitMgrs()
{
 InitGraf(&thePort);
 InitFonts();
 InitWindows();
 InitMenus();
 TEInit();
 InitDialogs(NULL);
 InitCursor();
 FlushEvents(everyEvent,0);
}

/**********************************************************
Routine: FillMenus()

Reads in the menu bar and menu resources, then builds the
dynamic menus.
**********************************************************/

void FillMenus()
{
 short i;
 Handle menuBarHdl;
 MenuHandle menuHdl;
 
 menuBarHdl=GetNewMBar(128);     /*Load the Menu Bar */
 SetMenuBar(menuBarHdl);         /*Load the MENUs */
 menuHdl=GetMHandle(APPLE_MENU); /*Build the Apple menu*/
 AddResMenu(menuHdl,'DRVR');
 menuHdl=GetMHandle(CURSOR_MENU);/*Build the Cursor menu*/
 AddResMenu(menuHdl,'acur');
 DrawMenuBar();
}

/**********************************************************
Routine: EventLoop()

Main event loop processing.
**********************************************************/

Boolean gDone=FALSE;
void EventLoop()
{
 EventRecord theEvent;
 WindowPtr theWindow;
 short partCode;
 
 while (!gDone){
 SystemTask();
 GetNextEvent(everyEvent,&theEvent);
 switch(theEvent.what){
 case nullEvent:
 break;
 case keyDown:
 case autoKey:
 if (theEvent.modifiers & cmdKey){
 DoMenu(MenuKey((short)(theEvent.message & charCodeMask)));
 HiliteMenu(0);
 }
 break;
 case updateEvt:
 break;
 case activateEvt:
 break;
 case mouseDown:
 switch (partCode=FindWindow(theEvent.where,&theWindow)){
 case inDesk: break;
 case inMenuBar:
 DoMenu(MenuSelect(theEvent.where));break;
 case inSysWindow:
 SystemClick(&theEvent,theWindow);break;
 default:
 break;
 }
 break;
 case app4Evt:
 if (!(theEvent.message&suspendResumeMessage)) StopCursor();
 break;
 }
 }
}

/**********************************************************
Routine: DoMenu(long command)

Handles menu selections.
**********************************************************/

void DoMenu(long command)
{
 short menu_id=HiWord(command);
 short item=LoWord(command);
 Str255 item_name;
 MenuHandle mHdl;
 DialogPtr dPtr;
 
 switch(menu_id){
 case APPLE_MENU:
 if (item==1){
 dPtr=GetNewDialog(ABOUT_DLOG, NULL, -1);
 while (ModalDialog(NULL, &item),item!=1);
 DisposDialog(dPtr);
 }else if (item>2){
 GetItem(GetMHandle(menu_id),item,item_name);
 OpenDeskAcc(item_name);
 }
 break;
 case EDIT_MENU:
 SystemEdit(item-1);break;
 case FILE_MENU:
 switch(item){
 case mStart:
 SpinCursor(5);
 Wait(6*60);
 break;
 case mStop:
 StopCursor();
 InitCursor();
 break;
 case mQuit:
 gDone=TRUE;
 break;
 }
 break;
 case CURSOR_MENU:
 mHdl=GetMHandle(menu_id);
 GetItem(mHdl,item,item_name);
 StopCursor();
 InitCursorCtrl((acurHandle)GetNamedResource('acur',
 item_name));
 CheckItem(mHdl, gCursorNum, FALSE);
 CheckItem(mHdl, item, TRUE);
 gCursorNum=item;
 break;
 case SPEED_MENU:
 mHdl=GetMHandle(menu_id);
 GetItem(mHdl,item,item_name);
 SetCursorSpeed((short)str2num((char *)item_name));
 CheckItem(mHdl, gSpeedNum, FALSE);
 CheckItem(mHdl, item, TRUE);
 gSpeedNum=item;
 break; 
 }
 HiliteMenu(0);
}

/**********************************************************
Routine: DoOpen()

Performs tasks that need to be done at startup.
**********************************************************/

void DoOpen()
{
 MenuHandle mHdl;
 Str255 s;
 
 gCursorNum=1; /* Select cursor #1 and update menu */
 mHdl=GetMenu(CURSOR_MENU);
 CheckItem(mHdl, gCursorNum, TRUE);
 GetItem(mHdl,gCursorNum,s);
 InitCursorCtrl((acurHandle)GetNamedResource('acur',s));

 gSpeedNum=5;  /* Select speed #5 and update menu  */
 mHdl=GetMenu(SPEED_MENU);
 CheckItem(mHdl, gSpeedNum, TRUE);
 GetItem(mHdl,gSpeedNum,s);
 SetCursorSpeed((short)str2num((char *)s));
}

/**********************************************************
Routine: DoClose()

Cleans up before shutting down the application.
**********************************************************/

void DoClose()
{
 QuitCursorCtrl();
}

/**********************************************************
Routine:Wait(short t)

Pauses execution for t sixtieths of a second.  Does not
allow background tasks to execute.
**********************************************************/

void Wait(short t)
{
 long s;
 
 s=TickCount()+t;
 while (TickCount()<s);
}
Listing: Cursor Test Π.r

resource 'MENU' (128) {
 128,
 textMenuProc,
 0x7FFFFFFD,
 enabled,
 apple,
 { /* array: 2 elements */
 /* [1] */
 "About Cursor Test", noIcon, noKey, noMark, plain,
 /* [2] */
 "-", noIcon, noKey, noMark, plain
 }
};

resource 'MENU' (129) {
 129,
 textMenuProc,
 0x7FFFFFFB,
 enabled,
 "File",
 { /* array: 4 elements */
 /* [1] */
 "Start Cursor", noIcon, "G", noMark, plain,
 /* [2] */
 "Stop Cursor", noIcon, ".", noMark, plain,
 /* [3] */
 "-", noIcon, noKey, noMark, plain,
 /* [4] */
 "Quit", noIcon, "Q", noMark, plain
 }
};

resource 'MENU' (130) {
 130,
 textMenuProc,
 0x7FFFFFFD,
 enabled,
 "Edit",
 { /* array: 6 elements */
 /* [1] */
 "Undo", noIcon, "Z", noMark, plain,
 /* [2] */
 "-", noIcon, noKey, noMark, plain,
 /* [3] */
 "Cut", noIcon, "X", noMark, plain,
 /* [4] */
 "Copy", noIcon, "C", noMark, plain,
 /* [5] */
 "Paste", noIcon, "V", noMark, plain,
 /* [6] */
 "Clear", noIcon, "B", noMark, plain
 }
};

resource 'MENU' (131) {
 131,
 textMenuProc,
 allEnabled,
 enabled,
 "Cursors",
 { /* array: 0 elements */
 }
};

resource 'MENU' (132) {
 132,
 textMenuProc,
 allEnabled,
 enabled,
 "Speed",
 { /* array: 10 elements */
 /* [1] */
 "1", noIcon, noKey, noMark, plain,
 /* [2] */
 "2", noIcon, noKey, noMark, plain,
 /* [3] */
 "4", noIcon, noKey, noMark, plain,
 /* [4] */
 "6", noIcon, noKey, noMark, plain,
 /* [5] */
 "8", noIcon, noKey, noMark, plain,
 /* [6] */
 "10", noIcon, noKey, noMark, plain,
 /* [7] */
 "15", noIcon, noKey, noMark, plain,
 /* [8] */
 "30", noIcon, noKey, noMark, plain,
 /* [9] */
 "45", noIcon, noKey, noMark, plain,
 /* [10] */
 "60", noIcon, noKey, noMark, plain
 }
};

resource 'MBAR' (128) {
 { /* array MenuArray: 5 elements */
 /* [1] */
 128,
 /* [2] */
 129,
 /* [3] */
 130,
 /* [4] */
 131,
 /* [5] */
 132
 }
};

data 'acur' (128, "Beach Ball") {
 $"00 04 00 00 00 80 00 00 00 81 00 00 00 82 00 00"
 $"00 83 00 00"
};

resource 'CURS' (128) {
 $"07 C0 1F F0 3F F8 5F F4 4F E4 87 C2 83 82 81 02"
 $"83 82 87 C2 4F E4 5F F4 3F F8 1F F0 07 C0",
 $"07 C0 1F F0 3F F8 7F FC 7F FC FF FE FF FE FF FE"
 $"FF FE FF FE 7F FC 7F FC 3F F8 1F F0 07 C0",
 {7, 7}
};

resource 'CURS' (129) {
 $"07 C0 19 F0 21 F8 41 FC 41 FC 81 FE 81 FE FF FE"
 $"FF 02 FF 02 7F 04 7F 04 3F 08 1F 30 07 C0",
 $"07 C0 1F F0 3F F8 7F FC 7F FC FF FE FF FE FF FE"
 $"FF FE FF FE 7F FC 7F FC 3F F8 1F F0 07 C0",
 {7, 7}
};

resource 'CURS' (130) {
 $"07 C0 18 30 20 08 70 1C 78 3C FC 7E FE FE FF FE"
 $"FE FE FC 7E 78 3C 70 1C 20 08 18 30 07 C0",
 $"07 C0 1F F0 3F F8 7F FC 7F FC FF FE FF FE FF FE"
 $"FF FE FF FE 7F FC 7F FC 3F F8 1F F0 07 C0",
 {7, 7}
};

resource 'CURS' (131) {
 $"07 C0 1F 30 3F 08 7F 04 7F 04 FF 02 FF 02 FF FE"
 $"81 FE 81 FE 41 FC 41 FC 21 F8 19 F0 07 C0",
 $"07 C0 1F F0 3F F8 7F FC 7F FC FF FE FF FE FF FE"
 $"FF FE FF FE 7F FC 7F FC 3F F8 1F F0 07 C0",
 {7, 7}
};

resource 'crsr' (128, purgeable) {
 colorCursor,
 $"07 C0 1F F0 3F F8 5F F4 4F E4 87 C2 83 82 81 02"
 $"83 82 87 C2 4F E4 5F F4 3F F8 1F F0 07 C0",
 $"07 C0 1F F0 3F F8 7F FC 7F FC FF FE FF FE FF FE"
 $"FF FE FF FE 7F FC 7F FC 3F F8 1F F0 07 C0",
 {7, 7},
 4,
 {0, 0, 16, 16},
 0,
 unpacked,
 0,
 0x480000,
 0x480000,
 chunky,
 2,
 1,
 2,
 0,
 $"00 3F F0 00 03 D5 5F 00 0D 55 55 C0 31 55 55 30"
 $"30 55 54 30 C0 15 50 0C C0 05 40 0C C0 01 00 0C"
 $"C0 05 40 0C C0 15 50 0C 30 55 54 30 31 55 55 30"
 $"0D 55 55 C0 03 D5 5F 00 00 3F F0 00 00 00 00 00",
 0x0,
 0,
 { /* array ColorSpec: 3 elements */
 /* [1] */
 0, 65535, 65535, 65535,
 /* [2] */
 1, 56797, 0, 0,
 /* [3] */
 3, 0, 0, 0
 }
};

resource 'crsr' (129, purgeable) {
 colorCursor,
 $"07 C0 19 F0 21 F8 41 FC 41 FC 81 FE 81 FE FF FE"
 $"FF 02 FF 02 7F 04 7F 04 3F 08 1F 30 07 C0",
 $"07 C0 1F F0 3F F8 7F FC 7F FC FF FE FF FE FF FE"
 $"FF FE FF FE 7F FC 7F FC 3F F8 1F F0 07 C0",
 {7, 7},
 4,
 {0, 0, 16, 16},
 0,
 unpacked,
 0,
 0x480000,
 0x480000,
 chunky,
 2,
 1,
 2,
 0,
 $"00 3F F0 00 03 C1 5F 00 0C 01 55 C0 30 01 55 70"
 $"30 01 55 70 C0 01 55 5C C0 01 55 5C D5 55 55 5C"
 $"D5 55 00 0C D5 55 00 0C 35 55 00 30 35 55 00 30"
 $"0D 55 00 C0 03 D5 0F 00 00 3F F0 00 00 00 00 00",
 0x0,
 0,
 { /* array ColorSpec: 3 elements */
 /* [1] */
 0, 65535, 65535, 65535,
 /* [2] */
 1, 56797, 0, 0,
 /* [3] */
 3, 0, 0, 0
 }
};

resource 'crsr' (130, purgeable) {
 colorCursor,
 $"07 C0 18 30 20 08 70 1C 78 3C FC 7E FE FE FF FE"
 $"FE FE FC 7E 78 3C 70 1C 20 08 18 30 07 C0",
 $"07 C0 1F F0 3F F8 7F FC 7F FC FF FE FF FE FF FE"
 $"FF FE FF FE 7F FC 7F FC 3F F8 1F F0 07 C0",
 {7, 7},
 4,
 {0, 0, 16, 16},
 0,
 unpacked,
 0,
 0x480000,
 0x480000,
 chunky,
 2,
 1,
 2,
 0,
 $"00 3F F0 00 03 C0 0F 00 0C 00 00 C0 35 00 01 70"
 $"35 40 05 70 D5 50 15 5C D5 54 55 5C D5 55 55 5C"
 $"D5 54 55 5C D5 50 15 5C 35 40 05 70 35 00 01 70"
 $"0C 00 00 C0 03 C0 0F 00 00 3F F0 00 00 00 00 00",
 0x0,
 0,
 { /* array ColorSpec: 3 elements */
 /* [1] */
 0, 65535, 65535, 65535,
 /* [2] */
 1, 56797, 0, 0,
 /* [3] */
 3, 0, 0, 0
 }
};

resource 'crsr' (131, purgeable) {
 colorCursor,
 $"07 C0 1F 30 3F 08 7F 04 7F 04 FF 02 FF 02 FF FE"
 $"81 FE 81 FE 41 FC 41 FC 21 F8 19 F0 07 C0",
 $"07 C0 1F F0 3F F8 7F FC 7F FC FF FE FF FE FF FE"
 $"FF FE FF FE 7F FC 7F FC 3F F8 1F F0 07 C0",
 {7, 7},
 4,
 {0, 0, 16, 16},
 0,
 unpacked,
 0,
 0x480000,
 0x480000,
 chunky,
 2,
 1,
 2,
 0,
 $"00 3F F0 00 03 D5 0F 00 0D 55 00 C0 35 55 00 30"
 $"35 55 00 30 D5 55 00 0C D5 55 00 0C D5 55 55 5C"
 $"C0 01 55 5C C0 01 55 5C 30 01 55 70 30 01 55 70"
 $"0C 01 55 C0 03 C1 5F 00 00 3F F0 00 00 00 00 00",
 0x0,
 0,
 { /* array ColorSpec: 3 elements */
 /* [1] */
 0, 65535, 65535, 65535,
 /* [2] */
 1, 56797, 0, 0,
 /* [3] */
 3, 0, 0, 0
 }
};

resource 'DLOG' (128) {
 {40, 40, 140, 331},
 dBoxProc,
 visible,
 goAway,
 0x0,
 128,
 ""
};

resource 'DITL' (128) {
 { /* array DITLarray: 4 elements */
 /* [1] */
 {70, 220, 90, 278},
 Button {
 enabled,
 "OK"
 },
 /* [2] */
 {6, 106, 22, 184},
 StaticText {
 disabled,
 "Cursor Test"
 },
 /* [3] */
 {28, 93, 44, 198},
 StaticText {
 disabled,
 "by Richard Lesh"
 },
 /* [4] */
 {50, 52, 66, 238},
 StaticText {
 disabled,
 "©1990, All Rights Reserved"
 }
 }
};

 
AAPL
$95.60
Apple Inc.
-2.55
MSFT
$43.16
Microsoft Corpora
-0.42
GOOG
$571.60
Google Inc.
-15.82

MacTech Search:
Community Search:

Software Updates via MacUpdate

OneNote 15.2 - Free digital notebook fro...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that’s too important to forget. Whether you’re at... Read more
iStat Menus 4.22 - Monitor your system r...
iStat Menus lets you monitor your system right from the menubar. Included are 8 menu extras that let you monitor every aspect of your system. Some features: CPU -- Monitor cpu usage. 7 display... Read more
Ember 1.8 - Versatile digital scrapbook....
Ember (formerly LittleSnapper) is your digital scrapbook of things that inspire you: websites, photos, apps or other things. Just drag in images that you want to keep, organize them into relevant... Read more
OmniPlan 2.3.6 - Robust project manageme...
With OmniPlan, you can create logical, manageable project plans with Gantt charts, schedules, summaries, milestones, and critical paths. Break down the tasks needed to make your project a success,... Read more
Command-C 1.1.1 - Clipboard sharing tool...
Command-C is a revolutionary app which makes easy to share your clipboard between iOS and OS X using your local WiFi network, even if the app is not currently opened. Copy anything (text, pictures,... Read more
Knock 1.1.7 - Unlock your Mac by knockin...
Knock is a faster, safer way to sign in. You keep your iPhone with you all the time. Now you can use it as a password. You never have to open the app -- just knock on your phone twice, even when it's... Read more
Mellel 3.3.6 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
LibreOffice 4.3.0.4 - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
Freeway Pro 7.0 - Drag-and-drop Web desi...
Freeway Pro lets you build websites with speed and precision... without writing a line of code! With it's user-oriented drag-and-drop interface, Freeway Pro helps you piece together the website of... Read more
Drive Genius 3.2.4 - Powerful system uti...
Drive Genius is an OS X utility designed to provide unsurpassed storage management. Featuring an easy-to-use interface, Drive Genius is packed with powerful tools such as a drive optimizer, a... Read more

Latest Forum Discussions

See All

Dawn of the Immortals Review
Dawn of the Immortals Review By Jennifer Allen on July 31st, 2014 Our Rating: :: RESPECTABLE EXPLORATIONUniversal App - Designed for iPhone and iPad Dawn of the Immortals might not re-invent the wheel, but it does tweak it a little... | Read more »
80 Days Review
80 Days Review By Jennifer Allen on July 31st, 2014 Our Rating: :: EPIC ADVENTUREUniversal App - Designed for iPhone and iPad A fantastic and fascinating re-envisioning of the classic novel by Jules Verne, 80 Days is a delightful... | Read more »
Battleheart Legacy Guide
The world of Battleheart Legacy is fun and deep; full of wizards, warriors, and witches. Here are some tips and tactics to help you get the most enjoyment out of this great game. | Read more »
Puzzle Roo Review
Puzzle Roo Review By Jennifer Allen on July 31st, 2014 Our Rating: :: PUZZLE-BASED TWISTUniversal App - Designed for iPhone and iPad A different take on the usual block dropping puzzle game, Puzzle Roo is quite pleasant.   | Read more »
Super Crossfire Re-Release Super Crossfi...
Super Crossfire Re-Release Super Crossfighter Coming Soon, Other Radiangames Titles Go 50% Off Posted by Ellis Spice on July 31st, 2014 [ | Read more »
Hexiled Review
Hexiled Review By Rob Thomas on July 31st, 2014 Our Rating: :: HEX SELLSUniversal App - Designed for iPhone and iPad In space, no one can hear you… spell? Hexiled is a neat concept for a word scramble puzzle, but it doesn’t go too... | Read more »
Summoners War: Sky Arena Passes 10 Milli...
Summoners War: Sky Arena Passes 10 Million Installs! Posted by Jessica Fisher on July 31st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Deep Loot Review
Deep Loot Review By Jennifer Allen on July 31st, 2014 Our Rating: :: DIVE DEEPUniversal App - Designed for iPhone and iPad Dive deep in this fun explore-em-up that’s a little grind heavy but ultimately quite entertaining.   | Read more »
Despicable Me: Minion Rush is One Year O...
Despicable Me: Minion Rush is One Year Old, Gets its Biggest Update Yet Posted by Jennifer Allen on July 31st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Fish & Shark Review
Fish & Shark Review By Jordan Minor on July 31st, 2014 Our Rating: :: FLAPPY FISHUniversal App - Designed for iPhone and iPad Fish & Shark’s beauty is only scale deep.   | Read more »

Price Scanner via MacPrices.net

All Over For Tablets Or Just A Maturing, Evol...
CNN’s David Goldman weighs in on tablet sector doom and gloom, asking rhetorically: “Is this the beginning of the end for the tablet?” Answering that, he contends that hysteria and panic are... Read more
Letterspace 1.0.1 – New Free iOS Text Editor...
Bangkok, Thailand based independent developer Sittipon Simasanti has released Letterspace, a new text editor for iPhone, iPad, and iPod touch devices. Letterspace is a note taking app with an... Read more
Save up to $130 on an iPad mini with Apple re...
The Apple Store has Certified Refurbished 2nd generation iPad minis with Retina Displays available for up to $130 off the cost of new models, starting at $339. Apple’s one-year warranty is included... Read more
iPad Cannibalization Threat “Overblown”
Seeking Alpha’s Kevin Greenhalgh observes that while many commentators think Apple’s forthcoming 5.5-inch panel iPhone 6 will cannibalize iPad sales, in his estimation, these concerns are being... Read more
Primate Labs Releases July 2014 MacBook Pro P...
Primate Labs’ John Poole has posted Geekbench 3 results for most of the new MacBook Pro models that Apple released on Tuesday. Poole observes that overall performance improvements for the new MacBook... Read more
Apple Re-Releases Bugfixed MacBook Air EFI Fi...
Apple has posted a bugfixed version EFI Firmware Update 2.9 a for MacBook Air (Mid 2011) models. The update addresses an issue where systems may take longer to wake from sleep than expected, and... Read more
Save $50 on the 2.5GHz Mac mini, plus free sh...
B&H Photo has the 2.5GHz Mac mini on sale for $549.99 including free shipping. That’s $50 off MSRP, and B&H will also include a free copy of Parallels Desktop software. NY sales tax only. Read more
Save up to $140 on an iPad Air with Apple ref...
Apple is offering Certified Refurbished iPad Airs for up to $140 off MSRP. Apple’s one-year warranty is included with each model, and shipping is free. Stock tends to come and go with some of these... Read more
$250 price drop on leftover 15-inch Retina Ma...
B&H Photo has dropped prices on 2013 15″ Retina MacBook Pros by $250 off original MSRP. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.3GHz Retina MacBook Pro: $2249, $250 off... Read more
More iPad Upgrade Musings – The ‘Book Mystiqu...
Much discussed recently, what with Apple reporting iPad sales shrinkage over two consecutive quarters, is that it had apparently been widely assumed that tablet users would follow a two-year hardware... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Sr. Product Leader, *Apple* Store Apps - Ap...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.