TweetFollow Us on Twitter

Color Animation II
Volume Number:10
Issue Number:4
Column Tag:Getting Started

Related Info: Color Quickdraw Quickdraw

Finally - Color Animation!!!

Flying color bits around flicker-free

By Dave Mark, MacTech Magazine Regular Contributing Author

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

A while ago, we got into bitmap animation using BitMapper. BitMapper was based on QuickDraw’s BitMap data structure. Since BitMaps are always 1 pixel deep, BitMapper was limited to black and white animation. This month, we’ll colorize BitMapper, taking advantage of Color QuickDraw’s PixMap data structure.

PixMaps and GWorlds

The PixMap is much more complex than the BitMap. With BitMap animation, all you had to do was fill out the relatively simple BitMap structure, create a GrafPort, then connect the two via a call to SetPortBits(). Once that’s done, you are ready to copy the BitMap from port to port via a call to CopyBits(). If you need a little refresher, check back to the BitMapper column (September, ‘93).

On the other hand, PixMaps are complicated beasties. Pop open your copy of Inside Macintosh, Volume V (or THINK Reference, if you prefer) and look up the PixMap data structure. To create a PixMap by hand, you’d have to initialize all of those fields. Notice the pmTable field, listed as a handle to a color table (also known as a color lookup table, or CLUT). In addition to the PixMap fields, you’ve also got to create and initialize a ColorTable structure. Look it up.

This stuff is non-trivial. Fortunately, the Toolbox offers a high-level set of functions that make the creation of off-screen PixMaps relatively simple. These off-screen PixMaps are known as GWorlds. Off-screen simply means that you are creating something that is not drawn in a window on the screen. GWorlds are created in memory and typically drawn to a window via a call to CopyBits(), just as we did with BitMaps in BitMapper.

PixMapper

This month’s program is called PixMapper. PixMapper creates a window the size of the main screen and fills it in with a randomly generated sequence of colored squares. Next, PixMapper loads a PICT resource and uses a series of GWorlds to smoothly animate the PICT over the colored background.

As usual, we’ll create the program this month, then go over the program details in next month’s column.

Creating the PixMapper Resources

Start by creating a folder named PixMapper inside your Development folder. Fire up ResEdit and create a file named PixMapper.Π.rsrc inside your PixMapper folder.

Now create an ALRT resource (along with a corresponding DITL resource) for our error alert. The ALRT resource should have a Top: of 40, a Left: of 30, a Height: of 116, and a Width: of 292. Be sure to set the DITL ID: to 128.

Next, create a DITL with an id of 128. Use the specifications in Figure 2 to create the OK button and the specifications in Figure 3 to create the error alert’s static text item.

Figure 2. Specifications for the error alert’s OK button.

Figure 3. Specifications for the error alert’s static text item.

Next, create an MBAR resource with an id of 128. The MBAR should list four MENU ids: 128, 129, 130 and 131. Create four MENU resources according to the specifications in Figure 4. Be sure to include a separator line as the second item in the File menu. Also, be sure to mark the appropriate items in the Colors menu with a check mark (). Use the popup menu in the Mark: field to do this.

Figure 4. The four MENU resources.

Finally, go into the scrapbook (or your favorite graphics application) and use Copy and Paste to create a PICT resource in the resource file. Be sure that the PICT has a resource id of 128. When choosing your PICT resource, here’s a couple of things to keep in mind. First of all, smaller is probably better. A smaller picture takes up less memory and contains less pixels to copy around in memory. Something around the size of a color icon is probably a good size to start with.

Next, you might want to create a picture that is non-rectangular or that has a hole in it, then use the lasso tool to select only the pixels in the picture (and not the background). Though a rectangular picture will work just fine, a non-rectangular picture (like an X or an O shape) produces much more impressive results.

Creating the PixMapper Project

Save your changes and quit ResEdit. Launch THINK C and create a new project named PixMapper.Π in the PixMapper folder. Add MacTraps to the project. Next, create a new source code window, save it as PixMapper.c and add it to the project.

