/*
 * BTTNCUR.C
 * Buttons & Cursors Version 1.1, March 1993
 *
 * Public functions to generate different states of toolbar buttons from
 * a single bitmap.  States are normal, pressed, checked, and disabled.
 *
 * Copyright (c)1992-1993 Microsoft Corporation, All Rights Reserved,
 * as applied to redistribution of this source code in source form
 * License is granted to use of compiled code in shipped binaries.
 */

#define STRICT
#include <windows.h>
#include <memory.h>
#include "bttncur.h"
#include "bttncuri.h"



//Display sensitive information
TOOLDISPLAYDATA     tdd;

//Library instance
HINSTANCE           ghInst;
/*
23/1/94 (kerenm)
Add intance count for 32bit dll
*/
int                 cInstances = 0;

//Cache GDI objects to speed drawing.
HDC     hDCGlyphs    = NULL;
HDC     hDCMono      = NULL;
HBRUSH  hBrushDither = NULL;

//Standard images to use in case caller doesn't provide them
HBITMAP rghBmpStandardImages[3];

//Standard button colors.
const COLORREF crStandard[4]={ RGB(0, 0, 0)          //STDCOLOR_BLACK
                      , RGB(128, 128, 128)    //STDCOLOR_DKGRAY
                      , RGB(192, 192, 192)    //STDCOLOR_LTGRAY
                      , RGB(255, 255, 255)};  //STDCOLOR_WHITE


/*
 * Mapping from image identifier to button type (command/attribute).
 * Version 1.00 of this DLL has no attribute images defined, so
 * the code will only support three states for each command
 * button.  Any state is, however, valid for an application
 * defined image.
 */

UINT mpButtonType[TOOLIMAGE_MAX-TOOLIMAGE_MIN+1]=
      {
      BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND,
      BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND,
      BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND
      };




/*
 * LibMain
 *
 * Purpose:
 *  DLL-specific entry point called from LibEntry.  Initializes
 *  global variables and loads standard image bitmaps.
 *
 * Parameters:
 *  hInstance       HANDLE instance of the DLL.
 *  wDataSeg        WORD segment selector of the DLL's data segment.
 *  wHeapSize       WORD byte count of the heap.
 *  lpCmdLine       LPSTR to command line used to start the module.
 *
 * Return Value:
 *  HANDLE          Instance handle of the DLL.
 */

BOOL xxxLibMain(HINSTANCE hInstance)
   {
    int i;

/*
23/1/94 (kerenm)
For WIN32S - should done only once. On NT ghInst is instance dataso it will
always be zero.
*/
#ifdef WIN32
   if (ghInst) {
      return(TRUE);
   }
#endif
   ghInst=hInstance;

    tdd.uDPI     =96;
   tdd.cyBar    =CYBUTTONBAR96;
   tdd.cxButton =TOOLBUTTON_STD96WIDTH;
   tdd.cyButton =TOOLBUTTON_STD96HEIGHT;
   tdd.cxImage  =TOOLBUTTON_STD96IMAGEWIDTH;
   tdd.cyImage  =TOOLBUTTON_STD96IMAGEHEIGHT;
   tdd.uIDImages=IDB_STANDARDIMAGES96;

   for (i=0; i < 3; i++)
      {
      rghBmpStandardImages[i]=LoadBitmap(hInstance
         , MAKEINTRESOURCE(IDB_STANDARDIMAGESMIN+i));

      if (NULL==rghBmpStandardImages[i])
         return FALSE;
      }


   //Perform global initialization.
   if (ToolButtonInit())
      {
      CursorsCache(hInstance);


      return TRUE;
      }

   return FALSE;

   }

#ifdef WIN32

void FAR PASCAL WEP(int);

BOOL WINAPI DllMain
(
   HINSTANCE hInstance,
   ULONG     Reason,
   PCONTEXT  Context
)
{
   OutputDebugString("bttncur LibMain:  bttncur.dll loaded\r\n");

   UNREFERENCED_PARAMETER(Context);

   if (Reason == DLL_PROCESS_DETACH) {

#ifdef WIN32
     if (!--cInstances) {
        WEP(0);
     }
#else
      WEP(0);
#endif
      return TRUE;
   }
   else if (Reason != DLL_PROCESS_ATTACH)
      return TRUE;

/* 1/23/94   */
#ifdef WIN32
   cInstances++;
#endif
   return xxxLibMain(hInstance);

}

#else

