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
$524.94
Apple Inc.
+5.93
MSFT
$40.01
Microsoft Corpora
-0.39
GOOG
$536.10
Google Inc.
-20.44

MacTech Search:
Community Search:

Software Updates via MacUpdate

Mac DVDRipper Pro 4.1.7 - Copy, backup,...
Mac DVDRipper Pro is the DVD backup solution that lets you protect your DVDs from scratches, save your batteries by reading your movies from your hard disk, manage your collection with just a few... Read more
PDFpenPro 6.2 - Advanced PDF toolkit for...
PDFpenPro allows users to edit PDF's easily. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Create... Read more
PDFpen 6.2 - Edit and annotate PDFs with...
PDFpen allows users to easily edit PDF's. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Features... Read more
Monolingual 1.5.9 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Starcraft II: Wings of Liberty 1.1.1.180...
Download the patch by launching the Starcraft II game and downloading it through the Battle.net connection within the app. Starcraft II: Wings of Liberty is a strategy game played in real-time. You... Read more
Sibelius 7.5.0 - Music notation solution...
Sibelius is the world's best-selling music notation software for Mac. It is as intuitive to use as a pen, yet so powerful that it does most things in less than the blink of an eye. The demo includes... Read more
Typinator 5.9 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
MYStuff Pro 2.0.16 - Create inventories...
MYStuff Pro is the most flexible way to create detail-rich inventories for your home or small business. Add items to MYStuff by dragging and dropping existing information, uploading new images, or... Read more
TurboTax 2013.r17.002 - Manage your 2013...
TurboTax guides you through your tax return step by step, does all the calculations, and checks your return for errors and overlooked deductions. It lets you file your return electronically to get... Read more

Latest Forum Discussions

See All

Zynga Launches Brand New Farmville Exper...
Zynga Launches Brand New Farmville Experience with Farmville 2: Country Escape Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
David. Review
David. Review By Cata Modorcea on April 18th, 2014 Our Rating: :: MINIMALISTIC IN A DIFFERENT WAYUniversal App - Designed for iPhone and iPad David is a minimalistic game wrapped inside of a soothing atmosphere in which the hero... | Read more »
Eyefi Unveils New Eyefi Cloud Service Th...
Eyefi Unveils New Eyefi Cloud Service That Allows Users to Share Media Across Personal Devices Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
Tales from the Dragon Mountain: The Lair...
Tales from the Dragon Mountain: The Lair Review By Jennifer Allen on April 18th, 2014 Our Rating: :: STEADY ADVENTURINGiPad Only App - Designed for the iPad Treading a safe path, Tales from the Dragon Mountain: The Lair is a... | Read more »
Yahoo Updates Flickr App with Advanced E...
Yahoo Updates Flickr App with Advanced Editing Features and More Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
My Incredible Body - A Kid's App to...
My Incredible Body - A Kid's App to Learn about the Human Body 1.1.00 Device: iOS Universal Category: Education Price: $2.99, Version: 1.1.00 (iTunes) Description: Wouldn’t it be cool to look inside yourself and see what was going on... | Read more »
Trials Frontier Review
Trials Frontier Review By Carter Dotson on April 18th, 2014 Our Rating: :: A ROUGH LANDINGUniversal App - Designed for iPhone and iPad Trials Frontier finally brings the famed stunt racing franchise to mobile, but how much does its... | Read more »
Evernote Business Notebook by Moleskin I...
Evernote Business Notebook by Moleskin Introduced – Support Available in Evernote for iOS Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
Sparkle Unleashed Review
Sparkle Unleashed Review By Jennifer Allen on April 18th, 2014 Our Rating: :: CLASSY MARBLE FLINGINGUniversal App - Designed for iPhone and iPad It’s a concept we’ve seen before, but Sparkle Unleashed is a solidly enjoyable orb... | Read more »
Tilt to Live 2: Redonkulus Receives its...
Tilt to Live 2: Redonkulus Receives its First Update – Gets New Brimstone Pinball DLC Posted by Rob Rich on April 18th, 2014 [ permalink ] | Read more »

Price Scanner via MacPrices.net

iMacs on sale for up to $160 off MSRP this we...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
iPad Airs on sale this weekend for up to $100...
Best Buy has WiFi iPad Airs on sale for $50 off MSRP and WiFi + Cellular iPad Airs on sale for $100 off MSRP on their online store for a limited time, with prices now starting at $449. Choose free... Read more
Apple restocks refurbished Mac minis starting...
The Apple Store has restocked Apple Certified Refurbished Mac minis for up to $150 off the cost of new models. Apple’s one-year warranty is included with each mini, and shipping is free: - 2.5GHz Mac... Read more
Hyundai Brings Apple CarPlay To The 2015 Sona...
Hyundai Motor America has announced it will bring Apple CarPlay functionality to the 2015 Sonata. CarPlay is pitched as a smarter, safer and easier way to use iPhone in the car and gives iPhone users... Read more
Updated iPads Coming Sooner Than We Had Thoug...
MacRumors, cites KGI securities analyst Ming Chi Kuo, well-respected as an Apple product prognisticator, saying that Apple will introduce an upgraded iPad Air and iPad mini in 2014/Q3, meaning the... Read more
Toshiba Unveils New High And Low End Laptop M...
Toshiba has announced new laptop models covering both the high-end and low-end of the notebook computer spectrum. Toshiba 4K Ultra HD Laptop Toshiba’s new Satellite P55t features one of the world’s... Read more
Save up to $270 with Apple refurbished 13-inc...
The Apple Store has Apple Certified Refurbished October 2013 13″ Retina MacBook Pros available starting at $1099, with models up to $270 off MSRP. Apple’s one-year warranty is standard, and shipping... Read more
Apple now offering refurbished iPad mini with...
The Apple Store has Certified Refurbished 2nd generation iPad minis with Retina Displays now available starting at $339. Apple’s one-year warranty is included with each model, and shipping is free.... Read more
Microsoft Blinks – Drops Microsoft Office 365...
Microsoft has dropped the annual subscription fee for Microsoft Office 365 Personal – which is needed in order to create and edit documents in Microsoft Office for iPad. However, Apple’s iOS and OS X... Read more
New AVG Vault Apps for iOS and Android Help K...
AVG Technologies N.V. an online security company for 177 million active users, has announced the launch of its latest mobile application, AVG Vault. The free app introduces an innovative user... Read more

Jobs Board

*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
*Apple* Retail - Manager - Holyoke - Apple I...
Job Summary Keeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you’re a master of them all. In the store’s fast-paced, Read more
*Apple* Retail - Manager - Apple (United Sta...
Job SummaryKeeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you're a master of them all. In the store's fast-paced, dynamic 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
*Apple* Retail - Market Leader - Cincinnati...
…challenges of developing individuals, building teams, and affecting growth across Apple Stores. You demonstrate successful leadership ability - focusing on excellence Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.