Type in the following source code:


/* 1 */
#include <QDOffscreen.h>
#include <Picker.h>
#include <GestaltEqu.h>

#define kMBARResID 128
#define kSleep   0L
#define kMoveToFront (WindowPtr)-1L
#define kEmptyString "\p"
#define kEmptyTitlekEmptyString
#define kVisible true
#define kNoGoAwayfalse
#define kNilRefCon (long)nil
#define kErrorAlertID128
#define kNilFilterProc    nil

#define kSquareSize16

#define kForegroundPICT   128
#define kIgnored nil
#define kUseMaxDepth 0
#define kNoFlags (GWorldFlags)0

#define mApple   128
#define iAbout   1

#define mFile    129
#define iRedraw  1
#define iQuit    3

#define mColors  131
#define iUseRGB  1
#define iUseHSV  2
#define iRed4
#define iGreen   5
#define iBlue    6
#define iHue4
#define iSaturation5
#define iBrightness6

/*  Globals  */

Boolean gDone;
Boolean gIsRGB = true, gRandomReds = true, 
 gRandomGreens = true, gRandomBlues = true;
Boolean gRandomHue = true, gRandomSaturation = true, gRandomBrightness 
= true;
WindowPtr gMainWindow;
short   gXBump = 1, gYBump = 1; // <--Try changing these numbers
GWorldPtr gPictWorld, gSaveWorld, gSaveMixWorld;
PixMapHandlegPixMapSave, gPixMapSaveMix, gPixMapPict;
Rect    gSavedFloaterRect, gPictWorldRect, gWorldRect;
short   gGlobalHue;

/*  Functions  */

void    ToolboxInit( void );
void    MenuBarInit( void );
Boolean HasGWorlds( void );
void    WindowInit( void );
void    PaintWindow( void );
void    RandomForeColor( void );
void    GWorldInit( void );
GWorldPtr MakeGWorld( Rect *boundsPtr );
void    EventLoop( void );
void    DoEvent( EventRecord *eventPtr );
void    HandleMouseDown( EventRecord *eventPtr );
void    HandleMenuChoice( long menuChoice );
void    HandleAppleChoice( short item );
void    HandleFileChoice( short item );
void    HandleColorsChoice( short item );
void    MainLoop( void );
void    DrawFirstFloater( void );
void    MoveFloater( void );
void    CalcNewFloaterPosition( void );
void    DoError( Str255 errorString );

/****************** main ***************************/
void  main( void )
{
 ToolboxInit();
 MenuBarInit();
 if ( ! HasGWorlds() )
 DoError( "\pDeep GWorlds not supported by this machine!" );
 
 WindowInit();
 GWorldInit();

 DrawFirstFloater();
 
 EventLoop();
}

/****************** ToolboxInit *********************/

void  ToolboxInit( void )
{
 InitGraf( &thePort );
 InitFonts();
 InitWindows();
 InitMenus();
 TEInit();
 InitDialogs( NULL );
 InitCursor();
}

/****************** MenuBarInit ***********************/

void  MenuBarInit( void )
{
 Handle menuBar;
 MenuHandle menu;
 
 menuBar = GetNewMBar( kMBARResID );
 SetMenuBar( menuBar );

 menu = GetMHandle( mApple );
 AddResMenu( menu, 'DRVR' );
 
 DrawMenuBar();
}

/****************** HasGWorlds *****************/

Boolean HasGWorlds( void )
{
 long   feature;
 OSErr  err;
 
 err = Gestalt( gestaltQuickdrawFeatures, &feature );
 
 if ( err != noErr )
 DoError( "\pError calling Gestalt!" );
 
 if ( feature & 0x0004 )
 return true;
 else
 return false;
}


/****************** WindowInit ***********************/

void  WindowInit( void )
{
 Rect   r;
 
 r = screenBits.bounds;
 r.top += GetMBarHeight();
 
 gMainWindow = NewCWindow( nil, &r, kEmptyTitle,
 kVisible, plainDBox, kMoveToFront,
 kNoGoAway, kNilRefCon );

 SetPort( gMainWindow );
 
 PaintWindow();
}