HANDLE FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg
                    , WORD cbHeapSize, LPSTR lpCmdLine)
   {

    //Perform global initialization.
   if (xxxLibMain(hInstance))
      if (0!=cbHeapSize)
         UnlockData(0);

   return hInstance;

   }

#endif


/*
 * WEP
 *
 * Purpose:
 *  Required DLL Exit function.  Does nothing.
 *
 * Parameters:
 *  bSystemExit     BOOL indicating if the system is being shut
 *                  down or the DLL has just been unloaded.
 *
 * Return Value:
 *  void
 *
 */

void FAR PASCAL WEP(int bSystemExit)
   {
   /*
    * **Developers:  Note that WEP is called AFTER Windows does any
    *                automatic task cleanup.  You may see warnings for
    *                that two DCs, a bitmap, and a brush, were not
    *                deleted before task termination.  THIS IS NOT A
    *                PROBLEM WITH THIS CODE AND IT IS NOT A BUG.  This
    *                WEP function is properly called and performs the
    *                cleanup as appropriate.  The fact that Windows is
    *                calling WEP after checking task cleanup is not
    *                something we can control.  Just to prove it, the
    *                OutputDebugStrings in this and ToolButtonFree
    *                show that the code is exercised.
    */

#ifdef DEBUG
   OutputDebugString ("BttnCur WEP Entry\r\n");
#endif
   CursorsFree();
   ToolButtonFree();
#ifdef DEBUG
   OutputDebugString ("BttnCur WEP Exit\r\n");
#endif
   return;
   }





/*
 * UIToolConfigureForDisplay
 * Public API
 *
 * Purpose:
 *  Initializes the library to scale button images for the display type.
 *  Without calling this function the library defaults to 96 DPI (VGA).
 *  By calling this function an application acknowledges that it must
 *  use the data returned from this function to configure itself for
 *  the display.
 *
 * Parameters:
 *  lpDD            LPTOOLDISPLAYDATA to fill with the display-sensitive
 *                  size values.
 *
 * Return Value:
 *  BOOL            TRUE if the sizes were obtained, FALSE otherwise.
 */

BOOL WINAPI UIToolConfigureForDisplay(LPTOOLDISPLAYDATA lpDD)
   {
   int         cy;
   HDC         hDC;


   if (NULL==lpDD || IsBadWritePtr(lpDD, sizeof(TOOLDISPLAYDATA)))
      return FALSE;

   /*
    * Determine the aspect ratio of the display we're currently
    * running on and calculate the necessary information.
    *
    * By retrieving the logical Y extent of the display driver, you
    * only have limited possibilities:
    *      LOGPIXELSY      Display
    *      ----------------------------------------
    *         48             CGA    (unsupported)
    *         72             EGA
    *         96             VGA
    *        120             8514/a (i.e. HiRes VGA)
    */

   hDC=GetDC(NULL);

   if (NULL==hDC)
      return FALSE;

   cy=GetDeviceCaps(hDC, LOGPIXELSY);
   ReleaseDC(NULL, hDC);

   /*
    * Instead of single comparisons, check ranges instead, so in case
    * we get something funky, we'll act reasonable.
    */
   if (72 >=cy)
      {
      lpDD->uDPI     =72;
      lpDD->cyBar    =CYBUTTONBAR72;
      lpDD->cxButton =TOOLBUTTON_STD72WIDTH;
      lpDD->cyButton =TOOLBUTTON_STD72HEIGHT;
      lpDD->cxImage  =TOOLBUTTON_STD72IMAGEWIDTH;
      lpDD->cyImage  =TOOLBUTTON_STD72IMAGEHEIGHT;
      lpDD->uIDImages=IDB_STANDARDIMAGES72;
      }
   else
      {
      if (72 < cy && 120 > cy)
         {
         lpDD->uDPI     =96;
         lpDD->cyBar    =CYBUTTONBAR96;
         lpDD->cxButton =TOOLBUTTON_STD96WIDTH;
         lpDD->cyButton =TOOLBUTTON_STD96HEIGHT;
         lpDD->cxImage  =TOOLBUTTON_STD96IMAGEWIDTH;
         lpDD->cyImage  =TOOLBUTTON_STD96IMAGEHEIGHT;
         lpDD->uIDImages=IDB_STANDARDIMAGES96;
         }
      else
         {
         lpDD->uDPI     =120;
         lpDD->cyBar    =CYBUTTONBAR120;
         lpDD->cxButton =TOOLBUTTON_STD120WIDTH;
         lpDD->cyButton =TOOLBUTTON_STD120HEIGHT;
         lpDD->cxImage  =TOOLBUTTON_STD120IMAGEWIDTH;
         lpDD->cyImage  =TOOLBUTTON_STD120IMAGEHEIGHT;
         lpDD->uIDImages=IDB_STANDARDIMAGES120;
         }
      }

   return TRUE;
   }








