
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

/******************************************************************************\
*
*  PROGRAM:     PRINTER.C
*
*  PURPOSE:     This is a sample application demostrating some of the new
*               printing functionality in Windows NT. This app allows the
*               user to select between various GDI graphics primitives,
*               to choose pen & brush colors, styles, and sizes, and to
*               the print these graphics to a printer. Also, the app
*               provides the user the ability to query information (reso-
*               lution, etc.) about the various printers & drivers by
*               making calls to GetDeviceCaps, etc.
*
*               Functionality for PRINTER is split into six different
*               modules as follows:
*
*                 printer.c - main event loop
*                             main window procedure
*                             about & abort dialog procedures
*                             printing thread
*
*                 paint.c   - handles all painting printers & most painting
*                             to window
*
*                 enumprt.c - manages the display of information returned
*                             from calling EnumPrinters, EnumPrinterDrivers
*
*                 devcapsx.c- manages the display of information returned
*                             from calling DeviceCapabilitiesEx
*
*                 getpdriv.c- manages the display of information returned
*                             from calling GetPrinterDriver
*
*                 getcaps.c - manages the display of information returned
*                             from calling GetDeviceCaps
*
*  FUNCTIONS:   WinMain               - initialization, create window, msg loop
*               MainWndProc           - processes main window msgs
*               AboutDlgProc          - processes About dlg msgs
*               InvalidateClient      - invalidates graphics part of client wnd
*               RefreshPrinterCombobox- updates list of printers
*               PrintThread           - printing done here
*               AbortProc             - msg loop for abort
*               AbortDlgProc          - processes abort dialog messages
*
\******************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <winspool.h>
#include <commdlg.h>
#include "common.h"
#include "printer.h"



/******************************************************************************\
*
*  FUNCTION:    WinMain (standard WinMain INPUTS/RETURNS)
*
*  COMMENTS:    Register & create main window, loop for messages
*
\******************************************************************************/

int WINAPI WinMain (HANDLE ghInstance, HANDLE hPrevInstance, LPSTR lpCmdLine,
                    int    nCmdShow)
{
  MSG msg;

  ghInst = ghInstance;

  if (!hPrevInstance)
  {
    WNDCLASS wc;

    wc.style         = MAIN_CLASS_STYLE;
    wc.lpfnWndProc   = (WNDPROC) MainWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = ghInst;
    wc.hIcon         = LoadIcon (ghInst, MAKEINTRESOURCE(MAIN_ICON));
    wc.hCursor       = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = (LPCSTR) MAIN_MENU_NAME;
    wc.lpszClassName = (LPCSTR) MAIN_CLASS_NAME;

    if (!RegisterClass (&wc))
    {
      MessageBox (NULL, "WinMain(): RegisterClass() failed",
                  ERR_MOD_NAME, MB_OK | MB_ICONHAND);

      return FALSE;
    }
  }

  if (!(ghwndMain = CreateWindow ((LPCSTR) MAIN_CLASS_NAME,
                                  (LPCSTR) MAIN_WND_TITLE,
                                  MAIN_WND_STYLE,
                                  CW_USEDEFAULT, CW_USEDEFAULT,
                                  CW_USEDEFAULT, CW_USEDEFAULT,
                                  NULL, NULL, ghInst, NULL)))
    return FALSE;

  ShowWindow (ghwndMain, nCmdShow);

  while (GetMessage (&msg, NULL, 0, 0))
  {
    TranslateMessage (&msg);
    DispatchMessage  (&msg);
  }

  return msg.wParam;
}



/******************************************************************************\
*
*  FUNCTION:    MainWndProc (standard window procedure INPUTS/RETURNS)
*
*  COMMENTS:    Handles main app window msg processing
*
\******************************************************************************/

