Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Programming With Color Quickdraw


SP 89: PROGRAMMING WITH COLOR QUICKDRAW

Chris is a member of the System Software group at Apple Computer and can be reached at 20525 Mariani Ave., MS 27-AJ, Cupertino, CA 95014.


The large base of software available for the Macintosh it imperative that any changes to the Mac, no matter how wonderful, not cause existing programs to stop working. Consequently, any improvement to the Macintosh, small or large, must be carefully designed to fit within the existing architecture. This is hard enough with simple changes, such as adding a single new toolbox call. With more extensive changes like adding color, maintaining compatibility can be a monumental task.

The goal of compatibility is allowing older software to continue to function, while the goal of new features is better software in the future. Well-written applications can benefit from both compatibility and new features. However, it is not always easy to create well-written applications. This is particularly true when approaching the problem of color. With this in mind, this article explains how you can write programs that are "color smart."

Maintaining Compatibility

One of the methods used to maintain compatibility is mimicry: New software emulates its older counterpart. Older applications continue to work, because the data structures and calls that they use are still supported. Often this support is a facade, though, and the calls get translated into the new way of doing things.

In Color QuickDraw, the CopyBits call continues to take the address of a port's portBits field as a parameter, even when that field contains a PixMap-Handle, and not a BitMap.

Sometimes the support for old calls and data structures is easy and natural. Other times, however, this support requires hacks to an otherwise clean design. And most times, the hacks would not be necessary if applications did not make assumptions about the environment in which they were running.

Some assumptions are obvious, and can easily be avoided. Other assumptions, though, are more subtle. When the Mac Plus was new, it was clear by then that assuming the location of the screen RAM was bad. The assumption that each pixel on the screen was represented by a single bit was less obvious. But because multiple monitors for a single personal computer were not yet common, it was natural to assume the presence of a single, rectangular, contiguous display.

Because it is difficult to completely predict future changes, some assumptions will always be made. Making new software compatible generally means supporting these unavoidable assumptions.

Unfortunately, there is an adverse side effect to supporting such assumptions with new software: Developers continue to write new programs that make the same assumptions. Writing such programs is tacitly encouraged, because software that assumes that it is running on the lowest common denominator of the machines will always run on all of the machines. This practice is acceptable, up to a point.

Programming for the lowest common denominator becomes undesirable when new software takes advantage of some of the new features and yet still relies on support for old assumptions. By their nature, assumptions valid for older environments are usually limiting in the new environment. Using such assumptions produces software that is unnecessarily limited.

One of the more common examples of this problem is color software, like paint programs, that will not run unless a color monitor is the main display device. Imagine the frustration such a program causes the Macintosh owner who has two monitors -- a large black-and-white one as the main screen, and a color display on the side.

The main screen on a Macintosh is the screen that is used to emulate the old, single, rectangular display. Associated concepts are also supported by this main screen, such as the QuickDraw global variable screenBits, which is supposed to contain the BitMap that defines the display. Except for certain hacks put in by Apple, programs that use screenBits find themselves restricted to a single screen when several might be available.

Again, this limitation isn't so bad for older software, or software that needs to run the same way on all models of the Macintosh. But programs written to know about Color QuickDraw could go one step further and know about multiple display devices. And programs that require Color QuickDraw to run have no excuse for not taking advantage of all of Color QuickDraw's features.

As a rule, programs that utilize the newest routines and data structures have greater functionality and flexibility. These programs also have a longer life expectancy, as older features will not be supported forever. They also prevent headaches for users who don't understand why the menu bar has to be on a particular screen for some programs to work.

Handling Multiple Monitors

As you can probably see by now, an important feature of a color-smart program is the ability to handle multiple monitors. The functionality required by your program may be available on a screen other than the main one. Instead of checking just the main screen for a particular configuration, such as pixel depth, a program should check all available screens, or better yet, let the user decide which screen to use.

If your window is displayed on a screen that can't support the program's features, it is better to disable the use of those features than to unconditionally exit the program. The user might change the situation at least two ways: By dragging the window to a different monitor, or by changing the screen attributes with the Control Panel. The program should explain to the user why features are disabled, and should suggest possible solutions.

In addition to your program's environmental needs, the use of multiple monitors affects the question of screen real estate. Historically, the size of the screen would dictate the limits for dragging and sizing windows. The common practice was to set these limits to an approximation of the screenBits .bounds rectangle.

Because the screenBits variable represents only the main screen, the user of a program developed before the advent of the Macintosh II would have been unable to drag windows to other monitors. Fortunately, Apple put in a hack that allows windows to be dragged onto other displays.

Changing the size of a window is another matter. In most cases, a 9-inch monitor was not large enough to display an entire document, so limiting the size of a window to the size of the screen was not a problem. With the proliferation of large monitors, though, windows could easily become larger than their documents. This resulted in garbled displays and sometimes in crashes. In other cases, programs benefited from having as large a window as they could get.

And so the relationship between window contents and window size must be considered much more carefully than that between window contents and window position. The limit you set for the size of a window should be based solely on the nature of the document in the window. Windows set up this way will be able to span multiple monitors when appropriate. For the user who purchases a second or larger screen, the benefit will be automatic.

High-Level Calls and Data Structures

Apple has defined high-level calls and documented data structures for determining the characteristics of display devices. You should take advantage of these resources.

The linked list of display devices (called "GDevices") can be accessed and traversed with the GetDeviceList and GetNextDevice calls. You can use the call GetMainDevice to find the particular element of this list that represents the main screen. Because each GDevice references a PixMap, you can determine the current color settings of each monitor. Each GDevice also contains a rectangle that represents that monitor's global position.

With this information, your program can locate the monitor which best supports your program's requirements. You may, for example, wish to open new windows on the most appropriate monitor. (An example of this technique is built into the program presented later in this article.)

Handling Special Cases

In addition to increased functionality, programs that know about their environment can provide greater efficiency and better-looking output.

