TweetFollow Us on Twitter

Nov 98 Getting Started

Volume Number: 14 (1998)
Issue Number: 11
Column Tag: Getting Started

Color QuickDraw Basics

by Dave Mark and Dan Parks Sydow

How a Mac program handles color

Last month's column introduced black and white animation. Next month's column looks at color animation. Before we make the transition from monochrome to color, we need to gain an understanding of Color QuickDraw - the part of the Macintosh Toolbox that allows a programmer to bring color into a program.

When creating a program, great effort can be spent in setting up data structures, or developing algorithms, or performing some other behind-the-scenes chore. So often times a programmer's hard work doesn't seem to really do much - a long programming session may result in a lot of code being written, but very little of it displays anything on screen. That's what makes working with color one of the most enjoyable parts of programming. Not only do you get to see the results of your efforts, you get to see them in brilliant, splendid color!

You'll have a little fun this month learning about programming in color - but we can't let you have too much fun. With that in mind, this month's article also covers the topics of multiple monitors and monitor pixel depth. Although you may only have one monitor connected to your Mac, and although you may know the number of colors your monitor is displaying, can you say the same for every one of the users of your soon-to-be-written, best-selling, color Mac program? Here you'll see how your program can easily account for the many possible monitor and color level combinations on the systems of Mac users.

About Color QuickDraw