LRESULT CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  static HMENU hMappingModesSubMenu;
  static HMENU hGraphicsSubMenu;
  static HMENU hPenWidthSubMenu;
  static HMENU hPenStyleSubMenu;
  static HMENU hBrushStyleSubMenu;
  static HWND  hwndCombobox;
  static int   iComboboxWidth;
  static LONG  lTextHeight;

  int i;

  switch (msg)
  {
    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
        case IDM_PRINT:
        case IDM_PRINTDLG:
        {
          DWORD  threadId;

          if (!CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) PrintThread,
                             (LPVOID) wParam, 0, &threadId))

            MessageBox (hwnd,
                        "MainWndProc(): Error creating print thread",
                        ERR_MOD_NAME, MB_OK | MB_ICONHAND);
          break;
        }

        case IDM_GETDEVICECAPS:

          DialogBox (ghInst, (LPCTSTR) "List", hwnd,
                     (DLGPROC) GetDeviceCapsDlgProc);
          break;

        case IDM_ENUMPRINTERS:

          DialogBox (ghInst, (LPCTSTR) "List", hwnd,
                     (DLGPROC) EnumPrintersDlgProc);
          break;

        case IDM_GETPRINTERDRIVER:

          if (strcmp (gszDeviceName, "Display"))

            DialogBox (ghInst, (LPCTSTR) "List", hwnd,
                       (DLGPROC) GetPrinterDriverDlgProc);
          else

            MessageBox (hwnd, (LPCTSTR) "Please select a printer",
                        (LPCTSTR) "PRINTER.EXE:", MB_OK);

          break;

        case IDM_ENUMPRINTERDRIVERS:

          DialogBox (ghInst, (LPCTSTR) "List", hwnd,
                     (DLGPROC) EnumPrinterDriversDlgProc);
          break;

        case IDM_REFRESH:

          RefreshPrinterCombobox (hwndCombobox);
          break;

        case IDM_ABOUT:

          DialogBox (ghInst, (LPCTSTR) "About", hwnd, (DLGPROC) AboutDlgProc);
          break;

        case IDM_HIENGLISH:
        case IDM_HIMETRIC:
        case IDM_LOENGLISH:
        case IDM_LOMETRIC:
        case IDM_TWIPS:
        case IDM_ISOTROPIC:
        case IDM_ANISOTROPIC:
        case IDM_TEXT:

          //
          // Uncheck the last map mode menuitem, check the new map mode
          //   menuitem, set iMappingMode according to menu id, cause a
          //   repaint
          //

          for (i = 0; i < MAX_MAP_MODES; i++)

            if (giMapMode == gaMMLookup[i].iMapMode)
            {
              CheckMenuItem (hMappingModesSubMenu, gaMMLookup[i].wMenuItem,
                             MF_UNCHECKED | MF_BYCOMMAND);
              break;
            }

          CheckMenuItem (hMappingModesSubMenu, LOWORD (wParam),
                         MF_CHECKED | MF_BYCOMMAND);

          for (i = 0; i < MAX_MAP_MODES; i++)

            if (LOWORD (wParam) == gaMMLookup[i].wMenuItem)
            {
              giMapMode = gaMMLookup[i].iMapMode;
              break;
            }

          //
          // invalidate the entire client so toolbar text gets updated
          //

          InvalidateRect (hwnd, NULL, TRUE);
          break;

        case IDM_ARC:
        case IDM_ELLIPSE:
        case IDM_LINETO:
        case IDM_PIE:
        case IDM_PLGBLT:
        case IDM_POLYBEZIER:
        case IDM_POLYGON:
        case IDM_POLYLINE:
        case IDM_POLYPOLYGON:
        case IDM_RECTANGLE:
        case IDM_ROUNDRECT:
        case IDM_STRETCHBLT:
        {
          //
          // Retrieve the DWORD flag value for the particular menuitem,
          //   toggle (un/check) the menuitem, set/clear the flag in
          //   gdwGraphicsOptions, cause a repaint
          //

          DWORD dwGraphic;

          for (i = 0; i < MAX_GRAPHICS; i++)

            if (LOWORD (wParam) == gaGraphicLookup[i].wMenuItem)
            {
              dwGraphic = gaGraphicLookup[i].dwGraphic;
              break;
            }

          if (GetMenuState (hGraphicsSubMenu, LOWORD(wParam), MF_BYCOMMAND)
              & MF_CHECKED)

          {
            gdwGraphicsOptions &= ~dwGraphic;
            CheckMenuItem (hGraphicsSubMenu, LOWORD(wParam),
                            MF_UNCHECKED | MF_BYCOMMAND);
          }
          else
          {
            //
            // Clear/uncheck the ENUMFONTS flag/menuitem
            //

            gdwGraphicsOptions &= ~ENUMFONTS;
            CheckMenuItem (hGraphicsSubMenu, IDM_ENUMFONTS,
                           MF_UNCHECKED | MF_BYCOMMAND);

            gdwGraphicsOptions |= dwGraphic;
            CheckMenuItem (hGraphicsSubMenu, LOWORD(wParam),
                            MF_CHECKED | MF_BYCOMMAND);
          }
          InvalidateClient ();
          break;
        }

        case IDM_ALLGRAPHICS:

          //
          // Clear/uncheck the ENUMFONTS flag/menuitem, set/check all
          //   other graphics flags/menuitems, cause a repaint
          //

          CheckMenuItem (hGraphicsSubMenu, IDM_ENUMFONTS,
                          MF_UNCHECKED | MF_BYCOMMAND);

          for (i = 0; i < MAX_GRAPHICS; i++)

            CheckMenuItem (hGraphicsSubMenu, IDM_ARC + i,
                           MF_CHECKED | MF_BYCOMMAND);

          gdwGraphicsOptions = ALLGRAPHICS | (gdwGraphicsOptions & DRAWAXIS);

          InvalidateClient ();
          break;

        case IDM_NOGRAPHICS:

          //
          // Clear/uncheck all graphics flags/menuitems, cause a repaint
          //

          for (i = 0; i < MAX_GRAPHICS; i++)

            CheckMenuItem (hGraphicsSubMenu, IDM_ARC + i,
                           MF_UNCHECKED | MF_BYCOMMAND);

          gdwGraphicsOptions &= (DRAWAXIS | ENUMFONTS);

          InvalidateClient ();
          break;

        case IDM_ENUMFONTS:

          //
          // Set/clear ENUMFONTS flag, toggle (un/check) menuitem, if
          //   checking IDM_ENUMFONTS then uncheck all other items,
          //   cause a repaint
          //

          if (GetMenuState (hGraphicsSubMenu, IDM_ENUMFONTS, MF_BYCOMMAND)
                & MF_CHECKED)
          {
            gdwGraphicsOptions &= DRAWAXIS;

            CheckMenuItem (hGraphicsSubMenu, IDM_ENUMFONTS,
                           MF_UNCHECKED | MF_BYCOMMAND);
          }

          else
          {
            SendMessage (hwnd, WM_COMMAND, IDM_NOGRAPHICS, 0);

            gdwGraphicsOptions = ENUMFONTS | (gdwGraphicsOptions & DRAWAXIS);

            CheckMenuItem (hGraphicsSubMenu, IDM_ENUMFONTS,
                           MF_CHECKED | MF_BYCOMMAND);
          }
          InvalidateClient ();
          break;

        case IDM_DRAWAXIS:

          //
          // Set/clear DRAWAXIS flag, toggle (un/check) menuitem,
          //   cause a repaint
          //

          if (GetMenuState (hGraphicsSubMenu, IDM_DRAWAXIS, MF_BYCOMMAND)
                & MF_CHECKED)
          {
            gdwGraphicsOptions &= ~DRAWAXIS;

            CheckMenuItem (hGraphicsSubMenu, IDM_DRAWAXIS,
                           MF_UNCHECKED | MF_BYCOMMAND);
          }

          else
          {
            gdwGraphicsOptions |= DRAWAXIS;

            CheckMenuItem (hGraphicsSubMenu, IDM_DRAWAXIS,
                           MF_CHECKED | MF_BYCOMMAND);
          }
          InvalidateClient ();
          break;

        case IDM_SETPENCOLOR:
        case IDM_SETBRUSHCOLOR:
        case IDM_TEXTCOLOR:
        {
          CHOOSECOLOR cc;

          static DWORD adwCustColors[16];

          memset ((void *) &cc, 0, sizeof (CHOOSECOLOR));

          cc.lStructSize  = sizeof (CHOOSECOLOR);
          cc.hwndOwner    = hwnd;
          cc.Flags        = CC_RGBINIT;
          cc.lpCustColors = adwCustColors;

          if (LOWORD (wParam) == IDM_SETPENCOLOR)

            cc.rgbResult = gdwPenColor;

          else if (LOWORD (wParam) == IDM_SETBRUSHCOLOR)

            cc.rgbResult = gdwBrushColor;

          else

            cc.rgbResult = gdwTextColor;

          //
          // bring up choose color common dialog
          //

          ChooseColor (&cc);

          if (LOWORD (wParam) == IDM_SETPENCOLOR)

            gdwPenColor   = cc.rgbResult;

          else if (LOWORD (wParam) == IDM_SETBRUSHCOLOR)

            gdwBrushColor = cc.rgbResult;

          else

            gdwTextColor  = cc.rgbResult;

          InvalidateClient ();
          break;
        }

        case IDM_PENWIDTH_1:
        case IDM_PENWIDTH_2:
        case IDM_PENWIDTH_3:
        case IDM_PENWIDTH_4:
        case IDM_PENWIDTH_5:
        case IDM_PENWIDTH_6:
        case IDM_PENWIDTH_7:
        case IDM_PENWIDTH_8:

          //
          // uncheck old pen width menuitem, check new one, cause a repaint
          //

          for (i = 0; i < MAX_PENWIDTHS; i++)

            if (giPenWidth == gaPenWidths[i].iPenWidth)
            {
              CheckMenuItem (hPenWidthSubMenu, gaPenWidths[i].wMenuItem,
                             MF_UNCHECKED | MF_BYCOMMAND);
              break;
            }

          for (i = 0; i < MAX_PENWIDTHS; i++)

            if (LOWORD(wParam) == gaPenWidths[i].wMenuItem)
            {
              CheckMenuItem (hPenWidthSubMenu, gaPenWidths[i].wMenuItem,
                             MF_CHECKED | MF_BYCOMMAND);

              giPenWidth = gaPenWidths[i].iPenWidth;

              break;
            }

          InvalidateClient ();
          break;

        case IDM_PENCOLOR_SOLID:
        case IDM_PENCOLOR_DASH:
        case IDM_PENCOLOR_DOT:
        case IDM_PENCOLOR_DASHDOT:
        case IDM_PENCOLOR_DASHDOTDOT:
        case IDM_PENCOLOR_NULL:
        case IDM_PENCOLOR_INSIDEFRAME:

          //
          // uncheck old pen style menuitem, check new one, cause a repaint
          //

          for (i = 0; i < MAX_PENSTYLES; i++)

            if (giPenStyle == gaPenStyles[i].iPenStyle)
            {
              CheckMenuItem (hPenStyleSubMenu, gaPenStyles[i].wMenuItem,
                             MF_UNCHECKED | MF_BYCOMMAND);
              break;
            }

          for (i = 0; i < MAX_PENSTYLES; i++)

            if (LOWORD(wParam) == gaPenStyles[i].wMenuItem)
            {
              CheckMenuItem (hPenStyleSubMenu, gaPenStyles[i].wMenuItem,
                             MF_CHECKED | MF_BYCOMMAND);

              giPenStyle = gaPenStyles[i].iPenStyle;

              break;
            }

          InvalidateClient ();
          break;

        case IDM_BRUSHSTYLE_HORIZONTAL:
        case IDM_BRUSHSTYLE_VERTICAL:
        case IDM_BRUSHSTYLE_FDIAGONAL:
        case IDM_BRUSHSTYLE_BDIAGONAL:
        case IDM_BRUSHSTYLE_CROSS:
        case IDM_BRUSHSTYLE_DIAGCROSS:
        case IDM_BRUSHSTYLE_SOLIDCLR:
        case IDM_BRUSHSTYLE_DITHEREDCLR:
        case IDM_BRUSHSTYLE_SOLIDTEXTCLR:
        case IDM_BRUSHSTYLE_DITHEREDTEXTCLR:
        case IDM_BRUSHSTYLE_SOLIDBKCLR:
        case IDM_BRUSHSTYLE_DITHEREDBKCLR:

          //
          // uncheck old brush style menuitem, check new one, cause a repaint
          //

          for (i = 0; i < MAX_BRUSHSTYLES; i++)

            if (giBrushStyle == gaBrushStyles[i].iBrushStyle)
            {
              CheckMenuItem (hBrushStyleSubMenu, gaBrushStyles[i].wMenuItem,
                             MF_UNCHECKED | MF_BYCOMMAND);
              break;
            }

          for (i = 0; i < MAX_BRUSHSTYLES; i++)

            if (LOWORD(wParam) == gaBrushStyles[i].wMenuItem)
            {
              CheckMenuItem (hBrushStyleSubMenu, gaBrushStyles[i].wMenuItem,
                             MF_CHECKED | MF_BYCOMMAND);

              giBrushStyle = gaBrushStyles[i].iBrushStyle;

              break;
            }

          InvalidateClient ();
          break;

        case ID_COMBOBOX:

          switch (HIWORD(wParam))
          {
            case CBN_SELCHANGE:
            {
              DWORD dwIndex;
              char  buf[BUFSIZE];

              //
              // User clicked on one of the items in the toolbar combobox;
              //   figure out which item, then parse the text apart and
              //   copy it to the gszDriverName, gszDeviceName, and gszPort
              //   variables.
              //

              dwIndex = (DWORD) SendMessage ((HWND) lParam,
                                             CB_GETCURSEL, 0, 0);
              SendMessage ((HWND) lParam, CB_GETLBTEXT, dwIndex,
                           (LONG) buf);

              if (!strcmp (buf, "Display"))
              {
                strcpy (gszDeviceName, "Display");

                gszPort[0]       =
                gszDriverName[0] = '\0';
              }
              else
              { int j = 0;

                i = -1;

                while (buf[++i] != ';') gszDeviceName[i] = buf[i];
                gszDeviceName[i] = '\0';
                while (buf[++i] != ';') gszPort[j++] = buf[i];
                gszPort[j] = '\0';
                j = 0;
                while (buf[++i] != '\0') gszDriverName[j++] = buf[i];
                gszDriverName[j] = '\0';
              }
              break;
            }
          }
          break;
      }
      break;

    case WM_PAINT:
    {
      PAINTSTRUCT ps;
      RECT        rect;
      HRGN        hrgn;
      HPEN        hpen, hpenSave;
      HBRUSH      hbr;
      char        buf[BUFSIZE];
      POINT       p;

      BeginPaint (hwnd, &ps);

      //
      // paint 3d toolbar background & client size text
      //

      GetClientRect (hwnd, &rect);
      rect.bottom = 2*glcyMenu;
      FillRect (ps.hdc, &rect, GetStockObject (LTGRAY_BRUSH));
      SelectObject (ps.hdc, GetStockObject (WHITE_PEN));
      MoveToEx (ps.hdc, 0, 2*glcyMenu - 2, NULL);
      LineTo   (ps.hdc, 0, 0);
      LineTo   (ps.hdc, (int) rect.right, 0);
      hpen = CreatePen (PS_SOLID, 1, 0x808080);
      hpenSave = SelectObject (ps.hdc, hpen);
      MoveToEx (ps.hdc, 0, (int) 2*glcyMenu-1, NULL);
      LineTo   (ps.hdc, (int) rect.right - 1, (int) 2*glcyMenu-1);
      LineTo   (ps.hdc, (int) rect.right - 1, 1);
      SelectObject (ps.hdc, hpenSave);
      DeleteObject (hpen);

      GetClientRect (hwnd, &rect);

      //
      // positioning of the string based upon x,y,cx,cy of combobox
      //

      SetBkMode (ps.hdc, TRANSPARENT);

      p.x = rect.right;
      p.y = (rect.bottom - 2*glcyMenu < 0 ? 0 : rect.bottom - 2*glcyMenu);
      SetMapMode (ps.hdc, giMapMode);
      DPtoLP (ps.hdc, &p, 1);

      if (giMapMode != MM_TEXT && giMapMode != MM_ANISOTROPIC)

        //
        // p.y will come out negative because we started with origin in
        //   upper left corner
        //

        p.y = -p.y;

      SetMapMode (ps.hdc, MM_TEXT);
      sprintf (buf, "cxClient = %ld (%ld)", rect.right, p.x);
      TextOut (ps.hdc, iComboboxWidth + (int) 3*glcyMenu/2, (int) glcyMenu/8,
               buf, strlen (buf));
      sprintf (buf, "cyClient = %ld (%ld)",
               rect.bottom - 2*glcyMenu < 0 ? 0 : rect.bottom - 2*glcyMenu,
               p.y);
      TextOut (ps.hdc, iComboboxWidth + (int) 3*glcyMenu/2,
               (int) (glcyMenu/8 + lTextHeight),
               buf, strlen (buf));

      //
      // paint graphics background white
      //

      rect.top    += 2*glcyMenu;
      FillRect (ps.hdc, &rect, GetStockObject (WHITE_BRUSH));


      if (rect.bottom <= 4*glcyMenu)

        //
        // we don't want to overpaint the toolbar, so just skip Paint() call
        //

        goto done_painting;

      //
      // set up a clip region so we don't draw all over our toolbar
      //

      GetClientRect (hwnd, &rect);
      rect.top += 2*glcyMenu;
      hrgn = CreateRectRgnIndirect (&rect);
      SelectClipRgn (ps.hdc, hrgn);
      DeleteObject (hrgn);

      //
      // set up view port, pens/brushes, & map mode, then paint
      //

      rect.top -= 2*glcyMenu;

      if (giMapMode == MM_TEXT || giMapMode == MM_ANISOTROPIC)

        SetViewportOrgEx (ps.hdc, glcyMenu, 3*glcyMenu, NULL);

      else

        SetViewportOrgEx (ps.hdc, glcyMenu, rect.bottom - glcyMenu, NULL);

      rect.bottom -= 4*glcyMenu;
      rect.right  -= 2*glcyMenu;

      hpen = CreatePen (giPenStyle, giPenWidth, gdwPenColor);
      SelectObject (ps.hdc, hpen);
      hbr  = CreateHatchBrush (giBrushStyle, gdwBrushColor);
      SelectObject (ps.hdc, hbr);

      SetTextColor (ps.hdc, gdwTextColor);

      SetMapMode (ps.hdc, giMapMode);
      Paint      (ps.hdc, &rect);

      DeleteObject (hpen);
      DeleteObject (hbr);

done_painting:

      EndPaint (hwnd, &ps);
      break;
    }

    case WM_CREATE:
    {
      HDC         hdc;
      TEXTMETRIC  tm;
      SIZE        size;
      HMENU       hmenu, hPenSubMenu, hBrushSubMenu;

      //
      // initialize the globals
      //

      glcyMenu = (LONG) GetSystemMetrics (SM_CYMENU);

      hmenu                = GetMenu (hwnd);
      hMappingModesSubMenu = GetSubMenu (hmenu, 1);
      hGraphicsSubMenu     = GetSubMenu (hmenu, 2);
      hPenSubMenu          = GetSubMenu (hmenu, 3);
      hPenWidthSubMenu     = GetSubMenu (hPenSubMenu, 1);
      hPenStyleSubMenu     = GetSubMenu (hPenSubMenu, 2);
      hBrushSubMenu        = GetSubMenu (hmenu, 4);
      hBrushStyleSubMenu   = GetSubMenu (hBrushSubMenu, 1);

      GetTextMetrics ((hdc = GetDC (hwnd)), &tm);
      lTextHeight = tm.tmHeight;

      //
      // create combobox to display current printers in. the width
      //   is caluculated by getting the text extent of a typical
      //   entry in the listbox.
      //


      #define ASTRING "long  printer  name;long  port  name;long  driver  name"

      GetTextExtentPoint (hdc, ASTRING, sizeof (ASTRING), &size);

      iComboboxWidth = (int) size.cx;

      ReleaseDC (hwnd, hdc);

      hwndCombobox = CreateWindow ((LPCSTR) "COMBOBOX", (LPCSTR) "",
                                   WS_CHILD | WS_VISIBLE | CBS_DROPDOWN,
                                   (int) glcyMenu/2,
                                   (int) glcyMenu/2 - 2,  // - 2 = fudge factor
                                   iComboboxWidth,
                                   (int) 6*glcyMenu,
                                   hwnd, NULL, ghInst, NULL);

      SetWindowLong (hwndCombobox, GWL_ID, ID_COMBOBOX);

      PostMessage (hwnd, WM_COMMAND,
                   (WPARAM) MAKELONG (IDM_REFRESH, 0),
                   (LPARAM) 0);
      PostMessage (hwnd, WM_COMMAND,
                   (WPARAM) MAKELONG (IDM_POLYPOLYGON, 0),
                   (LPARAM) 0);
      break;
    }

    case WM_DESTROY:

      PostQuitMessage (0);
      break;

    default:

      return (DefWindowProc (hwnd, msg, wParam, lParam));
  }
  return 0;
}