Images designed to look great on an 8-bit color display don't always come out as nicely when mapped by QuickDraw to a color device of lower resolution, or to a black-and-white device. And color mapping incurs an overhead that can be detrimental to high-performance programs. Frequently, however, you can make the same images look just as good at lower color resolutions, either by using a different set of colors, or by using patterns.

In general, software will work fine using a pure color model, and letting QuickDraw do the best rendering possible on each device, but there is an alternative if you wish to go that extra mile for appearance and performance. In addition to imaging for the generic case, you can deal with special cases. Performance and output will generally be enhanced for each special case, although the amount of improvement will vary, depending on the application.

The best way to apply this technique is to use a generic case that extends to the limits defined by ColorQuickDraw. In other words, use as many colors as are appropriate for the application, and specify each color by using the full 48 bits available in the RGBColordata structure. Then handle the common special cases of lower functionality, such as 8-bit color, 4-bit color, and black-and-white.

It is important to deal with the high-end generic situation. After your program has been written, more sophisticated hardware and software are bound to come along. Even though it seemed superfluous to draw with more than 256 colors when the Mac II first came out, the programs that did now render full-color images using Apple's 32-bit QuickDraw.

In order to deal with special cases, your program must know about the devices to which it will be drawing. The program can traverse the list of GDevices and compare their locations to the global coordinates of your window. Once the GDevice that contains your window is found, the GDevice's PixMap will let the program decide which, if any, special case to use.

A given window may intersect more than one display, and each display might be set to a different configuration. When traversing the device list, the program should treat each device window intersection as a separate case.

Even if a program is running on a Macintosh that has a single monitor, the user can change the color environment at any time with the Control Panel. Such changes may or may not be of concern to your program. If you use the technique just described for all of your drawing, then everything will work automatically, because this technique requires that you can actually draw something meaningful on each device, no matter what its color settings. An update event will be generated when the color environment changes, which will prompt the program to execute its GDevice intersection and drawing loop.

If your program requires a certain minimum color environment, you should check to see if the color environment has changed whenever your program gets an update event. This test is easy, because the ctSeed field in the color table of the GDevice's PixMap will change if that GDevice's color environment changes. If the ctSeed has changed, you can then check other values, like the pixel depth, to see if you should disable some features or just generate a new set of colors to use.

Using the Palette Manager

Your color programs should always use the Palette Manager. The days when the Palette Manager created more problems than it solved ended with the release of System 6.0.2. Now, as part of 32-bit QuickDraw, the Palette Manager has even more functionality and features that you will definitely want to use. In response to voluminous feedback, the Palette Manager has been extended to support the kinds of things that developers want to do.

For example, a single palette can now contain a different list of colors for each kind of device. This is exactly what is needed to support the technique described earlier for drawing the right thing to each GDevice.

Any old work-arounds that used to be required should be discarded. The support provided by the system via the Palette Manager should be used instead. Apple will continue to maintain the Palette Manager, but the same cannot be said about non-Apple work-arounds.

The ShowColors Program

The program ShowColors demonstrates some of the concepts described in this article. Listing One (page 57) gives the MPW Pascal version of this program, Listing Two (page 58) gives the MPW C version, and Listing Three (page 60) lists the Rez input for the application.

ShowColors is a color-smart application that displays something meaningful on any CLUT (color lookup table) or Fixed device. It doesn't crash when it encounters other types of devices. The display shown for each device is a representation of that device's color table. To create the display, the program uses the Palette Manager's pmExplicit entry type.

In order to show the right thing on each screen, the program uses the device window intersection loop. If the area of intersection is large enough, then the color table is drawn; otherwise, that portion of the window is left white. For displays that are not CLUT or Fixed-type devices, the window is painted with a 50 percent gray. When the program starts, it uses a simple algorithm to find what it considers to be the best device. The window is centered on that screen. Because this program has no fixed document size, the window can be made as large as desired.

Conclusion

I hope that this article has encouraged you to write programs that take full advantage of the ColorQuickDraw features. Your programs will live a longer life, and your users will appreciate the added functionality and flexibility.

_PROGRAMMING WITH COLOR QUICKDRAW_ by Chris DeRossi

[LISTING ONE]

<a name="027f_000c">

{[j=15-/40]}

PROGRAM ShowColors;

{ A color-smart application by Chris Derossi for Dr. Dobb's Journal.
  This nifty little utility displays the color table currently set for each
  display device. It uses a single window which can be moved onto any monitor,
  and grown to any size. The window is also allowed to lie across multiple
  monitors; each monitor-window intersection is drawn separately.
  If a particular device does not have a color table (it is not a
  CLUT or Fixed device), then that portion of the window is
  filled with 50% gray. If there is not enough room to display a
  device's entire color table, the window remains white.
  The window is initially made visible on what the program
  considers to be the best device for displaying color. }

USES
  Memtypes, OSIntf, ToolIntf, QuickDraw, Palettes;
  PROCEDURE _DataInit;   {Declared so we can reference it later.}
    EXTERNAL;
  CONST
    appleID      = 128;   {Standard Apple menu}
    fileID      = 129;   {File menu for Quit command}
    editID      = 130;   {Edit menu for DAs}
    appleM      = 1;
    fileM      = 2;
    editM      = 3;
    menuCount      = 3;
    kWindowID      = 128;   {The ID of our single window}
    kAboutMeDLOG   = 128;   {The About ShowColorsI Dialog}
    kNoColorID      = 129;   {The ID of the error alert}
    undoCommand    = 1;
    cutCommand      = 3;
    copyCommand    = 4;
    pasteCommand   = 5;
    clearCommand   = 6;
    aboutMeCommand = 1;     {About ShowColorsI item in the Apple menu}
    quitCommand    = 1;     {Quit command in the File menu}
    mBarHeight     = $BAA;
  TYPE
    IntPtr     = ^Integer;
  VAR
    myMenus     : ARRAY [1..menuCount] OF MenuHandle;
    dragRect     : Rect;
    newSize     : LongInt;
    doneFlag     : BOOLEAN;
    wRecord     : WindowRecord;
    myWindow     : WindowPtr;