/*
 * ToolButtonInit
 * Internal
 *
 * Purpose:
 *  Initializes GDI objects for drawing images through UIToolButtonDraw.
 *  If the function fails, the function has already performed proper
 *  cleanup.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded.  FALSE otherwise.
 */

static BOOL ToolButtonInit(void)
   {
   COLORREF        rgbHi;

   //DC for BitBltting the image (the glyph)
   hDCGlyphs=CreateCompatibleDC(NULL);

   //Create a monochrome DC and a brush for doing pattern dithering.
   hDCMono=CreateCompatibleDC(NULL);

   //Windows 3.0 doesn't support COLOR_BTNHIGHLIGHT, so leave it white.
   if (0x0300 < (UINT)GetVersion())
      rgbHi=GetSysColor(COLOR_BTNHIGHLIGHT);
   else
      rgbHi=crStandard[STDCOLOR_WHITE];

   hBrushDither=HBrushDitherCreate(GetSysColor(COLOR_BTNFACE), rgbHi);

   if (NULL==hDCGlyphs || NULL==hDCMono || NULL==hBrushDither)
      {
      //On failure, cleanup whatever might have been allocated.
      ToolButtonFree();
      return FALSE;
      }

   return TRUE;
   }





/*
 * ToolButtonFree
 * Internal
 *
 * Purpose:
 *  Free all GDI allocations made during initialization.  Note that the
 *  DEBUG output included here shows that WEP is called and cleanup actually
 *  occurs.  However, if you watch debug output in DBWIN or on a terminal,
 *  the debugging version of Windows does automatic app cleanup before WEP
 *  is called, leading some to believe that this code is buggy.  The
 *  debug output below shows that we do perform all necessary cleanup.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

static void ToolButtonFree(void)
   {
   UINT        i;

   if (NULL!=hDCMono)
      DeleteDC(hDCMono);
   hDCMono=NULL;


   if (NULL!=hDCGlyphs)
      DeleteDC(hDCGlyphs);
   hDCGlyphs=NULL;

   if (NULL!=hBrushDither)
      DeleteObject(hBrushDither);
   hBrushDither=NULL;

   for (i=0; i < 3; i++)
      {
      if (NULL!=rghBmpStandardImages[i])
         DeleteObject(rghBmpStandardImages[i]);
      rghBmpStandardImages[i]=NULL;
      }

   return;
   }





/*
 * HBrushDitherCreate
 * Internal
 *
 * Purpose:
 *  Creates and returns a handle to a pattern brush created from
 *  an 8*8 monochrome pattern bitmap.  We use the button face and
 *  highlight colors to indicate the resulting colors of a PatBlt
 *  using this brush.
 *
 * Parameters:
 *  rgbFace         COLORREF of the button face color.
 *  rgbHilight      COLORREF of the button highlight color.
 *
 * Return Value:
 *  HBITMAP         Handle to the dither bitmap.
 */