/******************************************************************************\
*
*  FUNCTION:    AboutDlgProc (standard dialog procedure INPUTS/RETURNS)
*
*  COMMENTS:    Handles "About" dialog messages
*
\******************************************************************************/

LRESULT CALLBACK AboutDlgProc (HWND   hwnd, UINT msg, WPARAM wParam,
                               LPARAM lParam)
{
  switch (msg)
  {
    case WM_INITDIALOG:

      return TRUE;

    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
        case IDOK:

          EndDialog (hwnd, TRUE);

          return 1;
      }
      break;
  }
  return 0;
}



/******************************************************************************\
*
*  FUNCTION:    InvalidateClient
*
*  COMMENTS:    Eliminates the flashing of the toolbar when we redraw
*
\******************************************************************************/

void InvalidateClient ()
{
  RECT rect;

  GetClientRect (ghwndMain, &rect);

  rect.top += 2*glcyMenu;

  InvalidateRect (ghwndMain, &rect, TRUE);
}



/******************************************************************************\
*
*  FUNCTION:    RefreshPrinterCombobox
*
*  INPUTS:      hwndCombobox- handle of the toolbar combobox
*
*  COMMENTS:    The idea here is to enumerate all printers & list them in
*               then combobox in the form: "DEVICE_NAME;PORT;DRIVER_NAME".
*               Then later, when a user selects one of these, we just
*               query out the string & parse it apart, sticking the
*               appropriate parts into the DriverName, DeviceName, and
*               Port variables.
*
*               Also, the "Display" option is added to the combobox so
*               that user can get DevCaps info about it.
*
\******************************************************************************/