/****************** PaintWindow ***********************/

void  PaintWindow( void )
{
 Rect   r;
 short  row, col, numRows, numCols;
 
 SetPort( gMainWindow );
 
 r = gMainWindow->portRect;
 
 numCols = (r.right - r.left) / kSquareSize;
 if ( numCols != numCols/kSquareSize*kSquareSize )
 numCols++;
 
 numRows = (r.bottom - r.top) / kSquareSize;
 if ( numRows != numRows/kSquareSize*kSquareSize )
 numRows++;
 
 GetDateTime( (unsigned long *)(&randSeed) );
 
 gGlobalHue = Random();
 
 for ( row=0; row<numRows; row++ )
 for ( col=0; col<numCols; col++ )
 {
 r.top = row * kSquareSize;
 r.bottom = r.top + kSquareSize;
 r.left = col * kSquareSize;
 r.right = r.left + kSquareSize;
 RandomForeColor();
 PaintRect( &r );
 }

 ForeColor( blackColor );
 BackColor( whiteColor );
}


/****************** RandomForeColor ***********************/

void  RandomForeColor( void )
{
 RGBColor color;
 HSVColor hsvColor;
 
 if ( gIsRGB )
 {
 if ( gRandomReds )
 color.red = Random();
 else
 color.red = 0;
 
 if ( gRandomGreens )
 color.green = Random();
 else
 color.green = 0;
 
 if ( gRandomBlues )
 color.blue = Random();
 else
 color.blue = 0;
 
 RGBForeColor( &color );
 }
 else
 {
 if ( gRandomHue )
 hsvColor.hue = Random();
 else
 hsvColor.hue = gGlobalHue;
 
 if ( gRandomSaturation )
 hsvColor.saturation = Random();
 else
 hsvColor.saturation = 65535;
 
 if ( gRandomBrightness )
 hsvColor.value = Random();
 else
 hsvColor.value = 65535;
 
 HSV2RGB( &hsvColor, &color );
 RGBForeColor( &color );
 }
}
/****************** GWorldInit ***********************/

void  GWorldInit( void )
{
 PicHandlepic;
 
 pic = GetPicture( kForegroundPICT );

 if ( pic == nil )
 DoError( "\pError loading PICT..." );

// Call HNoPurge() if your PICT is purgeable
 
 gPictWorldRect = (**pic).picFrame;
 OffsetRect( &gPictWorldRect, -gPictWorldRect.left, 
 -gPictWorldRect.top );
 
 gWorldRect = gPictWorldRect;
 gWorldRect.bottom += 2;
 gWorldRect.right += 2;
 
 gPictWorld = MakeGWorld( &gPictWorldRect );
 gSaveWorld = MakeGWorld( &gWorldRect );
 gSaveMixWorld = MakeGWorld( &gWorldRect );
 
 gPixMapPict = GetGWorldPixMap( gPictWorld );
 gPixMapSave = GetGWorldPixMap( gSaveWorld );
 gPixMapSaveMix = GetGWorldPixMap( gSaveMixWorld );

// Lock pixels before you draw or read from PixMap.  Unlock when you 
are done.
//We're leaving the whole thing locked to avoid the hassle. In a real 
app, do it right.

 if ( ! LockPixels( gPixMapPict ) )
 DoError( "\pLockPixels failed..." );

 if ( ! LockPixels( gPixMapSave ) )
 DoError( "\pLockPixels failed..." );

 if ( ! LockPixels( gPixMapSaveMix ) )
 DoError( "\pLockPixels failed..." );
 
 SetGWorld( gPictWorld, kIgnored );
 
 DrawPicture( pic, &gPictWorldRect );
}

/*********************************** MakeGWorld */