The original version of QuickDraw shipped with the very first Macs, and supported the display of only black-and-white drawing on the built-in screen. To keep track of the color of any one screen pixel required just a single bit (with the bit's two states representing black and white). The original QuickDraw supported a pixel depth of 1 - one bit dedicated to one pixel. Ever since the introduction of the Macintosh II, all Macs have shipped with a new version of QuickDraw known as Color QuickDraw. Color QuickDraw started as an 8-bit technology. Dedicating 8 bits to a pixel allowed for the support of 256 colors, and gave this version of QuickDraw a pixel depth of 8. Color QuickDraw then migrated to a 32-bit technology. This pixel depth of 32 could supporting millions of colors.

It's been quite a while since Apple shipped a Mac that didn't support color, so in all likelihood the user of your color program will be able to view it as you intended it to be seen. Still, your program should verify that the host machine does in fact have a version of Color QuickDraw. You'll do this for the sake of those few who are still using monochrome Macs. If your program attempts to use Color QuickDraw Toolbox routines on such a computer, it will crash that machine. Fortunately the Gestalt() Toolbox function makes it easy to test for the presence of Color QuickDraw.

If you aren't familiar with Gestalt(), you should get acquainted with it. This routine is a powerful tool that can be used to find out all sorts of things about the hardware and software of the Mac that a program is currently running on. In general, if you want to take advantage of a feature of the Macintosh that is not necessarily available on every Mac, you'll want to call Gestalt() to make sure the feature is present. Here's the prototype for Gestalt():

OSErr Gestalt( OSType selector, long *response );

Gestalt() takes two parameters. The first, selector, allows you to tell Gestalt() what part of the hardware or software you are interested in. You can use Gestalt() to find out how much RAM is on the current Mac, what version of the operating system is installed, what processor is in the Mac, whether the machine supports Color QuickDraw, and much, much more.

After executing, Gestalt() places the requested information in the second parameter, response. The type of information in response depends on the selector used. There's an Apple-defined selector constant for just about every imaginable Mac feature. Here we'll look at just the gestaltQuickdrawVersion selector. You can check the Gestalt Manager chapter of Inside Macintosh: Operating System Utilities, or peruse the Gestalt.h universal interface header file, to learn more about Gestalt() and its various selectors and responses.

To check for the presence of Color QuickDraw, begin by calling Gestalt() with a selector of gestaltQuickdrawVersion:

long   response;
OSErr  err;

err = Gestalt( gestaltQuickdrawVersion, &response );

Now compare the returned value in response to one of the pertinent Apple-defined response constants. For a selector of gestaltQuickdrawVersion there are five possible values Gestalt() might return:

gestaltOriginalQD        = 0x0000,         /* original 1-bit QD */
gestalt8BitQD            = 0x0100,         /* 8-bit color QD */
gestalt32BitQD           = 0x0200,         /* 32-bit color QD */
gestalt32BitQD11         = 0x0201,         /* 32-bit color QDv1.1 */
gestalt32BitQD12         = 0x0220,         /* 32-bit color QDv1.2 */
gestalt32BitQD13         = 0x0230          /* 32-bit color QDv1.3 */

To check for a particular version of Color QuickDraw, test the response as such:

if ( response == gestalt32BitQD )

It's more likely that you're program will simply want to know if the user's machine supports color - any color. To do that, see if the response is greater than the constant representing the original, monochrome version of QuickDraw. Here's the test we'll be using in this month's example:

if ( response > gestaltOriginalQD )

If the above test fails, exit the program or, better yet, call an error-handling routine to alert the user of the incompatibility problem.

Monitors and Color Levels

If you connect a second monitor to your Macintosh, it may or may not have the same pixel depth as the first monitor. That is, one monitor (or the graphics card controlling the monitor) may be capable of displaying more colors than the other monitor. Even if the two monitors are capable of displaying the same number of colors, at any given time they may not be doing that - the user may have limited the pixel depth of a monitor using the Monitors & Sound control panel. Because of this, the Mac needs a means of being able to know the state of each monitor, or graphics device.

Color QuickDraw represents each graphics device attached to a Mac, whether a display device or an offscreen color device (offscreen situations will be discussed in next month's article), using a gDevice data structure. Toolbox routines like GetDeviceList(), GetNextDevice(), and GetMainDevice() work with a gDevice data structure. To obtain a handle to the first device in a gDevice list, call GetDeviceList():

GDHandle      curDevice;
   
curDevice = GetDeviceList();

To find out which device is currently set to display the most colors, cycle through the list of devices. To do that, call GetNextDevice() within a loop. Before getting the next device from the gDevice list, check the pixel depth of the current device by calling the application-defined routine GetDeviceDepth().

short      curDepth;
while ( curDevice != NULL )
{
   curDepth = GetDeviceDepth( curDevice );
      
   curDevice = GetNextDevice( curDevice );
}

As you'll see in the code walk-though, there's a little bit more to it then what's shown above. In particular, you'll need to see how the application-defined GetDeviceDepth() is implemented. Also, when we encounter the device that has the greatest color depth, we'll want to save that information in the body of the above loop.

The RGB Color Model

Color QuickDraw represents colors using the RGB model. RGB stands for red, green, and blue - the three colors that combine to make up a single RGB color. The RGB model is based on the RGBColor data structure:

struct RGBColor
{
   unsigned short   red;
   unsigned short   green;
   unsigned short   blue;
};

Each component of an RGBColor can take on a value from 0 to 65535. The lower the value, the less intense the contribution of that one color to the overall, combined color. For instance, if red is 65535 and blue and green are both 0, the resulting color is bright red. As a second example, if the green component has a value of 0, and the red and blue components each have a value of 65535, then the resulting color will be an intense violet - a color rich in red and blue, but lacking any green. The extremes of the color spectrum are black and white. If red, green, and blue are each 0, the RGBColor represents black - the lack of any color in each component results in this dimmest of colors. If all three components are each set to 65535, the RGBColor represents white - the maximum intensity of each component creates this, the brightest of colors. As an aside, "colors" is used loosely in the previous two sentences. Black and white aren't truely colors: black is the absense of color, while white is the mixture of all visible wavelengths (so white is the combination of all colors).

We'll work with the RGB model throughout this program. Be aware that Color QuickDraw does support other color models, such as HSV (which is hue, saturation and brightness) and CMY (which is cyan, magenta, yellow) and provides routines that convert colors between each model.

To take advantage of the Color QuickDraw routines, you'll want to replace your use of WindowRecords with CWindowRecords. CWindowRecords are similar to WindowRecords, with a CGrafPort replacing the traditional GrafPort. Even with these changes, you can pass a pointer to a CWindowRecord to all the routines that usually take a WindowPtr. In this article's program, the only noticeable instance of working with color windows is in the creation of a window, where a call to GetNewCWindow() replaces the call we've used in the past - GetNewWindow().

ColorMondrian

This month's program, ColorMondrian, checks to see which monitor is capable of displaying the most colors and then opens a window on that monitor. If the user's Mac has only one monitor, then of course ColorMondrian knows to display the window on it. After the window is resized to match the size of the monitor's screen, the program draws randomly generated ovals, in randomly selected colors. ColorMondrian works just fine on a system that has a grayscale monitor - in Color QuickDraw shades of gray count as colors too! The shapes are continuously drawn until the user quits the program. Figure 1 shows a part of the_ColorMondrian window as shapes are being drawn to it.

Figure 1. The ColorMondrian window.

Creating the ColorMondrian Resources

To get started, move into your CodeWarrior development folder and create a folder named ColorMondrian. Launch ResEdit and create a new resource file named ColorMondrian.rsrc inside the ColorMondrian folder. Figure 2 shows the five types of resources used by ColorMondrian - you'll notice that they're all types with which you're familiar.

Figure 2. The MENU resources.

Figure 2 also shows the two particular MENU resources the program needs. Of course your own real-world color application will have more MENU resources, including one for the standard Edit menu and one for each application-defined menu.

ColorMondrian uses one ALRT and one DITL resource - they're shown in Figure 3. Both are used to support the error-handling alert displayed by the program's DoError() routine (a routine touched on in the ColorMondrian walk-through, and discussed at length two articles ago in the Getting Started column on Apple Events).

Figure 3. The resources needed to implement error-handling.

The only other resource needed is a WIND that will be used to hold the randomly drawn shapes. The size of the WIND isn't critical - we'll be resizing the window from within the source code. Since the window will be fixed on the screen, the type of WIND isn't of great importance either.

That's it for the ColorMondrian.rsrc file. Now quit ResEdit, making sure to first save your changes.

Creating the ColorMondrian Project

Launch CodeWarrior and create a new project based on the MacOS:C_C++:MacOS Toolbox:MacOS Toolbox Multi-Target stationary. You've already created the ColorMondrian project folder, so uncheck the Create Folder check box. Name the project ColorMondrian.mcp and specify that the project be placed in the ColorMondrian folder.

In the newly created project window you can go ahead and remove the SillyBalls.c and SillyBalls.rsrc files. Then add the ColorMondrian.rsrc file. The ColorMondrian project doesn't use any of the standard ANSI libraries, so you'll be safe in removing the ANSI Libraries folder.

Now choose New from the File menu to create a new, empty source code window. Save it with the name ColorMondrian.c. Add this new, empty file to the project by choosing Add Window from the Project menu. You'll find the complete source code listing for ColorMondrian in the source code walk-through. You can type it into the ColorMondrian.c file as you read the walk-through, or you can save yourself some effort and download the whole ColorMondrian project from MacTech's ftp site at ftp://ftp.mactech.com/src/mactech/volume14_1998/14.11.sit.

Walking Through the Source Code

As in past projects, ColorMondrian starts off with some constant definitions.

/********************* constants *********************/

#define kMBARResID               128
#define kALRTResID               128
#define kWINDResID               128

#define kSleep                   7
#define kMoveToFront             (WindowPtr)-1L
#define kWindowMargin            15

#define kRandomUpperLimit        32768

#define kDelaySixtiethSeconds    2

#define mApple                   128
#define iAbout                   1

#define mFile                    129
#define iQuit                    1

These are followed by ColorMondrian's one global variable:

/****************** global variables *****************/

Boolean      gDone;

Next come the program's function prototypes:

/********************* functions *********************/

void         ToolBoxInit( void );
Boolean      HasColorQD( void );
void         MenuBarInit( void );
GDHandle     GetDeepestDevice( void );
short        GetDeviceDepth( GDHandle device );
void         CreateWindow( GDHandle device );
void         EventLoop( void );
void         DrawRandomOval( void );
void         RandomColor( RGBColor *colorPtr );
void         RandomRect( Rect *rectPtr );
short        Randomize( short range );
void         DoEvent( EventRecord *eventPtr );
void         HandleMouseDown( EventRecord *eventPtr );
void         HandleMenuChoice( long menuChoice );
void         HandleAppleChoice( short item );
void         HandleFileChoice( short item );
void         DoError( Str255 errorString );

The main() function starts off with the declaration of a variable that's to be used to keep track to the deepest device - the graphic device currently set to display the most colors.

/************************ main ***********************/

void   main( void )
{
   GDHandle   deepestDevice;

Next, it's the familiar call to the Toolbox initialize function ToolBoxInit(). After that we call HasColorQD() (discussed next) to check for the presence of a version of Color QuickDraw. If the user's Mac doesn't support color, we invoke our own error-handling routine to inform the user of the reason the program is exiting.

   ToolBoxInit();
   
   if ( ! HasColorQD() )
      DoError( "\pThis Mac doesn't support Color QuickDraw!" );

A call to MenuBarInit() (another familiar routine, and one shown ahead) loads the menu-related resources and displays the menu bar. Then the local variable deepestDevice is set to reference the - you guessed it - device with the deepest pixel depth. This variable is passed to CreateWindow() so that the program's one window will be opened on the desired monitor. Both GetDeepestDevice() and CreateWindow() are described a bit down the way. A call to our now standard EventLoop() function wraps up main().

   MenuBarInit();
   
   deepestDevice = GetDeepestDevice();
   CreateWindow( deepestDevice );
   
   EventLoop();
}

ToolBoxInit() is, as you've certainly surmised, the same as in the past:

/******************** ToolBoxInit ********************/

void   ToolBoxInit( void )
{
   InitGraf( &qd.thePort );
   InitFonts();
   InitWindows();
   InitMenus();
   TEInit();
   InitDialogs( 0L );
   InitCursor();
}

HasColorQD() holds the call to Gestalt() that tells whether Color QuickDraw is installed. The gestaltQuickdrawVersion selector retrieves a value that is compared to the Apple-defined constant gestaltOriginalQD. Before examining response, HasColorQD() makes sure that the call to Gestalt() itself was successful (irrespective of the value now held in response). If we're error-free, it's on to the determination of whether Color QuickDraw is present. If response is greater than gestaltOriginalQD, the host machine has a version of QuickDraw other than the original version (and hence a Color QuickDraw version), the comparison test passes, and HasColorQD() returns a value of true to tell the caller (main()) to carry on with the program. If response is equal to gestaltOriginalQD, ColorMondrian is running on a monochrome Mac and needs to terminate.

/********************* HasColorQD ********************/

Boolean   HasColorQD( void )
{
   long      response;
   OSErr   err;

   err = Gestalt( gestaltQuickdrawVersion, &response );

   if ( err != noErr )
      DoError( "\pError calling Gestalt()" );

   if ( response > gestaltOriginalQD )
      return( true );
   else
      return( false );   
}

MenuBarInit() introduces no new code:

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

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

   menu = GetMenuHandle( mApple );
   AppendResMenu( menu, 'DRVR' );
   
   DrawMenuBar();
}

GetDeepestDevice() steps through the device list, calling the application-defined GetDeviceDepth() function (described next) to determine the device with the deepest pixel depth. Most of the code in GetDeepestDevice() was described earlier in this article. Here you see the addition of code that keeps track of the maximum color depth and the device on which this maximum is found. We need to carry out the loop until all devices are checked - the last device in the list may be the one with the maximum depth. Each pass through the loop compares what is so far the maximum depth with the depth of the device currently being checked. When the entire device list has been checked, the device with the deepest depth is returned to the caller (which is main()).

/***************** GetDeepestDevice ******************/

GDHandle   GetDeepestDevice( void )
{
   GDHandle   curDevice, maxDevice = NULL;
   short      curDepth, maxDepth = 0;
   
   curDevice = GetDeviceList();
   
   while ( curDevice != NULL )
   {
      curDepth = GetDeviceDepth( curDevice );
      
      if ( curDepth > maxDepth )
      {
         maxDepth = curDepth;
         maxDevice = curDevice;
      }

      curDevice = GetNextDevice( curDevice );
   }
   
   return( maxDevice );
}

GetDeviceDepth() starts by retrieving a handle to the device's PixMap from the GDevice structure. A PixMap is a color version of a BitMap. The BitMap data structure was discussed in last month's article - the PixMap data structure will be described in next month's column (if you're impatient check out the Color QuickDraw chapter of Inside Macintosh: Imaging With QuickDraw). The pixelSize field holds the depth of this PixMap. That reveals the device's depth, so that's what's returned to the caller (the GetDeepestDevice() function).