void RefreshPrinterCombobox (HWND hwndCombobox)
{
  DWORD            dwFlags = PRINTER_ENUM_FAVORITE | PRINTER_ENUM_LOCAL;
  LPPRINTER_INFO_2 pPrinters;
  DWORD            cbPrinters;
  DWORD            cReturned, i;
  char             buf[256];

  SendMessage (hwndCombobox, CB_RESETCONTENT, 0, 0);

  //
  // add the "Display" option to the combobox
  //

  strcpy (buf, "Display");
  SendMessage (hwndCombobox, CB_INSERTSTRING, (UINT)-1, (LONG) buf);

  //
  // get byte count needed for buffer, alloc buffer, the enum the printers
  //

  EnumPrinters (dwFlags, NULL, 2, NULL, 0, &cbPrinters,
                &cReturned);

  if (!(pPrinters = (LPPRINTER_INFO_2) LocalAlloc (LPTR, cbPrinters + 4)))
  {
    MessageBox (ghwndMain, (LPCTSTR) "LocalAlloc failed",
                (LPCTSTR) ERR_MOD_NAME, MB_OK | MB_ICONEXCLAMATION);
    goto done_refreshing;
  }


  if (!EnumPrinters (dwFlags, NULL, 2, (LPBYTE) pPrinters,
                     cbPrinters, &cbPrinters, &cReturned))
  {
    MessageBox (ghwndMain, (LPCTSTR) "EnumPrinters failed",
                (LPCTSTR) ERR_MOD_NAME, MB_OK | MB_ICONEXCLAMATION);
    goto done_refreshing;
  }

  if (cReturned > 0)

    for (i = 0; i < cReturned; i++)
    {
      //
      // for each printer in the PRINTER_INFO_2 array: build a string that
      //   looks like "DEVICE_NAME;PORT;DRIVER_NAME"
      //

      strcpy (buf, (pPrinters + i)->pPrinterName);
      strcat (buf, ";");
      strcat (buf, (pPrinters + i)->pPortName);
      strcat (buf, ";");
      strcat (buf, (pPrinters + i)->pDriverName);

      SendMessage (hwndCombobox, CB_INSERTSTRING, (UINT)-1, (LONG) buf);
    }

  else

    MessageBox (ghwndMain, "No printers listed", "PRINTER.EXE", MB_OK);

done_refreshing:

  SendMessage (hwndCombobox, CB_SELECTSTRING, (UINT) -1, (LONG) buf);

  PostMessage (ghwndMain, WM_COMMAND,
               (WPARAM) MAKELONG (ID_COMBOBOX, CBN_SELCHANGE),
               (LPARAM) hwndCombobox);

  LocalFree (LocalHandle (pPrinters));
}