{$S Initialize}
  PROCEDURE SetUpMenus;
    VAR
      i        : Integer;
    BEGIN
      myMenus[appleM] := GetMenu(appleID);
      AddResMenu(myMenus[appleM], 'DRVR');
      myMenus[fileM] := GetMenu(fileID);
      myMenus[editM] := GetMenu(editID);
      FOR i := 1 TO menuCount DO
   InsertMenu(myMenus[i], 0);
      DrawMenuBar;
    END;

{$S Initialize}
  FUNCTION FindBestDevice : GDHandle;
  { This function finds what it considers to be the best device from
    the list of screens connected to the Macintosh. For this program,
    best is considered to be more colors, and color is better than
    monochrome. The precise ordering of goodness is: 1-bit mono,
    2-bit mono, 2-bit color, 4-bit mono, 8-bit mono, 4-bit color,
    8-bit color. Non-CLUT or Fixed devices are not good at all for
    this program, even if they support more colors.
    To compare the devices, each device is assigned a rating. The
    higher the rating, the better the device. To convert a device's
    characteristics into a rating, we first convert the characteristics
    into a number, then use that number in a CASE statement to assign
    the rating. The mapping from characteristics to integer is as follows:
    bits 0..6 : pixel size = color depth  (enough bits to handle
    127 bits/pixel)
    bit  7 : 0 = monochrome, 1 = color. }
  VAR
    aDevice   : GDHandle;
    bestDevice   : GDHandle;
    aRating   : Integer;
    bestRating   : Integer;
  BEGIN
    bestRating := 0;
    aDevice := GetDeviceList;
    bestDevice := aDevice;  {In case we donUt find any good devices}
    WHILE aDevice <> NIL DO BEGIN
      IF (NOT TestDeviceAttribute(aDevice, screenActive)) OR
      ((aDevice^^.gdType <> clutType) AND (aDevice^^.gdType <> fixedType)) THEN
   aRating := 0
      ELSE
   CASE BAnd(aDevice^^.gdFlags, 1) * 128 + aDevice^^.gdPMap^^.pixelSize OF
     1   : aRating := 1;   {1-bit monochrome}
     129 : aRating := 2;   {1-bit color}
     2   : aRating := 3;   {2-bit monochrome}
     130 : aRating := 4;   {2-bit color}
     4   : aRating := 5;   {4-bit monochrome}
     8   : aRating := 6;   {8-bit monochrome}
     132 : aRating := 7;   {4-bit color}
     136 : aRating := 8;   {8-bit color}
   END;
      IF aRating > bestRating THEN BEGIN
   bestRating := aRating;
   bestDevice := aDevice;
      END;
      aDevice := GetNextDevice(aDevice);
    END;
    FindBestDevice := bestDevice;
  END;

{$S Main}
  FUNCTION PositionWindow(worldRect, windRect : Rect) : Rect;
  { This function centers the windRect over the worldRect in the horizontal
    direction, and places windRect one third of the way down over worldRect
    in the vertical direction. This positioned rectangle is then returned.}
  BEGIN
    OffsetRect(windRect, -windRect.left, -windRect.top);
    WITH worldRect DO
      OffsetRect(windRect, (right + left - windRect.right) DIV 2,
      (bottom - top - windRect.bottom) DIV 3 + top);
    PositionWindow := windRect;
  END;