GWorldPtr MakeGWorld( Rect *boundsPtr )
{
 QDErr  err;
 GWorldPtrnewGWorld;
 
 err = NewGWorld( &newGWorld, kUseMaxDepth,
 boundsPtr, kIgnored, kIgnored, noNewDevice );

// In the real world, call DisposeGWorld() when you are done with the 
GWorld...
 
 if ( err != noErr )
 DoError( "\pMy call to NewGWorld died!  Bye..." );
 
 return( newGWorld );
}

/************************************* EventLoop */

void  EventLoop( void )
{
 EventRecordevent;
 
 gDone = false;
 while ( gDone == false )
 {
 if ( WaitNextEvent( everyEvent, &event, kSleep, NULL ) )
 DoEvent( &event );
 MoveFloater();
 }
}

/************************************* DoEvent */
void  DoEvent( EventRecord *eventPtr )
{
 char theChar;
 
 switch ( eventPtr->what )
 {
 case mouseDown: 
 HandleMouseDown( eventPtr );
 break;
 case keyDown:
 case autoKey:
 theChar = eventPtr->message & charCodeMask;
 if ( (eventPtr->modifiers & cmdKey) != 0 ) 
 HandleMenuChoice( MenuKey( theChar ) );
 break;
 case updateEvt:
 BeginUpdate( (WindowPtr)(eventPtr->message) );
 EndUpdate( (WindowPtr)(eventPtr->message) );
 break;
 }
}

/************************************* HandleMouseDown */

void  HandleMouseDown( EventRecord *eventPtr )
{
 WindowPtrwindow;
 short  thePart;
 long   menuChoice;
 
 thePart = FindWindow( eventPtr->where, &window );
 
 switch ( thePart )
 {
 case inMenuBar:
 menuChoice = MenuSelect( eventPtr->where );
 HandleMenuChoice( menuChoice );
 break;
 case inSysWindow : 
 SystemClick( eventPtr, window );
 break;
 }
}

/************************************* HandleMenuChoice */

void  HandleMenuChoice( long menuChoice )
{
 short  menu;
 short  item;
 
 if ( menuChoice != 0 )
 {
 menu = HiWord( menuChoice );
 item = LoWord( menuChoice );
 
 switch ( menu )
 {
 case mApple:
 HandleAppleChoice( item );
 break;
 case mFile:
 HandleFileChoice( item );
 break;
 case mColors:
 HandleColorsChoice( item );
 break;
 }
 HiliteMenu( 0 );
 }
}

/************************************* HandleAppleChoice */

void  HandleAppleChoice( short item )
{
 MenuHandle appleMenu;
 Str255 accName;
 short  accNumber;
 
 switch ( item )
 {
 case iAbout:
 SysBeep( 20 );
 break;
 default:
 appleMenu = GetMHandle( mApple );
 GetItem( appleMenu, item, accName );
 accNumber = OpenDeskAcc( accName );
 break;
 }
}

/************************************* HandleFileChoice */

void  HandleFileChoice( short item )
{
 switch ( item )
 {
 case iRedraw:
 PaintWindow();
 DrawFirstFloater();
 break;
 case iQuit:
 gDone = true;
 break;
 }
}

/************************************* HandleColorsChoice */