/******************************************************************************\
*
*  FUNCTION:    PrintThread
*
*  INPUTS:      wParam - wParam of a WM_COMMAND message containing menuitem id
*
*  COMMENTS:    This is the code for the print thread created when the user
*               selects the "Print" or "PrintDlg" menuitems. A thread is used
*               here more demostration purposes only, since we really don't
*               have any background processing to do. A real app would want
*               to have alot more error checking here (e.g. check return of
*               StartDoc, StartPage...).
*
\******************************************************************************/

void PrintThread (LPVOID wParam)
{
  DOCINFO di;
  RECT    rect;
  HPEN    hpen;
  HBRUSH  hbr;

  switch (LOWORD((WPARAM) wParam))
  {
    case IDM_PRINT:
    {
      if (!strcmp (gszDeviceName, "Display"))
      {
        MessageBox (ghwndMain, "Please select a printer",
                    "PRINTER.EXE:", MB_OK);
        return;
      }
      else if (!(ghdc = CreateDC (gszDriverName, gszDeviceName, gszPort, NULL)))
      {
        MessageBox (ghwndMain, "PrintThread(): CreateDC() failed",
                    ERR_MOD_NAME, MB_OK);
        return;
      }
      break;
    }

    case IDM_PRINTDLG:
    {
      PRINTDLG  pd;

      //
      // Initialize a PRINTDLG struct and call PrintDlg to allow user to
      //   specify various printing options...
      //

      memset ((void *) &pd, 0, sizeof(PRINTDLG));

      pd.lStructSize = sizeof(PRINTDLG);
      pd.hwndOwner   = ghwndMain;
      pd.Flags       = PD_RETURNDC;
      pd.hInstance   = NULL;

      PrintDlg(&pd);
      ghdc = pd.hDC;

      if (pd.hDevMode)

        GlobalFree (pd.hDevMode);

      if (pd.hDevNames)

        GlobalFree (pd.hDevNames);

      if (!ghdc)
      {
        MessageBox (ghwndMain, "PrintDlg (PD_RETURNDC) failed",
                    ERR_MOD_NAME, MB_OK);
        return;
      }
    }
  }

  //
  // put up Abort & install the abort procedure
  //

  gbAbort = FALSE;
  ghwndAbort = CreateDialog (ghInst, (LPCTSTR) "Abort", ghwndMain,
                             (DLGPROC) AbortDlgProc);
  EnableWindow (ghwndMain, FALSE);
  SetAbortProc (ghdc, AbortProc);

  //
  // create & select pen/brush
  //

  hpen = CreatePen (giPenStyle, giPenWidth, gdwPenColor);
  SelectObject (ghdc, hpen);
  hbr  = CreateHatchBrush (giBrushStyle, gdwBrushColor);
  SelectObject (ghdc, hbr);

  SetTextColor (ghdc, gdwTextColor);

  SetMapMode (ghdc, giMapMode);
  rect.top       =
  rect.left      = 0;
  rect.right     = GetDeviceCaps (ghdc, HORZRES);
  rect.bottom    = GetDeviceCaps (ghdc, VERTRES);
  di.cbSize      = sizeof(DOCINFO);
  di.lpszDocName = "print test";
  di.lpszOutput  = NULL;

  StartDoc  (ghdc, &di);
  StartPage (ghdc);

  if (gdwGraphicsOptions)

    Paint (ghdc, &rect);

  else

    TextOut (ghdc, 5, 5, (LPCTSTR) "A blank page!", sizeof("A blank page!"));

  EndPage   (ghdc);
  EndDoc    (ghdc);
  DeleteDC  (ghdc);

  if (!gbAbort)
  {
   EnableWindow  (ghwndMain, TRUE);
   DestroyWindow (ghwndAbort);
  }
}