/****************** GetDeviceDepth *******************/

short   GetDeviceDepth( GDHandle device )
{
   PixMapHandle   screenPixMapH;
   
   screenPixMapH = (**device).gdPMap;
   
   return( (**screenPixMapH).pixelSize );
}

CreateWindow() creates a window on the specified device. Recall that main() invoked GetDeepestDevice() just before calling CreateWindow(). Doing so enabled main() to pass CreateWindow() the device that is to be the recipient of the new window:

/******************* CreateWindow ********************/

void   CreateWindow( GDHandle device )
{
   WindowPtr   window;
   Rect        wBounds;
   long        windowWidth, windowHeight;

The handle stored in device must be dereferenced twice to get to the GDevice structure. The gdRect field is a Rect containing the device's bounding rectangle.

   wBounds = (**device).gdRect;

GetMainDevice() returns a handle to the device containing the menu bar. If the device that is to receive the new window happens to be the main device, we need to take the height of the menu bar into account when calculating the window's size and placement. The Toolbox function GetMBarHeight() returns the height of the menu bar in pixels.

   if ( device == GetMainDevice() )
      wBounds.top += GetMBarHeight();

Purely for aesthetic reasons we make the window a little smaller then the screen. A call to InsetRect() handles that task.

   InsetRect( &wBounds, kWindowMargin, kWindowMargin );

Now we call GetNewCWindow() to create a new CWindowRecord, as opposed to the WindowRecord that would result from a call to GetNewWindow().

   window = GetNewCWindow( kWINDResID, nil, kMoveToFront );

The window is based on a WIND resource. At the time the WIND was created there was obviously no way of knowing the size of the monitor that would eventually be displaying the window. So now, in our code, we need to adjust the window's size and location before showing it. We base the calculations on the boundaries of the wBounds rectangle, which holds the display area of the graphics device (less the menu bar height, if relevant). Calls to the Toolbox functions SizeWindow() and MoveWindow() take care of the window manipulations.

   windowWidth = wBounds.right - wBounds.left;
   windowHeight = wBounds.bottom - wBounds.top;
   
   SizeWindow( window, windowWidth, windowHeight, true );
   MoveWindow( window, wBounds.left, wBounds.top, true );
   
   ShowWindow( window );
   SetPort( window );
}

