| Demonstration Program  // ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// PreQuickDraw.c
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// 
// This program opens a window in which is displayed some information extracted from
// the GDevice structure for the main video device and some colour information extracted
// from the window's colour graphics port structure.  When the monitor is set to 256 
// colours or less, the colours in the colour table in the GDevice structure's pixel map
// structure are also displayed.
//
// A Demonstration menu, which is enabled if the monitor is a direct device set to 256
// colours or less at program start, allows the user to set the monitor to 16-bit colour,
// and restore the original pixel depth, using application-defined functions. 
//
// The program utilises 'MBAR', 'MENU', 'WIND', and 'STR#' resources, and a 'SIZE' 
// resource with the is32BitCompatible flag set.
//
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// ............................................................................. includes
#include <Appearance.h>
#include <Devices.h>
#include <Palettes.h>
#include <LowMem.h>
#include <Sound.h>
#include <ToolUtils.h>
// .............................................................................. defines
#define rMenubar              128
#define rWindow               128
#define mApple                128
#define  iAbout               1
#define mFile                 129
#define  iQuit                11
#define mDemonstration        131
#define  iSetDepth            1
#define  iRestoreDepth        2
#define rIndexedStrings       128
#define  sMonitorInadequate   1
#define  sSettingPixelDepth16 2
#define  sMonitorIsDepth16    3  
#define  sMonitorIsDepthStart 4 
#define  sRestoringMonitor    5
#define MAXLONG               0x7FFFFFFF
#define topLeft(r)            (((Point *) &(r))[0])
#define botRight(r)           (((Point *) &(r))[1])
// ..................................................................... global variables
Boolean  gDone;
SInt16   gStartupPixelDepth;
// .................................................................. function prototypes
void    main                        (void);
void    doInitManagers              (void);
void    doEvents                    (EventRecord *);
void    doDisplayInformation        (WindowPtr);
Boolean doCheckMonitor             (void);
void    doSetMonitorPixelDepth      (void);
void    doRestoreMonitorPixelDepth  (void);
void    doMonitorAlert              (Str255);
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× main
void  main(void)
{
  Handle        menubarHdl;
  MenuHandle    menuHdl;
  WindowPtr      windowPtr;
  Str255        theString;
  EventRecord    EventStructure;
  // ................................................................ initialise managers
  doInitManagers();
  
  // .......................................................... set up menu bar and menus
  
  menubarHdl = GetNewMBar(rMenubar);
  if(menubarHdl == NULL)
    ExitToShell();
  SetMenuBar(menubarHdl);
  menuHdl = GetMenuHandle(mApple);
  if(menuHdl == NULL)
    ExitToShell();
  else
    AppendResMenu(menuHdl,'DRVR');
  if(!(doCheckMonitor()))
  {
    GetIndString(theString,rIndexedStrings,sMonitorInadequate);
    doMonitorAlert(theString);
    menuHdl = GetMenuHandle(mDemonstration);
    DisableItem(menuHdl,0);
  }
  else
  {
    if(gStartupPixelDepth > 8)
    {
      menuHdl = GetMenuHandle(mDemonstration);
      DisableItem(menuHdl,0);
    }
  }
        
  DrawMenuBar();
  
  // ............................ open windows, set font size, show windows, move windows
  if(!(windowPtr = GetNewCWindow(rWindow,NULL,(WindowPtr)-1)))
    ExitToShell();
  SetPort(windowPtr);
  TextSize(10);
  // .................................................................... enter eventLoop
  gDone = false;
  while(!gDone)
  {
    if(WaitNextEvent(everyEvent,&EventStructure,MAXLONG,NULL))
      doEvents(&EventStructure);
  }
}
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doInitManagers
void  doInitManagers(void)
{
  MaxApplZone();
  MoreMasters();
  InitGraf(&qd.thePort);
  InitFonts();
  InitWindows();
  InitMenus();
  TEInit();
  InitDialogs(NULL);
  InitCursor();  
  FlushEvents(everyEvent,0);
  RegisterAppearanceClient();
}
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doEvents
void  doEvents(EventRecord *eventStrucPtr)
{
  SInt8     charCode;
  SInt32    menuChoice;
  SInt16    menuID, menuItem;
  SInt16    partCode;
  WindowPtr windowPtr;
  Str255    itemName;
  SInt16    daDriverRefNum;
  Rect      theRect;
  
  switch(eventStrucPtr->what)
  {
    case keyDown:
    case autoKey:
      charCode = eventStrucPtr->message & charCodeMask;
      if((eventStrucPtr->modifiers & cmdKey) != 0)
      {
        menuChoice = MenuEvent(eventStrucPtr);
        menuID = HiWord(menuChoice);
        menuItem = LoWord(menuChoice);
        if(menuID == mFile && menuItem  == iQuit)
          gDone = true;
      }
      break;
  
    case mouseDown:
      if(partCode = FindWindow(eventStrucPtr->where,&windowPtr))
      {
        switch(partCode)
        {
          case inMenuBar:
            menuChoice = MenuSelect(eventStrucPtr->where);
            menuID = HiWord(menuChoice);
            menuItem = LoWord(menuChoice);
            if(menuID == 0)
              return;
            switch(menuID)
            {
              case mApple:
                if(menuItem == iAbout)
                  SysBeep(10);
                else
                {
                  GetMenuItemText(GetMenuHandle(mApple),menuItem,itemName);
                  daDriverRefNum = OpenDeskAcc(itemName);
                }
                break;
              case mFile:
                if(menuItem == iQuit)
                  gDone = true;
                break;
  
              case mDemonstration:
                if(menuItem == iSetDepth)
                  doSetMonitorPixelDepth();
                else if(menuItem == iRestoreDepth)
                  doRestoreMonitorPixelDepth();
                break;
            }
            HiliteMenu(0);
            break;
          
          case inDrag:
            DragWindow(windowPtr,eventStrucPtr->where,&qd.screenBits.bounds);
            theRect = windowPtr->portRect;
            theRect.right = windowPtr->portRect.left + 250;
            InvalRect(&theRect);
            break;
        }
      }
      break;
      
    case updateEvt:
      windowPtr = (WindowPtr) eventStrucPtr->message;
      BeginUpdate(windowPtr);
      SetPort(windowPtr);
      EraseRect(&windowPtr->portRect);
      doDisplayInformation(windowPtr);
      EndUpdate(windowPtr);
      break;
  }
}
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doDisplayInformation
void  doDisplayInformation(WindowPtr windowPtr)
{
  RGBColor      whiteColour = { 0xFFFF, 0xFFFF, 0xFFFF };
  RGBColor      blueColour  = { 0x4444, 0x4444, 0x9999 };
  GDHandle      deviceHdl;
  SInt16        videoDeviceCount = 0;  
  Str255        theString;
  SInt16        deviceType, pixelDepth, bytesPerRow;
  Rect          theRect;
  PixMapHandle  pixMapHdl;
  CGrafPtr      cgrafPtr;
  SInt32        pixelValue;
  SInt16        redComponent, greenComponent, blueComponent;
  CTabHandle    colorTableHdl;
  SInt16        entries = 0, a, b, c = 0;
  RGBColor      theColour;
  RGBForeColor(&whiteColour);
  RGBBackColor(&blueColour);
  EraseRect(&windowPtr->portRect);
  // .................................................................... Get Device List
  deviceHdl = LMGetDeviceList();
  // ................................................. count video devices in device list
  while(deviceHdl != NULL)
  {
    if(TestDeviceAttribute(deviceHdl,screenDevice))
      videoDeviceCount ++;
    deviceHdl = GetNextDevice(deviceHdl);
  }
  NumToString((SInt32) videoDeviceCount,theString);
  MoveTo(10,20);
  DrawString(theString);
  if(videoDeviceCount < 2)
    DrawString("\p video device in the device list.");
  else
    DrawString("\p video devices in the device list.");
  // .................................................................... Get Main Device
  deviceHdl = LMGetMainDevice();
  // .............................................................. determine device type
  MoveTo(10,35);
  if(BitTst(&(**deviceHdl).gdFlags,15 - gdDevType))
    DrawString("\pThe main video device is a colour device.");
  else
    DrawString("\pThe main video device is a monochrome device.");
  MoveTo(10,50);
  deviceType = (**deviceHdl).gdType;
  switch(deviceType)
  {
    case clutType:
      DrawString("\pIt is an indexed device with variable CLUT.");
      break;
    case fixedType:
      DrawString("\pIt is is an indexed device with fixed CLUT.");
      break;
    case directType:
      DrawString("\pIt is a direct device.");
      break;
  }
  // ............................................................ Get Handle to Pixel Map
  pixMapHdl = (**deviceHdl).gdPMap;
  // .............................................................. determine pixel depth
  MoveTo(10,70);
  DrawString("\pPixel depth = ");
  pixelDepth = (**pixMapHdl).pixelSize;
  NumToString((SInt32) pixelDepth,theString);
  DrawString(theString);
  // ..................................................... Get Device's Global Boundaries
  theRect = (**deviceHdl).gdRect;
  // ................................ determine bytes per row and total pixel image bytes
  MoveTo(10,90);
  bytesPerRow = (**pixMapHdl).rowBytes & 0x7FFF;
  DrawString("\pBytes per row = ");
  NumToString((SInt32) bytesPerRow,theString);
  DrawString(theString);
  MoveTo(10,105);
  DrawString("\pTotal pixel image bytes = ");
  NumToString((SInt32) bytesPerRow * theRect.bottom,theString);
  DrawString(theString);
  // ................. convert device's global boundaries to coordinates of graphics port
  GlobalToLocal(&topLeft(theRect));
  GlobalToLocal(&botRight(theRect));
  
  MoveTo(10,125);
  DrawString("\pBoundary rectangle top = ");
  NumToString((SInt32) theRect.top,theString);
  DrawString(theString);
  MoveTo(10,140);
  DrawString("\pBoundary rectangle left = ");
  NumToString((SInt32) theRect.left,theString);
  DrawString(theString);
  MoveTo(10,155);
  DrawString("\pBoundary rectangle bottom = ");
  NumToString((SInt32) theRect.bottom,theString);
  DrawString(theString);
  MoveTo(10,170);
  DrawString("\pBoundary rectangle right = ");
  NumToString((SInt32) theRect.right,theString);
  DrawString(theString);
  // ................................................ Get Pointer to Colour Graphics Port
  cgrafPtr = (CGrafPtr) windowPtr;
  // .............................................. determine requested background colour
  MoveTo(10,190);
  GetBackColor(&blueColour);
  DrawString("\pRequested background colour (rgb) = ");;
  MoveTo(10,205);
  NumToString((SInt32) blueColour.red,theString);
  DrawString(theString);
  DrawString("\p  ");
  NumToString((SInt32) blueColour.green,theString);
  DrawString(theString);
  DrawString("\p  ");
  NumToString((SInt32) blueColour.blue,theString);
  DrawString(theString);
  // .................................................... get actual colour (pixel value)
  pixelValue = cgrafPtr->bkColor;
  // ...... if direct device, extract colour components, else retrieve colour table index 
  MoveTo(10,220);
  if(deviceType == directType)
  {
    if(pixelDepth == 16)
    {
      redComponent    = pixelValue >> 10 & 0x0000001F;
      greenComponent  = pixelValue >> 5 & 0x0000001F;
      blueComponent    = pixelValue & 0x0000001F;
    }
    else if (pixelDepth == 32)
    {
      redComponent    = pixelValue >> 16 & 0x000000FF;
      greenComponent  = pixelValue >> 8 & 0x000000FF;
      blueComponent    = pixelValue & 0x000000FF;
    }
    DrawString("\pBackground colour used (rgb) = ");
    MoveTo(10,235);
    
    NumToString((SInt32) redComponent,theString);
    DrawString(theString);    
    DrawString("\p  ");
    NumToString((SInt32) greenComponent,theString);
    DrawString(theString);    
    DrawString("\p  ");
    NumToString((SInt32) blueComponent,theString);
    DrawString(theString);    
  }
  else if(deviceType == clutType || deviceType == fixedType)
  {
    DrawString("\p Background colour used (color table index) = ");    
    MoveTo(10,235);
    NumToString((SInt32) pixelValue,theString);
    DrawString(theString);
  }
  // ......................................................... Get Handle to Colour Table
  colorTableHdl = (*pixMapHdl)->pmTable;
  // ................................... if any entries in colour table, draw the colours
  MoveTo(250,20);
  DrawString("\pColour table in GDevice's PixMap:");
  entries = (*colorTableHdl)->ctSize;
  if(entries < 2)
  {
    MoveTo(260,105);
    DrawString("\pDummy (one entry) colour table only.");
    MoveTo(260,120);
    DrawString("\pTo get some entries, set the monitor to");
    MoveTo(260,135);
    DrawString("\p 256 colours, causing it to act like an");
    MoveTo(260,150);
    DrawString("\p                    indexed device.");
    SetRect(&theRect,250,28,458,236);
    FrameRect(&theRect);
  }
  for(a=28;a<224;a+=13)
  {
    for(b=250;b<446;b+=13)
    {
      if(c > entries)
        break;
      SetRect(&theRect,b,a,b+12,a+12);
      theColour = (*colorTableHdl)->ctTable[c++].rgb;
      RGBForeColor(&theColour);
      PaintRect(&theRect);
      if((deviceType == clutType || deviceType == fixedType) && c - 1 == pixelValue)
      {
        RGBForeColor(&whiteColour);
        InsetRect(&theRect,-1,-1);
        FrameRect(&theRect);
      }
    }
  }
}
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doCheckMonitor
Boolean  doCheckMonitor(void)
{
  GDHandle      mainDeviceHdl;
  mainDeviceHdl = LMGetMainDevice();
  if(!(HasDepth(mainDeviceHdl,16,0,0)))
    return false;
  else
  {
    gStartupPixelDepth = (**((**mainDeviceHdl).gdPMap)).pixelSize;
    return true;
  }
}
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doSetMonitorPixelDepth
void  doSetMonitorPixelDepth(void)
{
  GDHandle  mainDeviceHdl;
  Str255    alertString;  
  SInt16    pixelDepth;
  
  mainDeviceHdl = LMGetMainDevice();
  pixelDepth = (**((**mainDeviceHdl).gdPMap)).pixelSize;
  if(pixelDepth != 16)
  {
    GetIndString(alertString,rIndexedStrings,sSettingPixelDepth16);
    doMonitorAlert(alertString);
    SetDepth(mainDeviceHdl,16,0,0);
  }
  else
  {
    GetIndString(alertString,rIndexedStrings,sMonitorIsDepth16);
    doMonitorAlert(alertString);
  }
}
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doRestoreMonitorPixelDepth
void  doRestoreMonitorPixelDepth(void)
{
  GDHandle  mainDeviceHdl;
  Str255    alertString;  
  SInt16    pixelDepth;
  mainDeviceHdl = LMGetMainDevice();
  pixelDepth = (**((**mainDeviceHdl).gdPMap)).pixelSize;
  if(pixelDepth != gStartupPixelDepth)
  {
    GetIndString(alertString,rIndexedStrings,sRestoringMonitor);
    doMonitorAlert(alertString);
    SetDepth(mainDeviceHdl,gStartupPixelDepth,0,0);
  }
  else
  {
    GetIndString(alertString,rIndexedStrings,sMonitorIsDepthStart);
    doMonitorAlert(alertString);
  }
}
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doMonitorAlert
void  doMonitorAlert(Str255 labelText)
{
  AlertStdAlertParamRec paramRec;
  SInt16                itemHit;
  
  paramRec.movable        = true;
  paramRec.helpButton     = false;
  paramRec.filterProc     = NULL;
  paramRec.defaultText    = (StringPtr) kAlertDefaultOKText;
  paramRec.cancelText     = NULL;
  paramRec.otherText      = NULL;
  paramRec.defaultButton  = kAlertStdAlertOKButton;
  paramRec.cancelButton   = 0;
  paramRec.position       = kWindowDefaultPosition;
  StandardAlert(kAlertNoteAlert,labelText,NULL,¶mRec,&itemHit);
}
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
Demonstration Program CommentsWhen this program is first run, the user should:
*   Drag the window to various position on the main screen, noting the changes to the
    coordinates of the boundary rectangle.