static HBRUSH HBrushDitherCreate(COLORREF rgbFace, COLORREF rgbHilight)
   {
   struct  //BITMAPINFO with 16 colors
      {
      BITMAPINFOHEADER    bmiHeader;
      RGBQUAD             bmiColors[16];
      } bmi;

   HBRUSH          hBrush=NULL;
   DWORD           patGray[8];
   HDC             hDC;
   HBITMAP         hBmp;
   static COLORREF rgbFaceOld   =0xFFFFFFFF;  //Initially an impossible color
   static COLORREF rgbHilightOld=0xFFFFFFFF;  //so at first we always create

   /*
    * If the colors haven't changed from last time, just return the
    * existing brush.
    */
   if (rgbFace==rgbFaceOld && rgbHilight==rgbHilightOld)
      return hBrushDither;

   rgbFaceOld=rgbFace;
   rgbHilightOld=rgbHilight;

   /*
    * We're going to create an 8*8 brush for PatBlt using the
    * button face color and button highlight color.  We use this
    * brush to affect the pressed state and the disabled state.
    */
   bmi.bmiHeader.biSize         = sizeof(BITMAPINFOHEADER);
   bmi.bmiHeader.biWidth        = 8;
   bmi.bmiHeader.biHeight       = 8;
   bmi.bmiHeader.biPlanes       = 1;
   bmi.bmiHeader.biBitCount     = 1;
   bmi.bmiHeader.biCompression  = BI_RGB;
   bmi.bmiHeader.biSizeImage    = 0;
   bmi.bmiHeader.biXPelsPerMeter= 0;
   bmi.bmiHeader.biYPelsPerMeter= 0;
   bmi.bmiHeader.biClrUsed      = 0;
   bmi.bmiHeader.biClrImportant = 0;

   bmi.bmiColors[0].rgbBlue     = GetBValue(rgbFace);
   bmi.bmiColors[0].rgbGreen    = GetGValue(rgbFace);
   bmi.bmiColors[0].rgbRed      = GetRValue(rgbFace);
   bmi.bmiColors[0].rgbReserved = 0;

   bmi.bmiColors[1].rgbBlue     = GetBValue(rgbHilight);
   bmi.bmiColors[1].rgbGreen    = GetGValue(rgbHilight);
   bmi.bmiColors[1].rgbRed      = GetRValue(rgbHilight);
   bmi.bmiColors[1].rgbReserved = 0;

   //Create the byte array for CreateDIBitmap.
   patGray[6]=patGray[4]=patGray[2]=patGray[0]=0x5555AAAAL;
   patGray[7]=patGray[5]=patGray[3]=patGray[1]=0xAAAA5555L;

   //Create the bitmap
   hDC=GetDC(NULL);
   hBmp=CreateDIBitmap(hDC, &bmi.bmiHeader, CBM_INIT, patGray
                  , (LPBITMAPINFO)&bmi, DIB_RGB_COLORS);
   ReleaseDC(NULL, hDC);

   //Create the brush from the bitmap
   if (NULL!=hBmp)
      {
      hBrush=CreatePatternBrush(hBmp);
      DeleteObject(hBmp);
      }

   /*
    * If we could recreate a brush, clean up and make it the current
    * pattern.  Otherwise the best we can do it return the old one,
    * which will be colored wrong, but at least it works.
    */
   if (NULL!=hBrush)
      {
      if (NULL!=hBrushDither)
         DeleteObject(hBrushDither);

      hBrushDither=hBrush;
      }

   return hBrushDither;
   }





/*
 * UIToolButtonDraw
 * Public API
 *
 * Purpose:
 *  Draws the complete image of a toolbar-style button with a given
 *  image in the center and in a specific state.  The button is drawn
 *  on a specified hDC at a given location, so this function is useful
 *  on standard owner-draw buttons as well as on toolbar controls that
 *  have only one window but show images of multiple buttons.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  x, y            int coordinates at which to draw.
 *  dx, dy          int dimensions of the *button*, not necessarily the image.
 *  hBmp            HBITMAP from which to draw the image.
 *  bmx, bmy        int dimensions of each bitmap in hBmp.  If hBmp is NULL
 *                  then these are forced to the standard sizes.
 *  iImage          int index to the image to draw in the button
 *  uStateIn        UINT containing the state index for the button and the
 *                  color control bits.
 *
 * Return Value:
 *  BOOL            TRUE if drawing succeeded, FALSE otherwise meaning that
 *                  hDC is NULL or hBmp is NULL and iImage is not a valid
 *                  index for a standard image.
 */

BOOL WINAPI UIToolButtonDraw(HDC hDC, int x, int y, int dx, int dy
   , HBITMAP hBmp, int bmx, int bmy, int iImage, UINT uStateIn)
   {
   return UIToolButtonDrawTDD(hDC, x, y, dx, dy, hBmp, bmx, bmy, iImage
      , uStateIn, &tdd);
   }






/*
 * UIToolButtonDrawTDD
 * Public API
 *
 * Purpose:
 *  Draws the complete image of a toolbar-style button with a given
 *  image in the center and in a specific state.  The button is drawn
 *  on a specified hDC at a given location, so this function is useful
 *  on standard owner-draw buttons as well as on toolbar controls that
 *  have only one window but show images of multiple buttons.
 *
 *  This is the same as UIToolButtonDraw but adds the pTDD configuration
 *  structure.  UIToolButtonDraw calls us with that pointing to the
 *  default 96dpi structure.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  x, y            int coordinates at which to draw.
 *  dx, dy          int dimensions of the *button*, not necessarily the image.
 *  hBmp            HBITMAP from which to draw the image.
 *  bmx, bmy        int dimensions of each bitmap in hBmp.  If hBmp is NULL
 *                  then these are forced to the standard sizes.
 *  iImage          int index to the image to draw in the button
 *  uStateIn        UINT containing the state index for the button and the
 *                  color control bits.
 *  pTDD            LPTOOLDISPLAYDATA containing display configuration.
 *                  Can be NULL if hBmp is non-NULL.
 *
 * Return Value:
 *  BOOL            TRUE if drawing succeeded, FALSE otherwise meaning that
 *                  hDC is NULL or hBmp is NULL and iImage is not a valid
 *                  index for a standard image.
 */