EventLoop() includes the same code as previous versions, but it also adds a few new lines. The first addition is a call to the Toolbox function GetDateTime(). This call is made to initialize the randSeed field of the global data structure qd.

If you've made use of any of the five standard patterns (as in qd.ltGray and qd.black) or if you've accessed the map of the screen (qd.screenBits), you're familiar with the system-defined qd variable. The QDGlobals data structure of which qd is based on includes a randSeed member that serves as a seed for a random number generator. A computer's random number generator isn't truly random - given the same seed, or initial value, from which to generate random numbers, it will always produce the same sequence of numbers. This behavior won't matter during one running of a program - the seed is only used once to "kick off" the generator, and after that the generated numbers then seem to be random. Using the same seed will matter during subsequent running of the program - the same sequence of supposedly random numbers will appear during each running of the program. This repetitive sequence of random numbers can be avoided by choosing a different seed value during each running of a program. The GetDateTime() routine is generally used to return a long value that specifies the number of seconds that have elapsed from midnight January 1st 1904 until the time of the call to GetDateTime(). So each call to GetDateTime() is guaranteed to produce a unique value (provided the calls are made greater than one second apart!). We'll take advantage of that fact to use GetDateTime() not as a means of determining the current date, but instead to set the qd data member randSeed to a value never before used by the ColorMondrian program.

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