*   Open the Monitors and Sound control panel and, depending on the characteristics of
    the user's system:
*   Change between the available resolutions, noting the changes in the bytes per row
    and total pixel image bytes figures displayed in the window.
*   Change between the available colour depths, noting the changes to the pixel depth
    and total pixel image bytes figures, and the background colour used figures,
    displayed in the window.
*   Note that, when 256 or less colours are displayed on a direct device (in colours and
    grays), the device creates a CLUT and operates like a direct device.  In this case,
    the background colour used figure is the colour table entry (index), and the relevant
    colour in the colour table display is framed in white.
Assuming the user's monitor is a direct colour device, the user should then run the
program again with the monitor set to display 256 colours prior to program start.  The
Demonstration menu and its items will be enabled.  The user should then choose the items
in the Demonstration menu to set the monitor to a pixel depth of 16 and back to the
startup pixel depth.mainBefore DrawMenuBar is called, a call to the application-defined function doCheckMonitor
assigns the startup pixel depth to a global variable and determines whether the main
device supports 16-bit colour.  If the main device does not support 16-bit colour, the
Demonstration menu is disabled.  If the main device does support support 16-bit colour,
the Demonstration menu is disabled only if the current pixel depth is not 8 (256 colours)
or less. doEventsIn the case of a mouse-down event, in the inDrag case, when the user releases the mouse
button, the left half of the window is invalidated, causing the left half to be redrawn
with the new boundary rectangle coordinates. doDisplayInformationIn the first three lines, RGB colours are assigned to the window's colour graphics port's
rgbFgColor and rgbBkColor fields.  The call to EraseRect causes the content region to be
filled with the background colour. Get Device ListThe call to LMGetDeviceList gets a handle to the first GDevice structure in the device
list.  The device list is then "walked" in the while loop.  For every video device found
in the list, the variable videoDeviceCount is incremented.  GetNextDevice gets a handle
to the next device in the device list. Get Main DeviceLMGetMainDevice gets a handle to the startup device, that is, the device on which the
menu bar appears.
The call to BitTest with the gdDevType flag determines whether the main (startup) device
is a colour or black-and-white device.  In the next block, the gdType field of the
GDevice structure is examined to determine whether the device is an indexed device with a
variable CLUT, an indexed device with a fixed CLUT, or a direct device (or a direct
device set to display 256 colours or less and, as a consequence, acting like an indexed
device). Get Handle to Pixel MapAt the first line of this block, a handle to the GDevice structure's pixel map is
retrieved from the gdPMap field.
In the next block, the pixel depth is extracted from the PixMap structure's pixelSize
field. Get Device's Global BoundariesAt the first line of this block, the device's global boundaries are extracted from the
GDevice structure's gdRect field.
At the next block, the number of bytes in each row in the pixel map is determined.  (The
high bit in the rowBytes field of the PixMap structure is a flag which indicates whether
the data structure is a PixMap structure or a BitMap structure.)
At the next block, the bytes per row value is multiplied by the height of the boundary
rectangle to arrive at the total number of bytes in the pixel image.
The two calls to GlobalToLocal convert the boundary rectangle coordinates to coordinates
local to the colour graphics port. Get Pointer To Colour Graphics PortThe first line simply casts the windowPtr to a pointer to a colour graphics port so that,
later on, the bkColor field can be accessed.
The next block gets the current (requested) background colour using the function
GetBackColor, and then extracts the red, green, and blue components.
At the next line, the pixel value in the bkColor field of the colour graphics port is
retrieved.  This is an SInt32 value holding either the red, green, and blue components of
the background colour actually used for drawing (direct device) or the colour table entry
used for drawing (indexed devices).
For direct devices with a pixel depth of 16, the first 15 bits hold the three RGB
components.  For direct devices with a pixel depth of 32, the first 24 bits hold the RGB
components.  These are extracted in the if(deviceType == directType) block.  For indexed
devices the value is simply the colour table entry (index) determined by the Color
Manager to represent the nearest match to the requested colour. Get Handle To Colour TableThe first and fourth lines get a handle to the colour table in the GDevice structure's
pixel map and the number of entries in that table.
The final block paints small coloured rectangles for each entry in the colour table.  If
the main device is an indexed device (or if it is a direct device set to display 256
colours or less), the colour table entry being used as the best match for the requested
background colour is outlined in white. doCheckMonitordoCheckMonitor is called at program start to determine whether the main device supports
16-bit colour and, if it does, to assign the main device's pixel depth at startup to the
global variable gStartupPixelDepth.  
The call to LMGetMainDevice gets a handle to the main device's GDevice structure.  The
function HasDepth is used to determine whether the device supports 16-bit colour.  The
pixel depth is extracted from the pixelSize field of the PixMap structure in the GDevice
structure. doSetMonitorPixelDepthdoSetMonitorPixelDepth is called when the first item in the Demonstration menu is chosen
to set the main device's pixel depth to 16.
If the current pixel depth determined at the first two lines is not 16, a string is
retrieved from a 'STR#' resource and passed to the application-defined function
doMonitorAlert, which displays a movable modal alert box advising the user that the
monitor's bit depth is about to be changed to 16.  When the user dismisses the alert box,
SetDepth sets the main device's pixel depth to 16.
If the current pixel depth is 16, the last two lines display an alert box advising the
user that the device is currently set to that pixel depth. doRestoreMonitorPixelDepthdoRestoreMonitorPixelDepth is called when the second item in the Demonstration menu is
chosen to reset the main device's pixel depth to the startup pixel depth.
If the current pixel depth determined at the first two lines is not equal to the startup
pixel depth, a string is retrieved from a 'STR#' resource and passed to the
application-defined function doMonitorAlert, which displays a movable modal alert box
advising the user that the monitor's bit depth is about to be changed to the startup
pixel depth.  When the user dismisses the alert box, SetDepth sets the main device's
pixel depth to the startup pixel depth.
If the current pixel depth is the startup pixel depth, the last two lines display an
alert box advising the user that the device is currently set to that pixel depth. |