void  HandleColorsChoice( short item )
{
 MenuHandle menu;
 
 menu = GetMenu( mColors );
 
 if ( item == iUseRGB )
 {
 gIsRGB = true;
 
 SetItem( menu, iRed, "\pRandom Reds" );
 SetItem( menu, iGreen, "\pRandom Greens" );
 SetItem( menu, iBlue, "\pRandom Blues" );
 
 CheckItem( menu, iUseRGB, true );
 CheckItem( menu, iUseHSV, false );
 CheckItem( menu, iRed, gRandomReds );
 CheckItem( menu, iGreen, gRandomGreens );
 CheckItem( menu, iBlue, gRandomBlues );
 }
 else if ( item == iUseHSV )
 {
 gIsRGB = false;
 
 SetItem( menu, iHue, "\pRandom Hue" );
 SetItem( menu, iSaturation, "\pRandom Saturation" );
 SetItem( menu, iBrightness, "\pRandom Brightness" );
 
 CheckItem( menu, iUseRGB, false );
 CheckItem( menu, iUseHSV, true );
 CheckItem( menu, iHue, gRandomHue );
 CheckItem( menu, iSaturation, gRandomSaturation );
 CheckItem( menu, iBrightness, gRandomBrightness );
 }
 else if ( gIsRGB )
 {
 switch ( item )
 {
 case iRed:
 gRandomReds = !gRandomReds;
 CheckItem( menu, iRed, gRandomReds );
 break;
 case iGreen:
 gRandomGreens = ! gRandomGreens;
 CheckItem( menu, iGreen, gRandomGreens );
 break;
 case iBlue:
 gRandomBlues = ! gRandomBlues;
 CheckItem( menu, iBlue, gRandomBlues );
 break;
 }
 }
 else
 {
 switch ( item )
 {
 case iHue:
 gRandomHue = !gRandomHue;
 CheckItem( menu, iHue, gRandomHue );
 break;
 case iSaturation:
 gRandomSaturation = ! gRandomSaturation;
 CheckItem( menu, iSaturation, gRandomSaturation );
 break;
 case iBrightness:
 gRandomBrightness = ! gRandomBrightness;
 CheckItem( menu, iBrightness, gRandomBrightness );
 break;
 }
 }
 PaintWindow();
 DrawFirstFloater();
}

/****************** DrawFirstFloater *********************/

void  DrawFirstFloater( void )
{
 CopyBits( &(gMainWindow->portBits), 
 (BitMap *)(*gPixMapSave),
 &gWorldRect, &gWorldRect, srcCopy, nil );
 
 gSavedFloaterRect = gPictWorldRect;
 OffsetRect( &gSavedFloaterRect, 1, 1 );

 CopyBits( (BitMap *)(*gPixMapPict), 
 &(gMainWindow->portBits),
 &gPictWorldRect, &gSavedFloaterRect, transparent, nil );
}

/****************** MoveFloater *********************/

void  MoveFloater( void )
{
 Rect   r;
 RgnHandlenewRgn, savedRgn, oldClip;

 CalcNewFloaterPosition();
 
 CopyBits( (BitMap *)(*gPixMapSave), 
 (BitMap *)(*gPixMapSaveMix),
 &gWorldRect, &gWorldRect, srcCopy, nil );
 
 r = gPictWorldRect;
 OffsetRect( &r, gXBump + 1, gYBump + 1 );
 
 CopyBits( (BitMap *)(*gPixMapPict), 
 (BitMap *)(*gPixMapSaveMix),
 &gPictWorldRect, &r, transparent, nil );
 
 r = gSavedFloaterRect;
 InsetRect( &r, -1, -1 );
 
 CopyBits( (BitMap *)(*gPixMapSaveMix), 
 &(gMainWindow->portBits),
 &gWorldRect, &r, srcCopy, nil );
 OffsetRect( &gSavedFloaterRect, gXBump, gYBump );
 
 r = gSavedFloaterRect;
 InsetRect( &r, -1, -1 );
 
 CopyBits( &(gMainWindow->portBits), 
 (BitMap *)(*gPixMapSaveMix),
 &r, &gWorldRect, srcCopy, nil );
 
 r = gWorldRect;
 OffsetRect( &r, -gXBump, -gYBump );
 
 CopyBits( (BitMap *)(*gPixMapSave), 
 (BitMap *)(*gPixMapSaveMix),
 &gWorldRect, &r, srcCopy, nil );
 CopyBits( (BitMap *)(*gPixMapSaveMix), 
 (BitMap *)(*gPixMapSave),
 &gWorldRect, &gWorldRect, srcCopy, nil );
}

/*********************************** CalcNewFloaterPosition */