BOOL WINAPI UIToolButtonDrawTDD(HDC hDC, int x, int y, int dx, int dy
   , HBITMAP hBmp, int bmx, int bmy, int iImage, UINT uStateIn
   , LPTOOLDISPLAYDATA pTDD)
   {
   static COLORREF crSys[5];  //Avoid stack arrays in DLLs: use static
   UINT            uState=(UINT)LOBYTE((WORD)uStateIn);
   UINT            uColors=(UINT)HIBYTE((WORD)uStateIn & PRESERVE_ALL);
   int             xOffsetGlyph, yOffsetGlyph;
   int             i, iSaveDC;
   HDC             hMemDC;
   HGDIOBJ         hObj;
   HBRUSH          hBR;
   HBITMAP         hBmpT;
   HBITMAP         hBmpMono;
   HBITMAP         hBmpMonoOrg;
   HBITMAP         hBmpSave=NULL;

   if (NULL==hDC)
      return FALSE;

   /*
    * If we're given no image bitmap, then use the standard and validate the
    * image index.  We also enforce the standard bitmap size and the size of
    * the button (as requested by User Interface designers).
    */
   if (NULL==hBmp && !(uState & BUTTONGROUP_BLANK))
      {
      hBmp=rghBmpStandardImages[pTDD->uIDImages-IDB_STANDARDIMAGESMIN];

      bmx=pTDD->cxImage;            //Force bitmap dimensions
      bmy=pTDD->cyImage;

      dx=pTDD->cxButton;            //Force button dimensions
      dy=pTDD->cyButton;

      if (iImage > TOOLIMAGE_MAX)
         return FALSE;

      /*
       * If we are using a standard command button, verify that the state
       * does not contain the LIGHTFACE group which only applies to
       * attribute buttons.
       */
      if (BUTTONTYPE_COMMAND==mpButtonType[iImage]
         && (uState & BUTTONGROUP_LIGHTFACE))
         return FALSE;
      }

   //Create a dithered bitmap.
   hBmpMono=CreateBitmap(dx-2, dy-2, 1, 1, NULL);

   if (NULL==hBmpMono)
      return FALSE;

   hBmpMonoOrg=(HBITMAP)SelectObject(hDCMono,  hBmpMono);


   //Save the DC state before we munge on it.
   iSaveDC=SaveDC(hDC);

   /*
    * Draw a button sans image.  This also fills crSys with the system
    * colors for us which has space for five colors.  We don't use the
    * fifth, the frame color, in this function.
    */
   DrawBlankButton(hDC, x, y, dx, dy, (BOOL)(uState & BUTTONGROUP_DOWN), crSys);

   //Shift coordinates to account for the button's border
   x++;
   y++;
   dx-=2;
   dy-=2;

   /*
    * Determine the offset necessary to center the image but also reflect
    * the pushed-in state, which means just adding 1 to the up state.
    */
   i=(uState & BUTTONGROUP_DOWN) ? 1 : 0;
   xOffsetGlyph=((dx-bmx) >> 1)+i;
   yOffsetGlyph=((dy-bmy) >> 1)+i;


   //Select the given image bitmap into the glyph DC before calling MaskCreate
   if (NULL!=hBmp)
      hBmpSave=(HBITMAP)SelectObject(hDCGlyphs, hBmp);


   /*
    * Draw the face on the button.  If we have an up or [mouse]down
    * button then we can just draw it as-is.  For indeterminate,
    * disabled, or down disabled we have to gray the image and possibly
    * add a white shadow to it (disabled/down disabled).
    *
    * Also note that for the intermediate state we first draw the normal
    * up state, then proceed to add disabling looking highlights.
    */

   //Up, mouse down, down, indeterminate
   if ((uState & BUTTONGROUP_ACTIVE) && !(uState & BUTTONGROUP_BLANK))
      {
      BOOL            fColorsSame=TRUE;

      /*
       * In here we pay close attention to the system colors.  Where
       * the source image is black, we paint COLOR_BTNTEXT.  Where
       * light gray, we paint COLOR_BTNFACE.  Where dark gray we paint
       * COLOR_BTNSHADOW, and where white we paint COLOR_BTNHILIGHT.
       *
       * The uColors variable contains flags to prevent color
       * conversion.  To do a little optimization, we just do a
       * single BitBlt if we're preserving all colors or if no colors
       * are different than the standards, which is by far the most
       * common case.  Otherwise, cycle through the four colors we can
       * convert and do a BitBlt that converts it to the system color.
       */

      //See what colors are different.
      for (i=STDCOLOR_BLACK; i<=STDCOLOR_WHITE; i++)
         fColorsSame &= (crSys[i]==crStandard[i]);

      if (PRESERVE_ALL==uColors || fColorsSame)
         {
         BitBlt(hDC, x+xOffsetGlyph, y+yOffsetGlyph, bmx, bmy
               , hDCGlyphs, iImage*bmx, 0, SRCCOPY);
         }
      else
         {
         /*
          * Cycle through hard-coded colors and create a mask that has all
          * regions of that color in white and all other regions black.
          * Then we select a pattern brush of the color to convert to:
          * if we aren't converting the color then we use a brush of
          * the standard hard-coded color, otherwise we use the actual
          * system color.  The ROP_DSPDxax means that anything that's
          * 1's in the mask get the pattern, anything that's 0 is unchanged
          * in the destination.
          *
          * To prevent too many Blts to the screen, we use an intermediate
          * bitmap and DC.
          */

         hMemDC=CreateCompatibleDC(hDC);

         //Make sure conversion of monochrome to color stays B&W
         SetTextColor(hMemDC, 0L);                     //0's in mono -> 0
         SetBkColor(hMemDC, (COLORREF)0x00FFFFFF);     //1's in mono -> 1

         hBmpT=CreateCompatibleBitmap(hDC, bmx, bmy);
         SelectObject(hMemDC, hBmpT);

         //Copy the unmodified bitmap to the temporary bitmap
         BitBlt(hMemDC, 0, 0, bmx, bmy, hDCGlyphs, iImage*bmx, 0, SRCCOPY);

         for (i=STDCOLOR_BLACK; i<=STDCOLOR_WHITE; i++)
            {
            //Convert pixels of the color to convert to 1's in the mask
            SetBkColor(hDCGlyphs, crStandard[i]);
            BitBlt(hDCMono, 0, 0, bmx, bmy, hDCGlyphs, iImage*bmx, 0, SRCCOPY);

            //Preserve or modify the color depending on the flag.
            hBR=CreateSolidBrush((uColors & (1 << i))
                            ? crStandard[i] : crSys[i]);

            if (NULL!=hBR)
               {
               hObj=SelectObject(hMemDC, hBR);

               if (NULL!=hObj)
                  {
                  BitBlt(hMemDC, 0, 0, dx-1, dy-1, hDCMono, 0, 0, ROP_DSPDxax);
                  SelectObject(hMemDC, hObj);
                  }

               DeleteObject(hBR);
               }
            }

         //Now put the final version on the display and clean up
         BitBlt(hDC, x+xOffsetGlyph, y+yOffsetGlyph, dx-1, dy-1
               , hMemDC, 0, 0, SRCCOPY);

         DeleteDC(hMemDC);
         DeleteObject(hBmpT);

         }
      }


   //Disabled and indeterminate states (unless we're blank)
   if ((uState & BUTTONGROUP_DISABLED || ATTRIBUTEBUTTON_INDETERMINATE==uState)
      && !(uState & BUTTONGROUP_BLANK))
      {
      //Grayed state (up or down, no difference)
      MaskCreate(iImage, dx, dy, bmx, bmy, xOffsetGlyph, yOffsetGlyph, 0);

      //Make sure conversion of monochrome to color stays B&W
      SetTextColor(hDC, 0L);                     //0's in mono -> 0
      SetBkColor(hDC, (COLORREF)0x00FFFFFF);     //1's in mono -> 1

      //If we're disabled, up or down, draw the highlighted shadow.
      if (uState & BUTTONGROUP_DISABLED)
         {
         hBR=CreateSolidBrush(crSys[SYSCOLOR_HILIGHT]);

         if (NULL!=hBR)
            {
            hObj=SelectObject(hDC, hBR);

            if (NULL!=hObj)
               {
               //Draw hilight color where we have 0's in the mask
               BitBlt(hDC, x+1, y+1, dx-2, dy-2, hDCMono, 0, 0, ROP_PSDPxax);
               SelectObject(hDC, hObj);
               }
            DeleteObject(hBR);
            }
         }

      //Draw the gray image.
      hBR=CreateSolidBrush(crSys[SYSCOLOR_SHADOW]);

      if (NULL!=hBR)
         {
         hObj=SelectObject(hDC, hBR);

         if (NULL!=hObj)
            {
            //Draw the shadow color where we have 0's in the mask
            BitBlt(hDC, x, y, dx-2, dy-2, hDCMono, 0, 0, ROP_PSDPxax);
            SelectObject(hDC, hObj);
            }

         DeleteObject(hBR);
         }
      }

   //If the button is selected do the dither brush avoiding the glyph
   if (uState & BUTTONGROUP_LIGHTFACE)
      {
      HBRUSH      hBRDither;

      /*
       * Get the dither brush.  This function will recreate it if
       * necessary or return the global one if the colors already match.
       */
      hBRDither=HBrushDitherCreate(crSys[SYSCOLOR_FACE], crSys[SYSCOLOR_HILIGHT]);
      hObj=SelectObject(hDC, hBRDither);

      if (NULL!=hObj)
         {
         /*
          * The mask we create now determines where the dithering
          * ends up.  In the down disabled state, we have to preserve
          * the highlighted shadow, so the mask we create must have
          * two masks of the original glyph, one of them offset by
          * one pixel in both x & y.  For the indeterminate state,
          * we have to mask all highlighted areas.  The state passed
          * to MaskCreate matters here (we've used zero before).
          */
         MaskCreate(iImage, dx, dy, bmx, bmy
                  , xOffsetGlyph-1, yOffsetGlyph-1, uState);

         //Convert monochrome masks to B&W color bitmap in the BitBlt.
         SetTextColor(hDC, 0L);
         SetBkColor(hDC, (COLORREF)0x00FFFFFF);

         /*
          * Only draw the dither brush where the mask is 1's.  For
          * the indeterminate state we have to not overdraw the
          * shadow highlight so we use dx-3, dy-3 instead of dx-1
          * and dy-1.  We do this whether or not we're blank.
          */
         i=(ATTRIBUTEBUTTON_INDETERMINATE==uState
            || BLANKBUTTON_INDETERMINATE==uState) ? 3 : 1;

         BitBlt(hDC, x+1, y+1, dx-i, dy-i, hDCMono, 0, 0, ROP_DSPDxax);
         SelectObject(hDC, hObj);
         }

      //DO NOT delete hBRDither!  It's a reference to a shared global.
      }

   //Cleanup hDCGlyphs:  Must do AFTER calling MaskCreate
   if (NULL!=hBmpSave)
      SelectObject(hDCGlyphs, hBmpSave);

   SelectObject(hDCMono,   hBmpMonoOrg);
   DeleteObject(hBmpMono);

   //Restore everything in the DC.
   RestoreDC(hDC, iSaveDC);
   return TRUE;
   }