{$S Initialize}
  PROCEDURE ShowColorsInit;
  { Initialize the standard Mac stuff and the application stuff. For the
    application, the window needs to be created and placed on the best
    available monitor.
    Since this program requires Color QuickDraw, we check for its presence with
    SysEnvirons before we try to open the window. If Color QuickDraw is not
    present, we set doneFlag to TRUE which causes the application to
    terminate right away.}
  VAR
    mySysStuff     : SysEnvRec;
    bestDevice     : GDHandle;
    aRect     : Rect;
    mbhPtr     : IntPtr;
    dummyItem     : Integer;
    myPalette     : PaletteHandle;
  BEGIN
    UnLoadSeg(@_DataInit);    {Get rid of MPWUs data initialization segment}
    MaxApplZone;
    InitGraf(@thePort);
    InitFonts;
    FlushEvents(everyEvent, 0);
    InitWindows;
    InitMenus;
    TEInit;
    InitDialogs(NIL);
    InitCursor;
   IF SysEnvirons(1, mySysStuff) = 0 THEN {Nothing} ;
   IF mySysStuff.hasColorQD THEN BEGIN
      SetUpMenus;
      { Get our window and create a palette for it. Our palette needs to have
        256 explicit entries. We don't care what the palette color entries
        are, so we can pass NIL as the color table handle to NewPalette. }
      myWindow := GetNewCWindow(kWindowID, @wRecord, Pointer(-1));
      myPalette := NewPalette(256, NIL, pmExplicit, 0);
      NSetPalette(myWindow, myPalette, pmAllUpdates);
      { Find the best screen for our window. The window is markes as
        invisible in the resource template so we can move it before we
        show it. }
      bestDevice := FindBestDevice;
      aRect := bestDevice^^.gdRect;       {Device's global rectangle}
      IF bestDevice = GetMainDevice THEN BEGIN    {Take menu bar into account.}
   mbhPtr := IntPtr(mBarHeight);       {Get ptr to low memory global}
   aRect.top := aRect.top + mbhPtr^;    {Adjust size of rectangle}
      END;
      aRect := PositionWindow(aRect, myWindow^.portRect);
      MoveWindow(myWindow, aRect.left, aRect.top, TRUE);
      ShowWindow(myWindow);
      SetPort(myWindow);
      doneFlag := FALSE;    {Will be set to true when user chooses Quit}
    END ELSE BEGIN
      dummyItem := StopAlert(kNoColorID, NIL);
      doneFlag := TRUE;
    END;
  END;

{$S Main}
  PROCEDURE ShowAboutMeDialog;
    VAR
      itemHit       : Integer;
      theDialog     : DialogPtr;
      savedPort     : GrafPtr;
      aRect       : Rect;
      mbhPtr       : IntPtr;
    BEGIN
      GetPort(savedPort);
      theDialog := GetNewDialog(kAboutMeDLOG, NIL, WindowPtr( - 1));
      SetPort(theDialog);
      aRect := screenBits.bounds;   {Main Device's global rectangle}
      mbhPtr := IntPtr(mBarHeight);
      aRect.top := aRect.top + mbhPtr^; {Adjust for the menu bar}
      aRect := PositionWindow(aRect, theDialog^.portRect);
      MoveWindow(theDialog, aRect.left, aRect.top, TRUE);
      ShowWindow(theDialog);
      REPEAT
   ModalDialog(NIL, itemHit)
      UNTIL (itemHit = ok);
      DisposDialog(theDialog);
      SetPort(savedPort);
    END;

{$S Main}
  PROCEDURE DrawWindowContents(aWindow : WindowPtr);
  { This is the procedure that loops through all of the screens and draws
    whatever is appropriate for that screen in the part of the window
    which intersects the screen. Each screen-window intersection is
    treated separately. }
  VAR
    grayRGB       : RGBColor;
    aDevice       : GDHandle;
    aRect       : Rect;
    globalWindRect  : Rect;
    mbhPtr       : IntPtr;
    workRect       : Rect;
    vCount       : Integer;
    hCount       : Integer;
    vBlockSize       : Integer;
    hBlockSize       : Integer;
    v          : Integer;
    h          : Integer;
  BEGIN
    { Create a 50% gray color for filling in non-clut/fixed devices }
    WITH grayRGB DO BEGIN
      red := $8000;
      green := $8000;
      blue := $8000;
    END;
    { Turn the window's portRect into global coordinates for intersecting
      the screens. }
    globalWindRect := aWindow^.portRect;
    LocalToGlobal(globalWindRect.topLeft);
    LocalToGlobal(globalWindRect.botRight);
    { Loop through all of the devices, seeing if we intersect each one }
    aDevice := GetDeviceList;
    WHILE aDevice <> NIL DO BEGIN
      aRect := aDevice^^.gdRect;
      IF aDevice = GetMainDevice THEN BEGIN  {Exclude menu bar from draw. area}
   mbhPtr := IntPtr(mBarHeight);   {Get a ptr to the low memory global}
   aRect.top := aRect.top + mbhPtr^; {Adjust size of working rectangle}
      END;
      IF SectRect(aRect, globalWindRect, workRect) THEN BEGIN
   GlobalToLocal(workRect.topLeft);
   GlobalToLocal(workRect.botRight);
   { Figure how many blocks to draw to show whole color table }
  IF (aDevice^^.gdType = clutType) OR (aDevice^^.gdType = fixedType) THEN BEGIN
     CASE aDevice^^.gdPMap^^.pixelSize OF
       1 : BEGIN
         vCount := 1;
         hCount := 2;
       END;
       2 : BEGIN
         vCount := 2;
         hCount := 2;
       END;
       4 : BEGIN
         vCount := 4;
         hCount := 4;
       END;
       8 : BEGIN
         vCount := 16;
         hCount := 16;
       END;
       OTHERWISE         { Uh oh. A pixel size that we canUt handle. }
         vCount := -1;   {Force the vBlockSize to be less than zero.}
     END;
     { Size of blocks be in the horizontal and vertical directions? }
     vBlockSize := (workRect.bottom - workRect.top) DIV vCount;
     hBlockSize := (workRect.right - workRect.left) DIV hCount;
     { Use the smaller dimension for both to keep the blocks square }
     IF vBlockSize < hBlockSize THEN
       hBlockSize := vBlockSize
     ELSE
       vBlockSize := hBlockSize;
     { If there is enough room to draw the color table, then do it. }
     IF (vBlockSize > 0) AND (hBlockSize > 0) THEN BEGIN
       FOR v := 0 TO vCount-1 DO
         FOR h := 0 TO hCount-1 DO BEGIN
      PMForeColor(v * hCount + h);
  SetRect(aRect, workRect.left + h * hBlockSize, workRect.top + v * vBlockSize,
   workRect.left + (h+1) * hBlockSize, workRect.top + (v+1) * vBlockSize);
      PaintRect(aRect);
         END;
     END;
   END ELSE BEGIN    { Not a CLUT or Fixed device. Draw gray on screen }
     RGBForeColor(grayRGB);
     PaintRect(workRect);
   END;
      END;
      aDevice := GetNextDevice(aDevice);
    END;
  END;

{$S Main}
  PROCEDURE DoCommand(mResult: LongInt);
    VAR
      theItem       : Integer;
      theMenu       : Integer;
      name       : Str255;
      temp       : Integer;
      dummyBool     : Boolean;
    BEGIN
      theItem := LoWord(mResult);
      theMenu := HiWord(mResult);
      CASE theMenu OF
   appleID:
     IF (theItem = aboutMeCommand) THEN
       ShowAboutMeDialog
     ELSE BEGIN
       GetItem(myMenus[appleM], theItem, name);
       temp := OpenDeskAcc(name);
       SetPort(myWindow);
     END;
   fileID:
     CASE theItem OF
       quitCommand : doneFlag := TRUE;
     END;
   editID:
     dummyBool := SystemEdit(theItem - 1);
      END;
      HiliteMenu(0);
    END;

{$S Main}
  PROCEDURE Mainloop;
  { This is the standard event polling procedure that finds out what needs to
    be done and handles the request or dispatches to the appropriate routine. }
  VAR
    dragRect     : Rect;
    newSize     : LongInt;
    theChar     : CHAR;
    myEvent     : EventRecord;
    whichWindow   : WindowPtr;
  BEGIN
    SystemTask;
    IF GetNextEvent(everyEvent, myEvent) THEN
      CASE myEvent.what OF
   mouseDown:
     CASE FindWindow(myEvent.where, whichWindow) OF
       inSysWindow: SystemClick(myEvent, whichWindow);
       inMenuBar: DoCommand(MenuSelect(myEvent.where));
       inDrag: BEGIN
    { If the boundsRect parameter passed to DragWindow looks like it was
           derived from screenBits.bounds, DragWindow will substitute a region
      which represents active screens. This is what we want, since
      we can't pass a region to DragWindow, we have to rely on this hack.
           That's why we make dragRect equal to screenBits.bounds. }
         dragRect := screenBits.bounds;
         DragWindow(whichWindow, myEvent.where, dragRect);
         { Because dragging window may change the device-window
      intersections, we force the window to be redrawn completely.
                This causes a redraw even when the window is moved only a
                little bit on the same screen. A little more intelligence
                could be added here to avoid unneeded updating. That's left
                for the reader. }
         InvalRect(myWindow^.portRect);
       END;
       inGrow: BEGIN
         SetRect(dragRect, 32, 32, 32766, 32766);
         newSize := GrowWindow(whichWindow, myEvent.where, dragRect);
         IF LongInt(newSize) <> 0 THEN BEGIN
          SizeWindow(whichWindow, LoWord(newSize), HiWord(newSize), TRUE);
          InvalRect(myWindow^.portRect);
         END;
       END;
       inContent: BEGIN
         IF whichWindow <> FrontWindow THEN
      SelectWindow(whichWindow);
       END;
     END; {of mouseDown case}
   keyDown, autoKey:
     IF myWindow = FrontWindow THEN BEGIN
       theChar := CHR(BAnd(myEvent.message, charCodeMask));
       IF BAnd(myEvent.modifiers, cmdKey) <> 0 THEN
         DoCommand(MenuKey(theChar));
     END;
   activateEvt:
     IF WindowPtr(myEvent.message) = myWindow THEN BEGIN
       IF BAnd(myEvent.modifiers, activeFlag) <> 0 THEN BEGIN
         DisableItem(myMenus[editM], 0);
       END ELSE BEGIN
         EnableItem(myMenus[editM], 0);
       END;
       DrawMenuBar;
     END;
   updateEvt:
     IF WindowPtr(myEvent.message) = myWindow THEN BEGIN
       BeginUpdate(myWindow);
       EraseRect(myWindow^.portRect);
       DrawWindowContents(myWindow);
       EndUpdate(myWindow);
     END;
      END; {of myEvent.what cases}
  END;

{$S Main}
BEGIN {ShowColors}
  ShowColorsInit;
  WHILE NOT doneFlag DO
    Mainloop;
END.


<a name="027f_000d"><a name="027f_000d">
<a name="027f_000e">
[LISTING TWO]
<a name="027f_000e">

/*
 *  ShowColors - A sample color-smart application
 *  by Chris Derossi for Dr. Dobb's Journal. MPW C Version.
 *  This nifty little utility displays the color table currently set for each
 *  display device. Uses a single window which can be moved onto any monitor,
 *  and grown to any size. The window is also allowed to lie across multiple
 *  monitors; each monitor-window intersection is drawn separately.
 *  If a particular device does not have a color table (not a CLUT or Fixed
 *  device), then that part of the window is filled with 50% gray. If there is
 *  not enough room to display a deviceUs entire color table, window remains
 *  white.
 *  Window is initially made visible on what the program considers to be the
 *  best device for displaying color.
 */

#include  <types.h>
#include  <memory.h>
#include  <events.h>
#include  <osevents.h>
#include  <desk.h>
#include  <toolutils.h>
#include  <osutils.h>
#include  <menus.h>
#include  <windows.h>
#include  <dialogs.h>
#include  <Resources.h>
#include  <QuickDraw.h>
#include  <Fonts.h>
#include  <Palettes.h>

#define appleID   128
#define fileID     129
#define editID     130
#define appleM     0
#define fileM   1
#define editM   2
#define menuCount 3
#define kWindowID 128
#define kAboutMeDLOG  128
#define kNoColorID  129
#define aboutMeCommand   1
#define quitCommand 1
#define MBarHeight  (*((short *) 0xBAA))

MenuHandle    myMenus[menuCount];
Rect         dragRect;
long         newSize;
Boolean       doneFlag;
WindowRecord  wRecord;
WindowPtr     myWindow;

void SetUpMenus()

{
  short   i;
  myMenus[appleM] = GetMenu(appleID);
  AddResMenu(myMenus[appleM], 'DRVR');
  myMenus[fileM] = GetMenu(fileID);
  myMenus[editM] = GetMenu(editID);
  for (i = 0; i < menuCount; i++)
    InsertMenu(myMenus[i], 0);
  DrawMenuBar();
}

GDHandle FindBestDevice()
/*  This function finds what the best device from the list of
 *  screens connected to the Macintosh. For this program, best is considered
 *  more colors, and color is better than monochrome. The ordering of goodness
 *  is: 1-bit mono, 2-bit mono, 2-bit color, 4-bit mono, 8-bit mono,
 *  4-bit color, 8-bit color. Non-CLUT or Fixed devices are not good at all
 *  for this program, even if they support more colors.
 *  To compare the devices, each device is assigned a rating. The higher the
 *  rating, the better the device. To convert a device's characteristics into
 *  a rating, first convert the characteristics into a number, then use that
 *  number in a CASE statement to assign the rating. The mapping from
 *  characteristics to integer is as follows:
 *  bits 0..6 : pixel size = color depth  (enough bits to handle 127
 *  bits/pixel);  bit  7 : 0 = monochrome, 1 = color.
 */
{

  GDHandle  aDevice;
  GDHandle  bestDevice;
  short     aRating;
  short     bestRating;

  aDevice = bestDevice = GetDeviceList();
  bestRating = 0;
 while (aDevice) {
 if ((!TestDeviceAttribute(aDevice, screenActive)) ||
       (((**aDevice).gdType != clutType) && ((**aDevice).gdType != fixedType)))
      aRating = 0;
 else
  switch (((**aDevice).gdFlags & 1) * 128 + (**(**aDevice).gdPMap).pixelSize) {
   case 1:
     aRating = 1;     // 1-bit monochrome
     break;
   case 129:
     aRating = 2;     // 1-bit color
     break;
   case 2:
     aRating = 3;     // 2-bit monochrome
     break;
   case 130:
     aRating = 4;     // 2-bit color
     break;
   case 4:
     aRating = 5;     // 4-bit monochrome
     break;
   case 8:
     aRating = 6;     // 8-bit monochrome
     break;
   case 132:
     aRating = 7;     // 4-bit color
     break;
   case 136:
     aRating = 8;     // 8-bit color
     break;
      }
    if (aRating > bestRating) {
      bestRating = aRating;
      bestDevice = aDevice;
    }
    aDevice = GetNextDevice(aDevice);
  }

  return(bestDevice);
}

void PositionWindow(worldRect, windRect, resultRect)
  Rect   worldRect;
  Rect   windRect;
  Rect   *resultRect;

/*  This function centers the windRect over the worldRect in the horizontal
 *  direction, and places windRect one third of the way down over worldRect
 *  in the vertical direction.
 */

{
  *resultRect = windRect;
OffsetRect(resultRect, -resultRect->left, -resultRect->top);
OffsetRect(resultRect,(worldRect.right + worldRect.left - resultRect->right)/2,
  (worldRect.bottom - worldRect.top - resultRect->bottom) / 3 + worldRect.top);
}

void ShowColorsInit() {
/*  Initialize the standard Mac and the application stuff. For the application,
 *  the window needs to be created and placed on the RbestS available monitor.
 *  Since this program requires Color QuickDraw, we check for its presence with
 *  SysEnvirons before opening window. If Color QuickDraw is not present,
 *  set doneFlag to TRUE which causes the application to terminate right away.
 */

  SysEnvRec   mySysStuff;
  GDHandle   bestDevice;
  Rect      aRect;
  PaletteHandle myPalette;

  MaxApplZone();
  InitGraf(&qd.thePort);
  InitFonts();
  FlushEvents(everyEvent, 0);
  InitWindows();
  InitMenus();
  TEInit();
  InitDialogs(nil);
  InitCursor();

  SysEnvirons(1, &mySysStuff);
  if (mySysStuff.hasColorQD) {   // We're in good shape. Setup everything.
    SetUpMenus();

    /* Get window and create a palette. Our palette needs to have 256 explicit
      entries. Don't care what palette color entries are, so we can pass NIL
      as the color table handle to NewPalette. */
    myWindow = GetNewCWindow(kWindowID, (Ptr)&wRecord, (WindowPtr)-1);
    myPalette = NewPalette(256, nil, pmExplicit, 0);
    NSetPalette(myWindow, myPalette, pmAllUpdates);

    /* Find best screen for window. The window is markes as invisible in the
      resource template so we can move it before we show it. */
    bestDevice = FindBestDevice();
    aRect = (**bestDevice).gdRect;   // Device's global rectangle
    if (bestDevice == GetMainDevice())     // Take menu bar into account.
      aRect.top += MBarHeight;       // Adjust size of working rectangle
    PositionWindow(aRect, myWindow->portRect, &aRect);
    MoveWindow(myWindow, aRect.left, aRect.top, true);
    ShowWindow(myWindow);
    SetPort(myWindow);

    doneFlag = false;   // Will be set to true when the user chooses Quit
  } else {
    StopAlert(kNoColorID, nil);
    doneFlag = true;
  }
}

void ShowAboutMeDialog()
{
  short     itemHit;
  DialogPtr theDialog;
  GrafPtr   savedPort;
  Rect       aRect;
  GetPort(&savedPort);
  theDialog = GetNewDialog(kAboutMeDLOG, nil, (WindowPtr)-1);
  SetPort(theDialog);
  aRect = qd.screenBits.bounds;    // Main Device's global rectangle
  aRect.top += MBarHeight;       // Adjust for the menu bar
  PositionWindow(aRect, theDialog->portRect, &aRect);
  MoveWindow(theDialog, aRect.left, aRect.top, true);
  ShowWindow(theDialog);
  do
    ModalDialog(nil, &itemHit);
  while (itemHit != ok);
  DisposDialog(theDialog);
  SetPort(savedPort);
}

void DrawWindowContents(aWindow)
  WindowPtr   aWindow;
/*  The procedure that loops through all of the screens and draws whatever is
 *  appropriate for that screen. Each screen-window intersection is treated
 *  separately.
 */
{
  RGBColor    grayRGB;
  GDHandle    aDevice;
  Rect         aRect;
  Rect         globalWindRect;
  Rect         workRect;
  short       vCount;
  short       hCount;
  short       vBlockSize;
  short       hBlockSize;
  short       v;
  short       h;

  // Create a 50% gray color for filling in non-clut/fixed devices
  grayRGB.red = 0x8000;
  grayRGB.green = 0x8000;
  grayRGB.blue = 0x8000;

  // Turn window's portRect into global coordinates for intersecting screens.
  globalWindRect = aWindow->portRect;
  LocalToGlobal((Point *)&globalWindRect.top);
  LocalToGlobal((Point *)&globalWindRect.bottom);

  // Loop through all of the devices, seeing if we intersect each one.
  aDevice = GetDeviceList();
  while (aDevice) {
    aRect = (**aDevice).gdRect;
    if (aDevice == GetMainDevice())   // Exclude menu bar from drawable area
      aRect.top += MBarHeight;      // Adjust size of working rectangle
    if (SectRect(&aRect, &globalWindRect, &workRect)) { // Window intersects.
      GlobalToLocal((Point *)&workRect.top);
      GlobalToLocal((Point *)&workRect.bottom);
   // Figure out how many blocks to draw to show the whole color table
   if (((**aDevice).gdType == clutType) || ((**aDevice).gdType == fixedType)) {
   switch ((**(**aDevice).gdPMap).pixelSize) {
     case 1:
       vCount = 1;
       hCount = 2;
       break;
     case 2:
       vCount = hCount = 2;
       break;
     case 4:
       vCount = hCount = 4;
       break;
     case 8:
       vCount = hCount = 16;
       break;
     default:     // Uh oh. A pixel size that we canUt handle.
       vCount = -1;  // Will force the vBlockSize to be less than zero.
   }
   // How big will the blocks be in horizontal and vertical directions?
   vBlockSize = (workRect.bottom - workRect.top) / vCount;
   hBlockSize = (workRect.right - workRect.left) / hCount;
   // Use the smaller dimension for both to keep the blocks square
   if (vBlockSize < hBlockSize)
     hBlockSize = vBlockSize;
   else
     vBlockSize = hBlockSize;
   // If there is enough room to draw the color table, then do it.
   if ((vBlockSize > 0) && (hBlockSize > 0)) {
     for (v = 0; v < vCount; v++)
       for (h = 0; h < hCount; h++) {
         PmForeColor(v * hCount + h);
         SetRect(&aRect, workRect.left + h * hBlockSize,
            workRect.top + v * vBlockSize,
            workRect.left + (h+1) * hBlockSize,
            workRect.top + (v+1) * vBlockSize);
         PaintRect(&aRect);
       }
   }
      } else {       // Not a CLUT or Fixed device. Draw gray on this screen.
   RGBForeColor(&grayRGB);
   PaintRect(&workRect);
      }
    }
    aDevice = GetNextDevice(aDevice);
  }
}

void DoCommand(mResult)
  long     mResult;
{
  short   theItem;
  short   theMenu;
  char     name[256];
  theItem = LoWord(mResult);
  theMenu = HiWord(mResult);
  switch (theMenu) {
    case appleID:
      if (theItem == aboutMeCommand)
   ShowAboutMeDialog();
      else {
   GetItem(myMenus[appleM], theItem, name);
   OpenDeskAcc(name);
   SetPort(myWindow);
      }
      break;
    case fileID:
      doneFlag = true;
      break;
    case editID:
      SystemEdit(theItem - 1);
      break;
  }
  HiliteMenu(0);
}

void Mainloop()
/*  Standard event polling procedure that finds out what needs to be done
 *  and handles the request or dispatches to the appropriate routine.
 */
{

  Rect         dragRect;
  long         newSize;
  char         theChar;
  EventRecord myEvent;
  WindowPtr   whichWindow;
  SystemTask();
  if (GetNextEvent(everyEvent, &myEvent))
    switch (myEvent.what) {
      case mouseDown:
   switch (FindWindow(myEvent.where, &whichWindow)) {
     case inSysWindow:
       SystemClick(&myEvent, whichWindow);
       break;
     case inMenuBar:
       DoCommand(MenuSelect(myEvent.where));
       break;
     case inDrag:
       // If boundsRect parameter passed to DragWindow looks like it was
       // derived from screenBits.bounds, DragWindow substitutes region
       // which represents all of the active screens. We want this, since
       // we can't pass a region to DragWindow, we have to rely on hack.
            // That's why we make dragRect equal to screenBits.bounds.
       dragRect = qd.screenBits.bounds;
       DragWindow(whichWindow, myEvent.where, &dragRect);
       // Because dragging the window may change the device-window
       // intersections, window is redrawn completely. This causes
       // a redraw even when the window is moved on the same
       // screen. More intelligence could be added here to avoid unneeded
       // updating. That's left for the reader.
       InvalRect(&myWindow->portRect);
       break;
     case inGrow:
       SetRect(&dragRect, 32, 32, 32766, 32766);
       newSize = GrowWindow(whichWindow, myEvent.where, &dragRect);
       if (newSize) {
         SizeWindow(whichWindow, LoWord(newSize), HiWord(newSize), true);
         InvalRect(&myWindow->portRect);
       }
       break;
     case inContent:
       if (whichWindow != FrontWindow())
         SelectWindow(whichWindow);
       break;
   } // end of mouseDown case
      case keyDown:
      case autoKey:
   if (myWindow == FrontWindow()) {
     theChar = (myEvent.message & charCodeMask);
     if (myEvent.modifiers & cmdKey)
       DoCommand(MenuKey(theChar));
   }
   break;
      case activateEvt:
   if (myEvent.message == myWindow) {
     if (myEvent.modifiers & activeFlag) {
       DisableItem(myMenus[editM], 0);
     } else {
       EnableItem(myMenus[editM], 0);
     }
     DrawMenuBar();
   }
   break;
      case updateEvt:
   if (myEvent.message == myWindow) {
     BeginUpdate(myWindow);
     EraseRect(&myWindow->portRect);
     DrawWindowContents(myWindow);
     EndUpdate(myWindow);
   }
   break;
    }   // end of myEvent.what cases
}

void main()
{
  ShowColorsInit();
  while (!doneFlag)
    Mainloop();
}


<a name="027f_000f"><a name="027f_000f">
<a name="027f_0010">
[LISTING THREE]
<a name="027f_0010">

/*  ShowColors.r - Rez source for the color-smart program
 * by Chris Derossi for Dr. Dobb's Journal
 */

 #include "Types.r"

/* These define's are used in the MENU resources to disable specific
   menu items. */
#define AllItems    0b1111111111111111111111111111111   /* 31 flags */
#define MenuItem1   0b00001
#define MenuItem2   0b00010

type 'ShCo' as 'STR ';

resource 'ShCo' (0) {
    "ShowColors Application. Copyright ) 1989 Chris Derossi"
};

resource 'WIND' (128, "Colors Window") {
    {40, 10, 200, 170},
    documentProc,
    invisible,
    noGoAway,
    0x0,
    "Colors Window"
};

resource 'DLOG' (128, purgeable) {
    {40, 40, 200, 340},
    altDBoxProc,
    invisible,
    noGoAway,
    0x0,
    128,
    "About ShowColorsI Dialog"
};

resource 'DITL' (128, purgeable) {
    {   /* array DITLarray: 4 elements */
   /* [1] */
   {0, 0, 160, 300},
   UserItem {
       enabled
   },
   /* [2] */
   {17, 4, 37, 294},
   StaticText {
       disabled,
       "ShowColors Color-Smart Sample Application"
   },
   /* [3] */
   {113, 24, 132, 277},
   StaticText {
       disabled,
       "by Chris Derossi for Dr. DobbUs Journal"
   },
   /* [4] */
   {58, 129, 90, 161},
   Icon {
       disabled,
       128
   }
    }
};

resource 'ALRT' (129) {
    {36, 52, 114, 320},
    129,
    {   /* array: 4 elements */
   /* [1] */
   OK, visible, sound1,
   /* [2] */
   OK, visible, sound1,
   /* [3] */
   OK, visible, sound1,
   /* [4] */
   OK, visible, sound1
    }
};

resource 'DITL' (129) {
    {   /* array DITLarray: 2 elements */
   /* [1] */
   {50, 201, 70, 261},
   Button {
       enabled,
       "Okay"
   },
   /* [2] */
   {8, 87, 44, 261},
   StaticText {
       enabled,
       "ShowColors requires Color QuickDraw to be present."
   }
    }
};

resource 'MENU' (128, "Apple", preload) {
    128, textMenuProc,
    AllItems & ~MenuItem2,  /* Disable item #2 */
    enabled, apple,
    {
   "About ShowColorsI",
       noicon, nokey, nomark, plain;
   "-",
       noicon, nokey, nomark, plain
    }
};

resource 'MENU' (129, "File", preload) {
    129, textMenuProc,
    AllItems,
    enabled, "File",
    {
   "Quit",
       noicon, "Q", nomark, plain
    }
};

resource 'MENU' (130, "Edit", preload) {
    130, textMenuProc,
    AllItems & ~(MenuItem2),   /* Disable item #2 */
    enabled, "Edit",
     {
   "Undo",
       noicon, "Z", nomark, plain;
   "-",
       noicon, nokey, nomark, plain;
   "Cut",
       noicon, "X", nomark, plain;
   "Copy",
       noicon, "C", nomark, plain;
   "Paste",
       noicon, "V", nomark, plain;
   "Clear",
       noicon, nokey, nomark, plain
    }
};

resource 'ICON' (128) {
    $"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
    $"00 00 00 00 00 00 00 00 00 00 00 00 7F FF 00 00"
    $"80 00 80 00 80 00 80 00 FF FF 80 00 AA AA 80 00"
    $"D5 55 80 00 AA AA 80 00 D5 FF 80 00 AB 00 80 00"
    $"D5 FF 80 00 AB 00 80 00 D5 00 BF FE AB 00 C0 6B"
    $"D5 00 C0 55 AB 00 C0 6B D5 00 C0 55 AB 00 C0 6B"
    $"D5 00 C0 55 AB 00 C0 6B D5 00 C0 55 AB 00 C0 6B"
    $"D5 FF FF D5 AA AA EA AB D5 55 D5 55 7F FF 3F FE"
};

resource 'ICN#' (128) {
    {   /* array: 2 elements */
   /* [1] */
   $"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
   $"00 00 00 00 00 00 00 00 00 00 00 00 7F FF 00 00"
   $"80 00 80 00 80 00 80 00 FF FF 80 00 AA AA 80 00"
   $"D5 55 80 00 AA AA 80 00 D5 FF 80 00 AB 00 80 00"
   $"D5 FF 80 00 AB 00 80 00 D5 00 BF FE AB 00 C0 6B"
   $"D5 00 C0 55 AB 00 C0 6B D5 00 C0 55 AB 00 C0 6B"
   $"D5 00 C0 55 AB 00 C0 6B D5 00 C0 55 AB 00 C0 6B"
   $"D5 FF FF D5 AA AA EA AB D5 55 D5 55 7F FF 3F FE",
   /* [2] */
   $"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
   $"00 00 00 00 00 00 00 00 00 00 00 00 7F FF 00 00"
   $"FF FF 80 00 FF FF 80 00 FF FF 80 00 FF FF 80 00"
   $"FF FF 80 00 FF FF 80 00 FF FF 80 00 FF FF 80 00"
   $"FF FF 80 00 FF FF 80 00 FF FF BF FE FF FF FF FF"
   $"FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF"
   $"FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF"
   $"FF FF FF FF FF FF FF FF FF FF FF FF 7F FF 3F FE"
    }
};

resource 'BNDL' (128) {
    'ShCo',
    0,
    {
   'ICN#', {0, 128},
   'FREF', {0, 128}
    }
};

resource 'FREF' (128) {
    'APPL',
    0,
    ""
};

resource 'SIZE' (-1, purgeable) {
   dontSaveScreen,
   ignoreSuspendResumeEvents,
   enableOptionSwitch,
   cannotBackground,
   notMultiFinderAware,
   backgroundAndForeground,
   dontGetFrontClicks,
   ignoreChildDiedEvents,
   not32BitCompatible,
   reserved,
   reserved,
   reserved,
   reserved,
   reserved,
   reserved,
   reserved,
   50 * 1024,
   50 * 1024
};









Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.