void   EventLoop( void )
{      
   EventRecord      event;
   unsigned long   finalTicks;   
   
   GetDateTime( (unsigned long *)(&qd.randSeed) );

Now, on to the loop that repeatedly calls WaitNextEvent() and DoEvent() to retrieve and handle a single event. In this loop we've added a delay and a call to the application-defined DrawRandomOval() function. The purpose of DrawRandomOval() (discussed next) is obvious - it draws a single, randomly sized and placed oval. The delay is included to slow down the drawing - without it the program really flies! Delay() is a Toolbox function that pauses a program. The first parameter is the number of sixtieths of a second to delay (pass 30 to delay a half second, 60 to delay a full second, and so forth). The second parameter is a value filled in by the Delay() function. We ignore this returned value - it represents the number of sixtieths of a second that have elapsed since the user's Mac was started up. You can omit the delay by commenting out the call to Delay(). You can increase the delay by upping the value of the application-defined constant kDelaySixtiethSeconds.

   gDone = false;
   while ( gDone == false )
   {
      if ( WaitNextEvent( everyEvent, &event, kSleep, nil ) )
         DoEvent( &event );
      
      Delay( kDelaySixtiethSeconds, &finalTicks );   
      DrawRandomOval();
   }
}

DrawRandomOval() generates a random color, makes that color the current foreground color, generates random boundaries for an oval, and then draws that oval in the foreground color. RandomColor() and RandomRect() are application-defined routines that are described next.