/*
 * DrawBlankButton
 *
 * Purpose:
 *  Draws a button with no face using the current system colors in either
 *  an up or down state.
 *
 * Parameters:
 *  hDC             HDC on which to draw
 *  x, y            int coordinates where we start drawing
 *  dx,dy           int size of the button
 *  fDown           BOOL indicating the up or down state of the button
 *  pcr             COLORREF FAR * to five colors in which we store text,
 *                  shadow, face, highlight, and frame colors.  This is
 *                  a matter of convenience for the caller, since we have
 *                  to load these colors anyway we might as well send them
 *                  back.
 *
 * Return Value:
 *  None
 */

static void DrawBlankButton(HDC hDC, int x, int y, int dx, int dy
   , BOOL fDown, COLORREF FAR *pcr)
   {
   //Get the current system colors for buttons.
   pcr[0]=GetSysColor(COLOR_BTNTEXT);
   pcr[1]=GetSysColor(COLOR_BTNSHADOW);
   pcr[2]=GetSysColor(COLOR_BTNFACE);

   //Windows 3.0 doesn't support COLOR_BTNHIGHLIGHT, so leave it white.
   if (0x0300 < (UINT)GetVersion())
      pcr[3]=GetSysColor(COLOR_BTNHIGHLIGHT);
   else
      pcr[3]=crStandard[STDCOLOR_WHITE];

   pcr[4]=GetSysColor(COLOR_WINDOWFRAME);

   //Draw the border around the button.
   PatB(hDC, x+1,    y,      dx-2, 1,    pcr[4]);
   PatB(hDC, x+1,    y+dy-1, dx-2, 1,    pcr[4]);
   PatB(hDC, x,      y+1,    1,    dy-2, pcr[4]);
   PatB(hDC, x+dx-1, y+1,    1,    dy-2, pcr[4]);

   //Shift coordinates to account for the border we just drew
   x++;
   y++;
   dx-=2;
   dy-=2;

   //Paint the interior grey as a default.
   PatB(hDC, x, y, dx, dy, pcr[2]);

   /*
    * Draw shadows and highlights.  The DOWN grouping that contains
    * down, mouse down, and down disabled are drawn depressed.  Up,
    * indeterminate, and disabled are drawn up.
    */

   if (fDown)
      {
      PatB(hDC, x, y, 1,  dy, pcr[1]);
      PatB(hDC, x, y, dx, 1,  pcr[1]);
      }
   else
      {
      //Normal button look.
      PatB(hDC, x, y, 1,    dy-1, pcr[3]);
      PatB(hDC, x, y, dx-1, 1,    pcr[3]);

      PatB(hDC, x+dx-1, y,      1,  dy, pcr[1]);
      PatB(hDC, x,      y+dy-1, dx, 1,  pcr[1]);

      PatB(hDC, x+1+dx-3, y+1,    1,    dy-2, pcr[1]);
      PatB(hDC, x+1,      y+dy-2, dx-2, 1,    pcr[1]);
      }

   return;
   }