/******************************************************************************\
*
*  FUNCTION:    AbortProc
*
*  COMMENTS:    Standard printing abort proc
*
\******************************************************************************/

BOOL CALLBACK AbortProc (HDC hdc, int error)
{
  MSG msg;

  while (!gbAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))

    if (!ghwndAbort || !IsDialogMessage (ghwndAbort, &msg))
    {
      TranslateMessage (&msg);
      DispatchMessage (&msg);
    }

  return !gbAbort;
}



/******************************************************************************\
*
*  FUNCTION:    AbortDlgProc (standard dialog procedure INPUTS/RETURNS)
*
*  COMMENTS:    Handles "Abort" dialog messages
*
\******************************************************************************/

LRESULT CALLBACK AbortDlgProc (HWND   hwnd, UINT msg, WPARAM wParam,
                               LPARAM lParam)
{
  switch (msg)
  {
    case WM_INITDIALOG:

      ghwndAbort = hwnd;
      EnableMenuItem (GetSystemMenu (hwnd, FALSE), SC_CLOSE, MF_GRAYED);
      break;

    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
        case DID_CANCEL:

          gbAbort = TRUE;
          AbortDoc (ghdc);
          EnableWindow  (ghwndMain, TRUE);
          DestroyWindow (hwnd);
          return TRUE;
      }
      break;
  }
  return 0;
}