/******************* DrawRandomOval ******************/

void   DrawRandomOval( void )
{
   Rect         randomRect;
   RGBColor   color;
   
   RandomColor( &color );
   RGBForeColor( &color );
   RandomRect( &randomRect );
   PaintOval( &randomRect );
}

DrawRandomOval() calls RandomColor() to generate an RGBColor. RandomColor() comes up with this RGBColor by randomly generating values for red, green, and blue that range from 0 to 65534. A call to the Toolbox function Random() generates a value in the range of -32767 to 32767. Adding 32767 to the number produced by Random() results in a value in the range used to specify each component of an RGBColor.

/********************* RandomColor *******************/

void   RandomColor( RGBColor *colorPtr )
{
   colorPtr->red = Random() + 32767;
   colorPtr->blue = Random() + 32767;
   colorPtr->green = Random() + 32767;
}

DrawRandomOval() calls RandomRect() to generate a random rectangle. The size of this rectangle is dependent on the size of the frontmost (and in this program, the only) window. It will be within this rectangle that DrawRandomOval() inscribes an oval.

/******************** RandomRect ********************/

void   RandomRect( Rect *rectPtr )
{
   WindowPtr   window;
   short         windowWidth;
   short         windowHeight;
   
   window = FrontWindow();
   
   windowWidth = window->portRect.right - 
                 window->portRect.left;
   windowHeight = window->portRect.bottom - 
                  window->portRect.top;

   rectPtr->left = Randomize( windowWidth );
   rectPtr->right = Randomize( windowWidth );
   rectPtr->top = Randomize( windowHeight );
   rectPtr->bottom = Randomize( windowHeight );
}

RandomRect() relies on Randomize() to do the work of generating a number from 0 to the specified upper limit. Randomize() begins by calling the Toolbox function Random() to generate a number in the range of -32767 to 32767. If the value is negative, we simply multiply it by -1 to make it positive. Multiplying by the range (which is either the width or height of the window) and then dividing by kRandomUpperLimit (defined to be 32767) results in a value that is always greater than or equal to 0 and and always less than or equal to the width or height of the window.

/******************** Randomize **********************/

short   Randomize( short range )
{
   long      randomNumber;
   
   randomNumber = Random();
   
   if ( randomNumber < 0 )
      randomNumber *= -1;
   
   return( (randomNumber * range) / kRandomUpperLimit );
}

That's the last you'll need to hear from us for the remainder of the code walk-through. The DoEvent(), HandleMouseDown(), HandleMenuChoice(), HandleAppleChoice(), HandleFileChoice(), and DoError() functions are all similar to versions introduced and explained in recent columns.