/*
 * PatB
 * Internal
 *
 * Purpose:
 *  A more convenient PatBlt operation for drawing button borders and
 *  highlights.
 *
 * Parameters:
 *  hDC             HDC on which to paint.
 *  x, y            int coordinates at which to paint.
 *  dx, dy          int dimensions of rectangle to paint.
 *  rgb             COLORREF to use as the background color.
 *
 * Return Value:
 *  None
 */

static void PatB(HDC hDC, int x, int y, int dx, int dy, COLORREF rgb)
   {
   RECT        rc;

   SetBkColor(hDC, rgb);
   SetRect(&rc, x, y, x+dx, y+dy);
   ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
   }




/*
 * MaskCreate
 * Internal
 *
 * Purpose:
 *  Creates a monochrome mask bitmap of the given image at the given offset
 *  in the global hDCMono.  Anywhere in the image that you have the light
 *  gray (STDCOLOR_LTGRAY) or the white highlight (STDCOLOR_WHITE) you get
 *  get 1's.  All other pixels are 0's
 *
 * Parameters:
 *  iImage          UINT index of the image for which to create a mask.
 *  dx, dy          int dimensions of the button.
 *  bmx, bmy        int dimensions of the bitmap to use.
 *  xOffset         int offset for x inside hDCMono where we paint.
 *  yOffset         int offset for y inside hDCMono where we paint.
 *  uState          UINT state of the image.  Special cases are made
 *                  for ATTRIBUTEBUTTON_DOWNDISABLED and
 *                  ATTRIBUTEBUTTON_INDETERMINATE.  In any case where you
 *                  do not want a special case, pass zero here, regardless
 *                  of the true button state.
 *
 * Return Value:
 *  None
 */