void  CalcNewFloaterPosition( void )
{
 Rect r;
 
 r = gSavedFloaterRect;
 OffsetRect( &r, gXBump, gYBump );
 
 if ( (r.left < gMainWindow->portRect.left) ||
  ( r.right > gMainWindow->portRect.right ) )
 gXBump *= -1;
 if ( (r.top < gMainWindow->portRect.top) ||
  ( r.bottom > gMainWindow->portRect.bottom ) )
 gYBump *= -1;
}

/***************** DoError ********************/

void  DoError( Str255 errorString )
{
 ParamText( errorString, kEmptyString, kEmptyString,
 kEmptyString );
 
 StopAlert( kErrorAlertID, kNilFilterProc );
 ExitToShell();
}

Running PixMapper

As soon as you run PixMapper, the menu bar, featuring the •, File, Edit, and Colors menus, will appear. Next, a window will appear, filling the entire main screen. PixMapper will fill the window with colored squares. Finally, PixMapper will animate the PICT, starting in the upper left corner moving towards the lower right corner. Every time the PICT hits the edge of the window, the PICT will bounce off and continue in the opposite direction.

Figure 5. PixMapper in action.

The speed of your PICT will depend on the speed of your machine and the size of the PICT. The important thing to notice is that the PICT animates smoothly with absolutely no flicker. If there is any hesitation, it is most likely due to the system taking time to do some housekeeping chore.

Try selecting Redraw from the File menu. PixMapper will redraw the window and start the animation over again.

Next, go to the Colors menu and play with all the different menu settings. PixMapper lets you play with both the RGB and HSV color models. RGB lets you set a colors red, green, and blue values. If you uncheck Random Reds for example, the screen will be redrawn with a red value of 0 (all greens and blues). If Random Reds is checked, the red component of the color is a random value from 0 to 65535.

If you select HSV Colors, you’ll be able to work with hue, saturation, and brightness rather than red, green, and blue. Unlike the RGB menu items, if you uncheck Random Hues, a color’s hues component is set to 65535 instead of 0. Play around with these settings.

Till Next Month

Next month, we’ll walk through the PixMapper source code. In the meantime, pop open Inside Macintosh and read about GWorlds and PixMaps. See you next month...

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