/*********************** 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;
   }
}

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

void   HandleMouseDown( EventRecord *eventPtr )
{
   WindowPtr   window;
   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;
      }
      HiliteMenu( 0 );
   }
}

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

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

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

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

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

void   DoError( Str255 errorString )
{
   ParamText( errorString, "\p", "\p", "\p" );
   
   StopAlert( kALRTResID, nil );
   
   ExitToShell();
}

Running ColorMondrian

Run ColorMondrian by selecting Run from the Project menu. Once the code compiles, a window appears and begins to get filled with randomly colored, randomly placed, ovals. Make sure to choose Quit from the File menu to exit the program before hypnosis sets in!

Till Next Month...

There's a lot to learn about Color QuickDraw - hopefully, this column gave you a good foothold. You can continue learning about color programming by modifying the ColorMondrian code. If you want to experiment with QuickDraw shapes other than ovals, modify the DrawRandomOval() routine. Replace that function's call to PaintOval() with a call to your own application-defined function that randomly selects one of several shapes to draw. You can read up on shape-drawing in the QuickDraw Drawing chapter of Inside Macintosh: Imaging With QuickDraw.

To learn more about drawing in color, plan to spend some time in the Color QuickDraw chapter of Imaging With QuickDraw. In that chapter you'll find plenty of information on a variety of topics we've touched on here, including the Gestalt() function, RGB colors, color graphics ports, the setting of the foreground and background colors, and more.

After reading last month's column on monochrome animation and this month's column on Color QuickDraw, you're all set for color animation. That's coming next month. If you can't stand the suspense, go ahead and get a jump start on the topic by reading up on pixel maps and the PixMap data type in the Color QuickDraw chapter of Imaging With QuickDraw and on offscreen graphics worlds and the GWorld data type in the Offscreen Graphics Worlds chapter of that same book. We'll be back next month, in living color, to morph the monochrome BitMapper program into the color animation PixMapper program. See you then...

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Ableton Live 11.3.11 - Record music usin...
Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
Affinity Photo 2.2.0 - Digital editing f...
Affinity Photo - redefines the boundaries for professional photo editing software for the Mac. With a meticulous focus on workflow it offers sophisticated tools for enhancing, editing and retouching... Read more
SpamSieve 3.0 - Robust spam filter for m...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
WhatsApp 2.2338.12 - Desktop client for...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
Fantastical 3.8.2 - Create calendar even...
Fantastical is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event details... Read more
iShowU Instant 1.4.14 - Full-featured sc...
iShowU Instant gives you real-time screen recording like you've never seen before! It is the fastest, most feature-filled real-time screen capture tool from shinywhitebox yet. All of the features you... Read more
Geekbench 6.2.0 - Measure processor and...
Geekbench provides a comprehensive set of benchmarks engineered to quickly and accurately measure processor and memory performance. Designed to make benchmarks easy to run and easy to understand,... Read more
Quicken 7.2.3 - Complete personal financ...
Quicken makes managing your money easier than ever. Whether paying bills, upgrading from Windows, enjoying more reliable downloads, or getting expert product help, Quicken's new and improved features... Read more
EtreCheckPro 6.8.2 - For troubleshooting...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
iMazing 2.17.7 - Complete iOS device man...
iMazing is the world’s favourite iOS device manager for Mac and PC. Millions of users every year leverage its powerful capabilities to make the most of their personal or business iPhone and iPad.... Read more

Latest Forum Discussions

See All

Motorsport legends NASCAR announce an up...
NASCAR often gets a bad reputation outside of America, but there is a certain charm to it with its close side-by-side action and its focus on pure speed, but it never managed to really massively break out internationally. Now, there's a chance... | Read more »
Skullgirls Mobile Version 6.0 Update Rel...
I’ve been covering Marie’s upcoming release from Hidden Variable in Skullgirls Mobile (Free) for a while now across the announcement, gameplay | Read more »
Amanita Design Is Hosting a 20th Anniver...
Amanita Design is celebrating its 20th anniversary (wow I’m old!) with a massive discount across its catalogue on iOS, Android, and Steam for two weeks. The announcement mentions up to 85% off on the games, and it looks like the mobile games that... | Read more »
SwitchArcade Round-Up: ‘Operation Wolf R...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for September 21st, 2023. I got back from the Tokyo Game Show at 8 PM, got to the office here at 9:30 PM, and it is presently 11:30 PM. I’ve done what I can today, and I hope you enjoy... | Read more »
Massive “Dark Rebirth” Update Launches f...
It’s been a couple of months since we last checked in on Diablo Immortal and in that time the game has been doing what it’s been doing since its release in June of last year: Bringing out new seasons with new content and features. | Read more »
‘Samba De Amigo Party-To-Go’ Apple Arcad...
SEGA recently released Samba de Amigo: Party-To-Go () on Apple Arcade and Samba de Amigo: Party Central on Nintendo Switch worldwide as the first new entries in the series in ages. | Read more »
The “Clan of the Eagle” DLC Now Availabl...
Following the last paid DLC and free updates for the game, Playdigious just released a new DLC pack for Northgard ($5.99) on mobile. Today’s new DLC is the “Clan of the Eagle" pack that is available on both iOS and Android for $2.99. | Read more »
Let fly the birds of war as a new Clan d...
Name the most Norse bird you can think of, then give it a twist because Playdigious is introducing not the Raven clan, mostly because they already exist, but the Clan of the Eagle in Northgard’s latest DLC. If you find gathering resources a... | Read more »
Out Now: ‘Ghost Detective’, ‘Thunder Ray...
Each and every day new mobile games are hitting the App Store, and so each week we put together a big old list of all the best new releases of the past seven days. Back in the day the App Store would showcase the same games for a week, and then... | Read more »
Urban Open-World RPG ‘Project Mugen’ Fro...
Last month, NetEase Games revealed a new free to play open world RPG tentatively titled Project Mugen for mobile, PC, and consoles. I’ve liked the setting and aesthetic since its first trailer, and today’s new video has the Game Designer and... | Read more »

Price Scanner via MacPrices.net

Apple AirPods 2 with USB-C now in stock and o...
Amazon has Apple’s 2023 AirPods Pro with USB-C now in stock and on sale for $199.99 including free shipping. Their price is $50 off MSRP, and it’s currently the lowest price available for new AirPods... Read more
New low prices: Apple’s 15″ M2 MacBook Airs w...
Amazon has 15″ MacBook Airs with M2 CPUs and 512GB of storage in stock and on sale for $1249 shipped. That’s $250 off Apple’s MSRP, and it’s the lowest price available for these M2-powered MacBook... Read more
New low price: Clearance 16″ Apple MacBook Pr...
B&H Photo has clearance 16″ M1 Max MacBook Pros, 10-core CPU/32-core GPU/1TB SSD/Space Gray or Silver, in stock today for $2399 including free 1-2 day delivery to most US addresses. Their price... Read more
Switch to Red Pocket Mobile and get a new iPh...
Red Pocket Mobile has new Apple iPhone 15 and 15 Pro models on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide service using all the major... Read more
Apple continues to offer a $350 discount on 2...
Apple has Studio Display models available in their Certified Refurbished store for up to $350 off MSRP. Each display comes with Apple’s one-year warranty, with new glass and a case, and ships free.... Read more
Apple’s 16-inch MacBook Pros with M2 Pro CPUs...
Amazon is offering a $250 discount on new Apple 16-inch M2 Pro MacBook Pros for a limited time. Their prices are currently the lowest available for these models from any Apple retailer: – 16″ MacBook... Read more
Closeout Sale: Apple Watch Ultra with Green A...
Adorama haș the Apple Watch Ultra with a Green Alpine Loop on clearance sale for $699 including free shipping. Their price is $100 off original MSRP, and it’s the lowest price we’ve seen for an Apple... Read more
Use this promo code at Verizon to take $150 o...
Verizon is offering a $150 discount on cellular-capable Apple Watch Series 9 and Ultra 2 models for a limited time. Use code WATCH150 at checkout to take advantage of this offer. The fine print: “Up... Read more
New low price: Apple’s 10th generation iPads...
B&H Photo has the 10th generation 64GB WiFi iPad (Blue and Silver colors) in stock and on sale for $379 for a limited time. B&H’s price is $70 off Apple’s MSRP, and it’s the lowest price... Read more
14″ M1 Pro MacBook Pros still available at Ap...
Apple continues to stock Certified Refurbished standard-configuration 14″ MacBook Pros with M1 Pro CPUs for as much as $570 off original MSRP, with models available starting at $1539. Each model... Read more

Jobs Board

Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Retail Key Holder- *Apple* Blossom Mall - Ba...
Retail Key Holder- APPLE BLOSSOM MALL Brand: Bath & Body Works Location: Winchester, VA, US Location Type: On-site Job ID: 03YM1 Job Area: Store: Sales and Support Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.