static void MaskCreate(UINT iImage, int dx, int dy, int bmx, int bmy
   ,int xOffset, int yOffset, UINT uState)
   {
   //Initalize whole area with zeros
   PatBlt(hDCMono, 0, 0, dx, dy, WHITENESS);

   if (uState & BUTTONGROUP_BLANK)
      return;

   //Convert face colored pixels to 1's. all others to black.
   SetBkColor(hDCGlyphs, crStandard[STDCOLOR_LTGRAY]);
   BitBlt(hDCMono, xOffset, yOffset, bmx, bmy, hDCGlyphs, iImage*bmx, 0, SRCCOPY);

   //In the indeterminate state, don't turn highlight's to 1's. Leave black.
   if (ATTRIBUTEBUTTON_INDETERMINATE!=uState)
      {
      //Convert highlight colored pixels to 1's and OR them with the previous.
      SetBkColor(hDCGlyphs, crStandard[STDCOLOR_WHITE]);
      BitBlt(hDCMono, xOffset, yOffset, bmx, bmy, hDCGlyphs, iImage*bmx, 0, SRCPAINT);
      }

   /*
    * For the down disabled state, AND this same mask with itself at an
    * offset of 1, which accounts for the highlight shadow.
    */
   if (ATTRIBUTEBUTTON_DOWNDISABLED==uState)
      BitBlt(hDCMono, 1, 1, dx-1, dy-1, hDCMono,  0, 0, SRCAND);

   return;
   }