The beginner's guide to Warbits
Warbits is a turn-based strategy that's clearly inspired by Nintendo's Advance Wars series. Since turn-based strategy games can be kind of tricky to dive into, see below for a few tips to help you in the beginning. Positioning is crucial [Read... | Read more »
How to upgrade your character in Spellsp...
So you’ve mastered the basics of Spellspire. By which I mean you’ve realised it’s all about spelling things in a spire. What next? Well you’re going to need to figure out how to toughen up your character. It’s all well and good being able to spell... | Read more »
5 slither.io mash-ups we'd love to...
If there's one thing that slither.io has proved, it's that the addictive gameplay of Agar.io can be transplanted onto basically anything and it will still be good fun. It wouldn't be surprising if we saw other developers jumping on the bandwagon,... | Read more »
How to navigate the terrain in Sky Charm...
Sky Charms is a whimsical match-'em up adventure that uses creative level design to really ramp up the difficulty. [Read more] | Read more »
Victorious Knight (Games)
Victorious Knight 1.3 Device: iOS Universal Category: Games Price: $1.99, Version: 1.3 (iTunes) Description: New challenges awaits you! Experience fresh RPG experience with a unique combat mechanic, packed with high quality 3D... | Read more »
Agent Gumball - Roguelike Spy Game (Gam...
Agent Gumball - Roguelike Spy Game 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: Someone’s been spying on Gumball. What the what?! Two can play at that game! GO UNDERCOVERSneak past enemy... | Read more »
Runaway Toad (Games)
Runaway Toad 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: It ain’t easy bein’ green! Tap, hold, and swipe to help Toad hop to safety in this gorgeous new action game from the creators of... | Read more »
PsyCard (Games)
PsyCard 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: From the makers och Card City Nights, Progress To 100 and Ittle Dew PSYCARD is a minesweeper-like game set in a cozy cyberpunk... | Read more »
Sago Mini Robot Party (Education)
Sago Mini Robot Party 1.0 Device: iOS Universal Category: Education Price: $2.99, Version: 1.0 (iTunes) Description: -- Children's Technology Review Editor's Choice -- | Read more »
How to get a high score in every level o...
Sky Charms is an adorable match three puzzler that provides a decent challenge thanks to its creative level design. It regularly presents something new, forcing you to think on your feet. [Read more] | Read more »

Price Scanner via MacPrices.net

Apple restocks Certified Refurbished Mac mini...
Apple has restocked Certified Refurbished 2014 Mac minis, with models available starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: - 1.4GHz Mac mini: $419 $... Read more
15-inch 2.2GHz Retina MacBook Pro on sale for...
Amazon.com has the 15″ 2.2GHz Retina MacBook Pro on sale for $1699.99 including free shipping. Their price is $300 off MSRP, and it’s the lowest price available for this model from any reseller (and... Read more
Apple Beats Microsoft at Own Game; Amazon Pri...
First quarter seasonality combined with an overall disinterested customer base led to an annual decline of 14.7% in worldwide tablet shipments during the first quarter of 2016 (1Q16). Worldwide... Read more
Tablets Had Worst Quarter Since 2012, says St...
The global tablet market began 2016 just as 2015 left off, down. Tablet shipments fell 10% to 46.5 million units during the Q1 2016, according to the new “Preliminary Global Tablet Shipments and... Read more
Clearance 13-inch MacBook Airs, Apple refurbi...
Apple recently dropped prices on certified refurbished 2015 13″ MacBook Airs with 4GB of RAM with models now available starting at $759. An Apple one-year warranty is included with each MacBook, and... Read more
Clearance 12-inch Retina MacBooks, Apple refu...
Apple has dropped prices on Certified Refurbished 2015 12″ Retina MacBooks with models now available starting at $929. Apple will include a standard one-year warranty with each MacBook, and shipping... Read more
Aleratec Releases Mac Software Upgrade for 1...
California based Aleratec Inc., designer, developer and manufacturer of Portable Device Management (PDM) charge/sync products for mobile devices and professional-grade duplicators for hard disk... Read more
Sale! Amazon offers 27-inch iMac, 13-inch 2.9...
Amazon has the 27″ 3.2GHz 5K iMac and the 13″ 3.9GHz Retina MacBook Pro on sale for $300 off MSRP, each including free shipping, for a limited time: - 27″ 3.2GHz/1TB HD 5K iMac (model MK462LL/A): $... Read more
Apple refurbished 13-inch Retina MacBook Pros...
Apple has Certified Refurbished 13″ Retina MacBook Pros available for up to $270 off the cost of new models. An Apple one-year warranty is included with each model, and shipping is free: - 13″ 2.7GHz... Read more
13-inch 2.7GHz/128GB Retina MacBook Pro on sa...
Take $200 off MSRP on the price of a new 13″ 2.7GHz/128GB Retina MacBook Pro (model MF839LL/A) at Amazon. Shipping is free: - 13″ 2.7GHz/128GB Retina MacBook Pro: $1099.99 $200 off MSRP Act now if... Read more

Jobs Board

Restaurant Manager (Neighborhood Captain) - A...
…in every aspect of daily operation. WHY YOU'LL LIKE IT: You'll be the Big Apple . You'll solve problems. You'll get to show your ability to handle the stress and Read more
Automotive Sales Consultant - Apple Ford Linc...
…you. The best candidates are smart, technologically savvy and are customer focused. Apple Ford Lincoln Apple Valley is different, because: $30,000 annual salary Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Solutions Consultant - Apple (United...
# Apple Solutions Consultant Job Number: 48260200 Phoenix, Arizona, United States Posted: Apr. 22, 2016 Weekly Hours: 40.00 **Job Summary** As an Apple Solutions Read more
Restaurant Manager (Neighborhood Captain) - A...
…in every aspect of daily operation. WHY YOU'LL LIKE IT: You'll be the Big Apple . You'll solve problems. You'll get to show your ability to handle the stress and Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.