//===============================================================
// vwinprtr.cpp - Windows Printer class
//
// Copyright (C) 1995,1996,1997,1998  Bruce E. Wampler
//
// This file is part of the V C++ GUI Framework, and is covered
// under the terms of the GNU Library General Public License,
// Version 2. This library has NO WARRANTY. See the source file
// vapp.cxx for more complete information about license terms.
//===============================================================
#include <v/vos2.h>           // for OS/2 stuff
#include <v/vapp.h>
#include <v/vwinprtr.h>
#include <v/vthislst.h>         // For this list

  // call back for the print setup dialog
  MRESULT EXPENTRY PrintDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);

  static vThisList _thisList;           // hide this in this file

  int vWinPrinter::_instances = 0;     // reference counter for copy / assignment

//================>>> vWinPrinter::vWinPrinter <<<========================
  vWinPrinter::vWinPrinter()
  {
    SysDebug1(Constructor,"vWinPrinter::vWinPrinter() constructor (instance=%u)\n",
      _instances )

    ++_instances;            // bump reference counter
    _printhDC = 0;
    _bitmapOn = FALSE;

    _name = "VPrintJob";     // a default name
    _copies = 1;
    _width = 0;
    _height = 0;
    _portrait = 1;
    _useColor = 0;

    _paperType = vPaperDefault;
    _toFile = 0;

    // allocate a block of memory to hold printer information
    // of size 32kB
    DosAllocMem((PPVOID)&_pMem, 0x40000, PAG_READ | PAG_WRITE);
    // allow block to be suballocated into smaller chunks as needed
    // to conserve RAM.
    DosSubSetMem(_pMem, DOSSUB_INIT | DOSSUB_SPARSE_OBJ, 0x3c000);
    _isCopy = FALSE;

    // set default printer properties
    _hPrintDevC = 0;
    _PhyResDpi.cx = 0;
    _PhyResDpi.cy = 0;
    _PrintSize.cx = 0;
    _PrintSize.cy = 0;
    _PhyPagePels.cx = 0;
    _PhyPagePels.cy = 0;
    _PrintOffsetSizel.cx = 0;
    _PrintOffsetSizel.cy = 0;
    _PrintRange = 0;
    _Collate = 0;
    _Priority = 50;

    _pPRQCurrent = 0;
    _SizePRQCurrent = 0;
    _pDDCurrent = 0;
    _SizeDDCurrent = 0;
    _pPRQArray = 0;
    _SizePRQArray = 0;
    _ixCurrent = 0;

    // Query the default printer
    char Buf[MAX_BUFF];

    _SizePRQCurrent = PrfQueryProfileString(HINI_PROFILE, "PM_SPOOLER", "QUEUE",
      0, Buf, MAX_BUFF);
    // if got default printer ok, then build PRQINFO3 structure
    // describing the printer in detail
    if (_SizePRQCurrent)
    {
      // chop off the ';' at end
      PSZ pStr = strchr(Buf, ';');
      *pStr = '\0';

      // how much space do we need?
      SplQueryQueue(0, Buf, 3, 0, 0, &_SizePRQCurrent);

      // make enough room for the printer info
      if ( _SizePRQCurrent &&
        !(DosSubAllocMem(_pMem, (PPVOID) &_pPRQCurrent, _SizePRQCurrent)) )
      {
	SplQueryQueue(0, Buf, 3, _pPRQCurrent, _SizePRQCurrent, &_SizePRQCurrent);
      }

      // If the queue description is a NULL string then use the
      //the queue name so the dialog won't have a blank entry
      if (*_pPRQCurrent->pszComment)
	strcpy(_QueueDesc, _pPRQCurrent->pszComment);
      else
	strcpy(_QueueDesc, _pPRQCurrent->pszName);

      // update queue name
      strcpy(_QueueName, _pPRQCurrent->pszName);

      // load the default printer properties
      GetJobProps(DPDM_QUERYJOBPROP);
    }
  }

//================>>> vWinPrinter::vWinPrinter <<<========================
  vWinPrinter::vWinPrinter(const vWinPrinter& pr)
  {
    // copy constructor
    SysDebug1(Constructor,"vWinPrinter::vWinPrinter() copy constructor (instance=%u)\n",
      _instances )

    ++_instances;               // bump reference counter
    _isCopy = TRUE;             // we are a copy

    _printhDC = pr._printhDC;
    _bitmapOn = pr._bitmapOn;
    _name = pr._name;
    _copies = pr._copies;
    _width = pr._width;
    _height = pr._height;
    _portrait = pr._portrait;
    _useColor = pr._useColor;
    _paperType = pr._paperType;
    _toFile = pr._toFile;
    _pMem = pr._pMem;

    _hPrintDevC = pr._hPrintDevC;

    _PhyResDpi.cx = pr._PhyResDpi.cx;
    _PhyResDpi.cy = pr._PhyResDpi.cy;
    _PrintSize.cx = pr._PrintSize.cx;
    _PrintSize.cy = pr._PrintSize.cy;
    _PhyPagePels.cx = pr._PhyPagePels.cx;
    _PhyPagePels.cy = pr._PhyPagePels.cy;
    _PrintOffsetSizel.cx = pr._PrintOffsetSizel.cx;
    _PrintOffsetSizel.cy = pr._PrintOffsetSizel.cy;
    _PrintRange = pr._PrintRange;
    _Collate = pr._Collate;
    _Priority = pr._Priority ;

    _pPRQCurrent = pr._pPRQCurrent;
    _SizePRQCurrent = pr._SizePRQCurrent;
    _pDDCurrent = pr._pDDCurrent;
    _SizeDDCurrent = pr._SizeDDCurrent;
    _pPRQArray = pr._pPRQArray;
    _SizePRQArray = pr._SizePRQArray;
    strcpy(_QueueDesc, pr._QueueDesc);
    strcpy(_QueueName, pr._QueueName);

  }

//================>>> vWinPrinter::= <<<========================
// note that when this function is called, the assignee has already
// run through its own winprinter constructor and has its own queue,
// memory heap, etc, so need to be careful about what and how we assign
// variables here.
//
// Typically, this is called after setup() but before any printing
// has been started, so there is probably no expectation that the
// print PS will exist (we proceed on that assumption!).

  vWinPrinter& vWinPrinter::operator =(const vWinPrinter& pr)
  {
    SysDebug(Constructor,"vWinPrinter::vWinPrinter() '=' operator\n" )

    if (this == &pr)    // to self?
	return *this;

//    _printhDC = pr._printhDC;      // assignee has its own so don't copy over
    _name = pr._name;
    _copies = pr._copies;
    _width = pr._width;
    _height = pr._height;
    _portrait = pr._portrait;
    _useColor = pr._useColor;
    _paperType = pr._paperType;
    _toFile = pr._toFile;
    _pMem = pr._pMem;
    _isCopy = pr._isCopy;

//    _hPrintDevC = pr._hPrintDevC;  // assignee has its own so don't copy over
    _PhyResDpi.cx = pr._PhyResDpi.cx;
    _PhyResDpi.cy = pr._PhyResDpi.cy;
    _PrintSize.cx = pr._PrintSize.cx;
    _PrintSize.cy = pr._PrintSize.cy;
    _PhyPagePels.cx = pr._PhyPagePels.cx;
    _PhyPagePels.cy = pr._PhyPagePels.cy;
    _PrintOffsetSizel.cx = pr._PrintOffsetSizel.cx;
    _PrintOffsetSizel.cy = pr._PrintOffsetSizel.cy;
    _PrintRange = pr._PrintRange;
    _Collate = pr._Collate;
    _Priority = pr._Priority ;

    // The following are copied to the assignee, but remain distinct.
    // The procedure is the same for each... first delete any existing
    // local data, then allocate new space based on the incoming data
    // size, then copy the data over
    if (_SizePRQCurrent != 0)
    {
      DosSubFreeMem(_pMem, (PPVOID)_pPRQCurrent, _SizePRQCurrent);
      _SizePRQCurrent = 0;
    }
    _SizePRQCurrent = pr._SizePRQCurrent;
    if (_SizePRQCurrent)
    {
      if ( !DosSubAllocMem(_pMem, (PPVOID) &_pPRQCurrent, _SizePRQCurrent) )
	memcpy(_pPRQCurrent, pr._pPRQCurrent, _SizePRQCurrent);
    }

    if (_SizeDDCurrent != 0)
    {
      DosSubFreeMem(_pMem, (PPVOID)_pDDCurrent, _SizeDDCurrent);
      _SizeDDCurrent = 0;
    }
    _SizeDDCurrent = pr._SizeDDCurrent;
    if (_SizeDDCurrent)
    {
      if ( !DosSubAllocMem(_pMem, (PPVOID) &_pDDCurrent, _SizeDDCurrent) )
	memcpy(_pDDCurrent, pr._pDDCurrent, _SizeDDCurrent);
    }

    if (_SizePRQArray != 0)
    {
      DosSubFreeMem(_pMem, (PPVOID)_pPRQArray, _SizePRQArray);
      _SizePRQArray =0;
    }
    _SizePRQArray = pr._SizePRQArray;
    if (_SizePRQArray)
    {
      if ( !DosSubAllocMem(_pMem, (PPVOID) &_pPRQArray, _SizePRQArray) )
	memcpy(_pPRQArray, pr._pPRQArray, _SizePRQArray);
    }

    strcpy(_QueueDesc, pr._QueueDesc);
    strcpy(_QueueName, pr._QueueName);

    return *this;
  }

//================>>> vWinPrinter::~vWinPrinter <<<========================
  vWinPrinter::~vWinPrinter()
  {
    // We are using a reference count to track instances of printers.
    // We want to allow the user to pass copies of the single printer
    // around, but there should only be one name and one stream;
    //
    // If the printer is a copy of another, then we do not delete
    // any heap data to avoid confusing the original 'owner' instance.

    --_instances;               // one less instance!

    SysDebug1(Destructor,"vWinPrinter::~vWinPrinter() destructor (instance=%u)\n",
      _instances)

    if (_printhDC)
    {
      GpiAssociate(_printhDC, NULLHANDLE);
      GpiDestroyPS(_printhDC);
      _printhDC = 0;
      DevCloseDC(_hPrintDevC);
      _hPrintDevC = 0;
    }

    if (_isCopy == TRUE)
      return;                 // don't do anything more

    // deallocate resources for printer info
    if (_SizePRQCurrent != 0)
    {
      DosSubFreeMem(_pMem, (PPVOID)_pPRQCurrent, _SizePRQCurrent);
      _SizePRQCurrent = 0;
    }

    if (_SizeDDCurrent != 0)
    {
      DosSubFreeMem(_pMem, (PPVOID)_pDDCurrent, _SizeDDCurrent);
      _SizeDDCurrent = 0;
    }

    if (_SizePRQArray != 0)
    {
      DosSubFreeMem(_pMem, (PPVOID)_pPRQArray, _SizePRQArray);
      _SizePRQArray =0;
    }

    // free the allocation block
    DosFreeMem((PPVOID)_pMem);

  }

//================>>> vWinPrinter::GetPaperName <<<========================
  char* vWinPrinter::GetPaperName()
  {
    if (_paperType == vPaperLetter)     // we have (8.5x11) added...
	return "Letter";
    else
	return "Unknown";
  }

//====================>>> vWinPrinter::GetWidth <<<========================
//  returns the printable width of the printer canvas in logical units
//  Note: this will be less than the physical page width due to
//  unprintable area of page margins
//
//   Mode controls resolution of PS
//   0  set to 72 dpi resolution (V compatibility mode)
//   1  set to printer native resolution
//

  int vWinPrinter::GetWidth(const int mode)
  {
    if (mode > 0)
    {
      // these units are native printer pels
      return(_PrintSize.cx);
    }
    else
    {
      // we want to scale the PS so that we get 1 logical unit = 1/72 inch (72 dpi)
      return( (_PrintSize.cx * 72 + (_PhyResDpi.cx/2) ) / _PhyResDpi.cx );
    }
  }

//====================>>> vWinPrinter::GetHeight <<<========================
//  returns the printable height of the printer canvas in logical units
//  Note: this will be less than the physical page height due to
//  unprintable area of page margins
//
//   Mode controls resolution of PS
//   0  set to 72 dpi resolution (V compatibility mode)
//   1  set to printer native resolution
//

  int vWinPrinter::GetHeight(const int mode)
  {
    if (mode > 0)
    {
      // these units are native printer pels
      return(_PrintSize.cy);
    }
    else
    {
      // we want to scale the PS so that we get 1 logical unit = 1/72 inch (72 dpi)
      return( (_PrintSize.cy * 72 + (_PhyResDpi.cy/2) ) / _PhyResDpi.cy );
    }
  }

//====================>>> vWinPrinter::GetScale <<<========================
//  returns the scaling needed to achieve the desired resolution
//  This is used to scale the canvas to provide an artificial 72 dpi
//  resolution for the so-called default mode.
//  Alternatively, If a scale of 1 is used, then you get the native printer
//  resolution.
//  (Note: we use the OS/2 FIXED data type so we can handle fractional scaling)
//
//   Mode controls resolution of PS
//   0  set to 72 dpi resolution (V compatibility mode)
//   1  set to printer native resolution
//

  FIXED vWinPrinter::GetScale(const int mode)
  {
    if (mode > 0)
    {
      // these units are native printer pels
      return(MAKEFIXED(1,0));
    }
    else
    {
      // we want to scale the PS so that we get 1 logical unit = 1/72 inch (72 dpi)
      double dpi = 72.0;
      return( DBLTOFX(_PhyResDpi.cy / dpi) );
    }
  }


//================>>> vWinPrinter::Setup <<<========================
  int vWinPrinter::Setup(VCONST char* fn)
  {
    // copies are not allowed to run setup
    // copies can only print to the printer queue they were
    // originally set to.
    if (_isCopy == TRUE)
      return 0;

    // fn is an override default name
    if (fn)
      _name = fn;

    //destroy the PS before running setup again
    if (_printhDC != 0)
      DestroyHDC();

    // this allows user to set printer and printer parms.
    // the global printer parms are then updated before returning
//    int result = WinDlgBox(HWND_DESKTOP, _myHwnd,
//      (PFNWP)&PrintDlgProc, 0L, vID_PRINT_DLG, NULL);
    int result = WinDlgBox(HWND_DESKTOP, HWND_DESKTOP,
      (PFNWP)&PrintDlgProc, 0L, vID_PRINT_DLG, (PVOID) this);

    if (result == DID_OK)
    {
      return DID_OK;
    }
    return 0;    // user cancelled print job
  }



/*-----------------------------------------------------------------

   This function will enumerate the installed print queues and add
   their description to hWndCBox.  The function returns a pointer
   to an array of PPRQINFO3 structures if successful. The number of
   entries in the array and the total size of the allocated memory
   is returned in the _numPRQ and SizePRQArray parameters respectfully.
   The index of the current application default queue is returned in
   the _ixCurrent parameter.

-------------------------------------------------------------------*/
  VOID vWinPrinter::EnumPrintQueues(HWND hWnd)
  {
    ULONG     Returned, numPRQ;
    int       i;
    PSZ       pName;
    HWND hWndCBox = WinWindowFromID(hWnd, vID_PRINTER);

    SysDebug(Constructor,"vWinPrinter::EnumPrintQueues() \n" )
    // get number/size of available printers if first time
    // delete any previous data first
    if (_SizePRQArray != 0)
    {
      DosSubFreeMem(_pMem, (PPVOID)_pPRQArray, _SizePRQArray);
      _SizePRQArray = 0;
      _pPRQArray = 0;
    }
    SplEnumQueue(0, 3, 0, 0L, (PULONG)&Returned, (PULONG)&numPRQ, (PULONG)&_SizePRQArray, 0);

    // suballocate required RAM for printer info and point to it with pPRQArray
    if (!DosSubAllocMem(_pMem, (PPVOID)&_pPRQArray, _SizePRQArray))
    {
      // get printer data
      SplEnumQueue( 0, 3, _pPRQArray, _SizePRQArray, &Returned, &numPRQ, &_SizePRQArray, 0);
      // for performance, we disable the combobox while its list is updated
      WinEnableWindowUpdate(hWndCBox,FALSE);
      WinSendMsg (hWndCBox, LM_DELETEALL,0,0);
      for (i = 0; i < numPRQ; i++)
      {
        // If the queue description is a NULL string then use the
	//the queue name so the dialog won't have a blank entry
        if (*_pPRQArray[i].pszComment)
          pName = _pPRQArray[i].pszComment;
	else
          pName = _pPRQArray[i].pszName;
	// we check for embedded carriage returns and replace with a space
        // if we find any to make the listbox entry more esthetic
        char *ch;
        while ( (ch = strpbrk(pName, "\r\n")) != 0)
	  *ch = ' ';

        WinSendMsg (hWndCBox, LM_INSERTITEM, (MPARAM)LIT_END, (MPARAM)pName);
        // check if it is default queue
	if (strcmp(_QueueDesc, pName) == 0)
        {
	  // This is the current application default
          _ixCurrent = i;
        }
      }
      WinEnableWindowUpdate(hWndCBox, TRUE);
    }
  }


//-----------------------------------------------------------------*\
//
//   This function will query or post job properties for the queue
//   identified in pPRQCurrent. Option should be set to
//   DPDM_QUERYJOBPROP or DPDM_POSTJOBPROP.
//   Space for the corresponding job properties is allocated as needed
//   and _pDDCurrent is set to point to it.
//
//-----------------------------------------------------------------*/
  VOID vWinPrinter::GetJobProps(ULONG Option)
  {
    int   i;
    ULONG sizepPrinters;
    PSZ   pTemp, pPrinters;
    CHAR  DriverName[MAX_DRIVERNAME];
    CHAR  DeviceName[MAX_DEVICENAME];

    SysDebug(Constructor,"vWinPrinter::GetJobProps() \n" )

    // splits "driver.device" into separate driver and device strings
    SplitDotName( _pPRQCurrent->pszDriverName, DriverName, DeviceName);

    // we only want the first printer if queue is pooled
    // so chop off everything after first comma
    sizepPrinters = strlen(_pPRQCurrent->pszPrinters)+1;
    DosSubAllocMem(_pMem, (PPVOID) &pPrinters, sizepPrinters );
    strcpy(pPrinters, _pPRQCurrent->pszPrinters);
    pTemp = (PSZ)strchr(pPrinters, ',');
    if ( pTemp )
      *pTemp = '\0' ;

    if (_SizeDDCurrent == 0)  // first time or printer changed
    {
      // Allocate memory for the driver data by first finding out how
      // much space to allocate
      _SizeDDCurrent = DevPostDeviceModes(theApp->AppHab(), 0, DriverName, DeviceName,
	pPrinters, Option);
      if (_SizeDDCurrent)
      {
	if ( !DosSubAllocMem(_pMem, (PPVOID) &_pDDCurrent, _SizeDDCurrent) )
	{
	  // fill in a few blanks as well
//        _pDDCurrent->cb = _SizeDDCurrent;  // this is unnecesary
//        _pDDCurrent->lVersion = 0;
//        strcpy(_pDDCurrent->szDeviceName, DeviceName);
	  DevPostDeviceModes(theApp->AppHab(), _pDDCurrent, DriverName, DeviceName,
	    pPrinters, Option);
	}
      }
    }
    // get the info
    // previous changes to the job props will be retained
    // from last time if the printer wasn't changed or the
    // instance destructor called.
    else
    {
      DevPostDeviceModes(theApp->AppHab(), _pDDCurrent, DriverName, DeviceName,
	pPrinters, Option);
    }

    // cleanup
    if (sizepPrinters)
      DosSubFreeMem(_pMem, (PPVOID)pPrinters, sizepPrinters);
  }


//-----------------------------------------------------------------
//
//   This function will intialize the capabilities based
//   on the queue pointed to by _pPRQCurrent.
//
//   A new info HDC is created and the the metrics data is
//   refreshed.
//
//-----------------------------------------------------------------
  BOOL vWinPrinter::CreateInfoDC(void)
  {
    // PRDINFO3 Print device information structure (level 3).
    // PSZ       pszPrinterName;  /* Print device name  */
    // PSZ       pszUserName;     /* User who submitted job  */
    // PSZ       pszLogAddr;      /* Logical address (for example LPT1)  */
    // USHORT    uJobId;          /* Identity of current job  */
    // USHORT    fsStatus;        /* Print destination status  */
    // PSZ       pszStatus;       /* Print device comment while printing  */
    // PSZ       pszComment;      /* Print device description  */
    // PSZ       pszDrivers;      /* Drivers supported by print device  */
    // USHORT    time;            /* Time job has been printing (minutes)  */
    // USHORT    usTimeOut;       /* Device timeout (seconds)  */
    //  } PRDINFO3;
    PPRDINFO3    pPrd3;
    ULONG        SizePrd3;

    // DEVOPENSTRUC Open-device data structure.
    // typedef struct _DEVOPENSTRUC {
    // PSZ          pszLogAddress;       /* Logical address  */
    // PSZ          pszDriverName;       /* Driver name  */
    // PDRIVDATA    pdriv;               /* Driver data  */
    // PSZ          pszDataType;         /* Data type  */
    // PSZ          pszComment;          /* Comment  */
    // PSZ          pszQueueProcName;    /* Queue-processor name  */
    // PSZ          pszQueueProcParams;  /* Queue-processor parameters  */
    // PSZ          pszSpoolerParams;    /* Spooler parameters  */
    // PSZ          pszNetworkParams;    /* Network parameters  */
    DEVOPENSTRUC DOP;

    // HCINFO Hardcopy-capabilities structure.
    // CHAR    szFormname[32];  /* Form name  */
    // LONG    cx;              /* Width (left-to-right) in millimeters  */
    // LONG    cy;              /* Height (top-to-bottom) in millimeters  */
    // LONG    xLeftClip;       /* Left clip limit in millimeters  */
    // LONG    yBottomClip;     /* Bottom clip limit in millimeters  */
    // LONG    xRightClip;      /* Right clip limit in millimeters  */
    // LONG    yTopClip;        /* Top clip limit in millimeters  */
    // LONG    xPels;           /* Number of pels between left and right clip limits  */
    // LONG    yPels;           /* Number of pels between bottom and top clip limits  */
    // LONG    flAttributes;    /* Attributes of the form identifier  */
    PHCINFO      pHCInfo;
    LONG         SizeHC;

    HDC          hInfoDevC;
    HPS          hInfoPS;
    LONG         i;
    CHAR         DriverName[MAX_DRIVERNAME];
    CHAR         DeviceName[MAX_DEVICENAME];
    SIZEL        sizel;
    POINTL       PtlRes;
    BOOL         rc = TRUE;

    SysDebug(Constructor,"vWinPrinter::GetInfoDC() \n" )

    // splits "driver.device" into separate driver and device strings
    SplitDotName( _pPRQCurrent->pszDriverName, DriverName, DeviceName);

    SysDebug1(OS2Dev,"CreateInfoDC: DriverName = %s \n", DriverName)
    SysDebug1(OS2Dev,"              DeviceName = %s \n", DeviceName)

    // allocate space for printer device info data
    rc = SplQueryDevice(0, _pPRQCurrent->pszPrinters, 3, 0, 0, &SizePrd3);
//    SysDebug1(OS2Dev,"CreateInfoDC: SplQueryDevice rc = %u \n", rc)

    // we expect to get NERR_BufTooSmall if all is well (since return buffer is NULL)
    if (rc != NERR_BufTooSmall)
    {
      return(FALSE);  // shouldn't happen!
    }

    if ( (rc = DosSubAllocMem(_pMem, (PPVOID)&pPrd3, SizePrd3)) != 0)
    {
      SysDebug1(OS2Dev,"CreateInfoDC: DosSubAllocMem failed rc = %u \n", rc)
      return (FALSE);
    }

    // load printer device data and disassemble
    if ( (rc = SplQueryDevice(0, _pPRQCurrent->pszPrinters, 3,
      (PVOID)pPrd3, SizePrd3, &SizePrd3)) !=0 )
    {
      SysDebug1(OS2Dev,"CreateInfoDC: SplQueryDevice failed rc = %u \n", rc)
      return(FALSE);
    }

    // clear the DOP struct
    memset((PVOID)&DOP, 0, sizeof(DOP));
    DOP.pszLogAddress = pPrd3->pszLogAddr;
    DOP.pszDriverName = DriverName;
    DOP.pdriv         = _pDDCurrent;
    DOP.pszDataType   = "PM_Q_STD";

    SysDebug1(OS2Dev,"CreateInfoDC: pszLogAddress = `%s` \n", pPrd3->pszLogAddr);
    SysDebug1(OS2Dev,"              pszPrinters = `%s` \n", _pPRQCurrent->pszPrinters);
    SysDebug1(OS2Dev,"              DrivData.Devicename = %s \n", _pDDCurrent->szDeviceName);

    // open the info DC and PS to get page metrics
    hInfoDevC = DevOpenDC (theApp->AppHab(), OD_INFO, "*", 4,
      (PDEVOPENDATA) &DOP, 0);
    SysDebug1(OS2Dev,"CreateInfoDC: hInfoDevC = %x \n", hInfoDevC)

    if (hInfoDevC == 0)
    {
      ERRORID err;
      err = WinGetLastError(theApp->AppHab());
      SysDebug1(Build,"vWinPrinter::CreateInfoDC() failed to create hInfoDevC (err=%x) \n",
	err)
      return(FALSE);      // couldn't create OD_INFO
    }
    // otherwise, plow ahead
    else
    {
      sizel.cx = 0;
      sizel.cy = 0;
      // use PU_PELS to get native printer resolution (usually 300dpi res)
      hInfoPS = GpiCreatePS (theApp->AppHab(), hInfoDevC, &sizel,
	GPIA_ASSOC | PU_PELS);
      SysDebug1(OS2Dev,"CreateInfoDC: hInfoPS = %x \n", hInfoPS);
      if(hInfoPS == 0)
      {
	// couldn't create PS
	ERRORID err;
	err = WinGetLastError(theApp->AppHab());
	SysDebug1(Build,"vWinPrinter::CreateInfoDC() failed to create hInfoPS (err=%x) \n", err)
	DevCloseDC(hInfoDevC);
	hInfoDevC = 0;
	return(FALSE);
      }
      else
      {
	// created PS, now get page metrics
	GpiQueryPS(hInfoPS, (PSIZEL)&_PrintSize);  // get page size in pels
        SysDebug2(OS2Dev,"              _PrintSize = (%u, %u) \n", _PrintSize.cx, _PrintSize.cy)

	// now get forms data, but first find out how big structure is
	SizeHC = DevQueryHardcopyCaps(hInfoDevC, 0, 0, 0);

	// query forms data of printer
	if ( (SizeHC > 0) &&
	  (!DosSubAllocMem(_pMem, (PPVOID)&pHCInfo, SizeHC * sizeof(HCINFO))) )
	{
	  DevQueryHardcopyCaps(hInfoDevC, 0, SizeHC, pHCInfo);
	  for (i = 0; i < SizeHC; i++)
	  {
	    // we are only interested in the current form for the printer
	    if (pHCInfo[i].flAttributes & HCAPS_CURRENT)
	    {
	      // Calculate the physical page size (function returns pels/metre)
	      // and store in PtlRes
	      DevQueryCaps (hInfoDevC, CAPS_HORIZONTAL_RESOLUTION, 1L,
		(PLONG) &PtlRes.x);
	      DevQueryCaps (hInfoDevC, CAPS_VERTICAL_RESOLUTION, 1L,
		(PLONG) &PtlRes.y);
	      // compute printer resolution in dpi
	      // note: we use +50 to get correct rounding
	      _PhyResDpi.cx = (LONG) (((double)PtlRes.x) * 2.54 + 50) / 100.;
	      _PhyResDpi.cy = (LONG) (((double)PtlRes.y) * 2.54 + 50) / 100.;
              SysDebug2(OS2Dev,"              _PhyResDpi = (%u, %u) \n", _PhyResDpi.cx, _PhyResDpi.cy)

	      // compute and store total size of form in pels in _PhyPagePels
	      // convert pels/m to pels/mm
	      // multiply pels/mm by form width/height in mm from HCINFO structure
	      // to get total form width/height in pels
	      // note: we use +500 to get correct rounding
	      _PhyPagePels.cx = ((PtlRes.x * pHCInfo[i].cx) + 500)/ 1000;
	      _PhyPagePels.cy = ((PtlRes.y * pHCInfo[i].cy) + 500)/ 1000;
              SysDebug2(OS2Dev,"              _PhyPagePels = (%u, %u) \n", _PhyPagePels.cx, _PhyPagePels.cy)

	      // now do the same for margin offsets (dpi units)
	      _PrintOffsetSizel.cx = ((PtlRes.x * pHCInfo[i].xLeftClip) + 500) / 1000;
	      _PrintOffsetSizel.cy = ((PtlRes.y * pHCInfo[i].yBottomClip) + 500) / 1000;
              SysDebug2(OS2Dev,"              _PrintOffset = (%u, %u) \n", _PrintOffsetSizel.cx, _PrintOffsetSizel.cy)
	      break;
	    }
	  } // for
	}
	else
	{
	  // DevQueryHardcopyCaps failed
	  pHCInfo = 0;
	  SizeHC = 0;
	  rc = 1;
	}
      }
    }

    // cleanup
    if (pPrd3)
      DosSubFreeMem(_pMem, pPrd3, SizePrd3);

    if (pHCInfo)
      DosSubFreeMem(_pMem, pHCInfo, (SizeHC * sizeof(HCINFO)) );

    // Free info DevC and PS
    if (hInfoPS)
    {
      GpiAssociate(hInfoPS, NULLHANDLE);
      GpiDestroyPS(hInfoPS);
      hInfoPS = 0;
      DevCloseDC(hInfoDevC);
      hInfoDevC = 0;
    }
    return rc;
  }

/*-----------------------------------------------------------------*\
   This function will create an OD_QUEUED device context based on the
   contents of the pDDCurrent parameter. If successful a presentation space
   The presentation space is then reset from indexed to RGB mode.
   If a PS already exists, it will be destroyed first.

   Mode controls resolution of PS
   0  set to 72 dpi resolution
   1  set to printer native resolution (usually 300-600 dpi)
\*-----------------------------------------------------------------*/
  HPS vWinPrinter::CreateHDC(const int Mode)
  {
    DEVOPENSTRUC  DOP;
    CHAR          QueueProcParams[32];
    CHAR          SpoolerParams[32];
    CHAR          DriverName[MAX_DRIVERNAME];
    CHAR          DeviceName[MAX_DEVICENAME];
    int           i;
    SIZEL         size;

    SysDebug(Constructor,"vWinPrinter::CreateHDC() \n" )

    // destroy old PS and DevC if they exist
    if (_printhDC)
    {
      GpiAssociate(_printhDC, NULLHANDLE);
      GpiDestroyPS(_printhDC);
      _printhDC = 0;
      DevCloseDC(_hPrintDevC);
      _hPrintDevC = 0;
    }

    // clear the DOP struct
    memset((PVOID)&DOP, 0, sizeof(DOP));

    // Set priority in spooler params
    sprintf((PSZ)SpoolerParams,(PSZ)"PRTY=%d", _Priority);

    // Set number of copies in the queue processor params
    // turn page fit transform off
    // force origin to be 0,0
    // specify color or monochrome
    if (_useColor)
      sprintf((PSZ)QueueProcParams,(PSZ)"COP=%d XFM=0 XLT=1 COL=C", _copies);
    else   // monochrome
      sprintf((PSZ)QueueProcParams,(PSZ)"COP=%d XFM=0 XLT=1 COL=M", _copies);

    SplitDotName( _pPRQCurrent->pszDriverName, DriverName, DeviceName);

    DOP.pszLogAddress      = _QueueName;
    SysDebug1(OS2Dev,"CreateHDC: _QueueName = %s \n", _QueueName);
    DOP.pszDriverName      = DriverName;
    DOP.pdriv              = _pDDCurrent;
    DOP.pszDataType        = "PM_Q_STD";
    DOP.pszComment         = (PSZ) _name;
    DOP.pszQueueProcName = "";
    DOP.pszQueueProcParams = (PSZ)QueueProcParams;
    DOP.pszSpoolerParams   = (PSZ)SpoolerParams;
    DOP.pszNetworkParams = "";

    _hPrintDevC = DevOpenDC(theApp->AppHab(), OD_QUEUED, "*", 9,
      (PDEVOPENDATA) &DOP, 0);

    if (_hPrintDevC == 0)
    {
      ERRORID err;
      err = WinGetLastError(theApp->AppHab());
      SysDebug1(Build,"vWinPrinter::CreateHDC() failed to create _hPrintDevC (err=%x) \n",
	err)
      return 0;
    }

    // DevC created okay, now create the PS
    size.cx =0;     // force PS to max size
    size.cy =0;
    _printhDC = GpiCreatePS(theApp->AppHab(), _hPrintDevC, (PSIZEL) &size,
      GPIA_ASSOC | PU_PELS);

    // check if PS created okay
    if (_printhDC == 0)
    {
      ERRORID err;
      err = WinGetLastError(theApp->AppHab());
      SysDebug1(Build,"vWinPrinter::CreateHDC() failed to create _hPrintDC (err=%x) \n",
	err)
      DevCloseDC(_hPrintDevC);
      _hPrintDevC = 0;
      return 0;
    }

    // PS created okay, now set the canvas to RGB mode
    GpiCreateLogColorTable (_printhDC, 0L, LCOLF_RGB, 0, 0, NULL);

    // Originally I set the model transfor here using the OS/2
    // GpiSetModelTransformMatrix() function, but that only worked
    // for some printer drivers, and many (especially HP laserjet)
    // are completely broken when scaling is used, so we give up on that
    // idea for now and scale everything ourselves.
    if (Mode > 0)
    {
      // native (raw) resolution - no further scaling needed
      _width = _PrintSize.cx;
      _height =_PrintSize.cy;

    }
    else
    {
      // we want to scale the PS so that we get 1 pel = 1/72 inch (72 dpi)
      int dpi = 72;    // target dpi
      _width = (_PrintSize.cx * dpi + (_PhyResDpi.cx/2) ) / _PhyResDpi.cx;
      _height = (_PrintSize.cy * dpi + (_PhyResDpi.cy/2) ) / _PhyResDpi.cy;

    }

    SysDebug2(OS2Dev,"CreateHDC: _width=%u, _height=%u \n", _width, _height)

    // tell the print systems we are beginning a new document
    // we include any document name provided by the app for queue ID
    if (DevEscape (_hPrintDevC, DEVESC_STARTDOC, strlen(_name),
      _name, NULL, NULL) != DEV_OK)
      return 0;
    else
      return( _printhDC);
}

//--------------------------------------------------------------------
//   This function will destroy the print PS and DevC if they exist.
//--------------------------------------------------------------------
  void vWinPrinter::DestroyHDC(VOID)
  {
    SysDebug(Constructor,"vWinPrinter::DestroyHDC() \n" )

    // destroy old OD_QUEUED PS and DevC if they exist
    if (_printhDC)
    {
      GpiAssociate(_printhDC, NULLHANDLE);
      GpiDestroyPS(_printhDC);
      _printhDC = 0;
      DevCloseDC(_hPrintDevC);
      _hPrintDevC = 0;
    }
  }


//--------------------------------------------------------------------
//   This function will issue a NewFrame command to the printer
//   to eject the current page
//--------------------------------------------------------------------
  void vWinPrinter::NewFrame(VOID)
  {
    DevEscape (_hPrintDevC, DEVESC_NEWFRAME, strlen(_name),
      _name, NULL, NULL);
  }


//--------------------------------------------------------------------
//   This function will issue a EndDoc command to the printer
//   to eject the current page
//--------------------------------------------------------------------
  USHORT vWinPrinter::EndDoc(VOID)
  {
    // now finish off the print job
    LONG SizeJobID = sizeof(_jobID);
    DevEscape(_hPrintDevC, DEVESC_ENDDOC, 0, NULL,
      (PLONG) &SizeJobID, (PBYTE) &_jobID);

    // destroy the printer PS and DevC
    if (_printhDC)
    {
      GpiAssociate(_printhDC, NULLHANDLE);
      GpiDestroyPS(_printhDC);
      _printhDC = 0;
      DevCloseDC(_hPrintDevC);
      _hPrintDevC = 0;
    }
    return (_jobID);
  }


//====================>>> vDialog::DynamicDlgProc <<<=======================
  int vWinPrinter::DynamicDlgProc(HWND hwnd, UINT msg,
			    MPARAM mp1, MPARAM mp2)
  {
  RECTL   Rectl;
  LONG    i;
  LONG    Selection;
  char    Temp[MAX_BUFF], *pStr;

  switch(msg)
  {
    case WM_INITDLG:
    {
      // update the dialog status area for the selected printer
      if (_pPRQCurrent->fsStatus == PRQ3_PAUSED)
	sprintf(Temp, "Not Ready,  Queue Paused");
      else if (_pPRQCurrent->fsStatus == PRQ3_PENDING)
	sprintf(Temp, "Not Ready,  Queue Pending Deletion");
      else
	sprintf(Temp, "Ready,  %u Job(s) in Queue", _pPRQCurrent->cJobs);
      WinSetWindowText(WinWindowFromID(hwnd, vID_STATUS), Temp);

      // Device Type (DeviceName)
      PSZ pch = (PSZ) strchr(_pPRQCurrent->pszDriverName,'.');
      if ( pch )
      {
	strcpy(Temp, ++pch);
      }
      else  // some drivers do not have a device name, so use driver name
	strcpy(Temp, (PSZ)_pPRQCurrent->pszDriverName);
      WinSetWindowText(WinWindowFromID(hwnd, vID_TYPE), Temp);

      // Where
//      WinSetWindowText(WinWindowFromID(hwnd, vID_WHERE), _QueueName);
      WinSetWindowText(WinWindowFromID(hwnd, vID_WHERE), _pPRQCurrent->pszPrinters);
      // initialize remaining controls
      switch(_PrintRange)
      {
	case 0:
	  WinCheckButton(hwnd, vID_PRINTALL, 1);
	  break;
        case 1:
          WinCheckButton(hwnd, vID_PRINTPAGES, 1);
	  break;
        case 2:
	  WinCheckButton(hwnd, vID_PRINTSEL, 1);
          break;
      }
      WinCheckButton(hwnd, vID_COLLATE, _Collate);
      WinSendMsg(WinWindowFromID(hwnd, vID_COPIES), SPBM_SETLIMITS,
        (MPARAM) 999, (MPARAM) 1);
      WinSendMsg(WinWindowFromID(hwnd, vID_COPIES),
	SPBM_SETCURRENTVALUE, MPFROMLONG(_copies), (MPARAM) NULL);

      // load combobox with printer names, punt if trouble
      EnumPrintQueues(hwnd);
      // Select the current (default) entry in the list box
      WinSendMsg (WinWindowFromID(hwnd, vID_PRINTER), LM_SELECTITEM, MPFROMSHORT(_ixCurrent), MPFROMSHORT(TRUE));
    }
    break;

  case WM_CONTROL:
  {
    switch (SHORT1FROMMP(mp1))
    {
      case vID_PRINTER:
      {
        switch (SHORT2FROMMP(mp1))
	{
          // selected printer from combobox
          case CBN_ENTER:
//          case CBN_LBSELECT:
	  {
	    // get new printer selection
            Selection = (ULONG)WinSendMsg(WinWindowFromID(hwnd, vID_PRINTER),
              LM_QUERYSELECTION, MPFROMSHORT(LIT_FIRST),0);
            if (Selection != LIT_NONE)
            {
              // get QueueDesc for selection
              if (*_pPRQArray[Selection].pszComment)
              {
		pStr = _pPRQArray[Selection].pszComment;
              }
	      else
              {
		pStr = _pPRQArray[Selection].pszName;
	      }

	      // if it is not the current queue, then we must update info
              if (strcmp(_QueueDesc, pStr) != 0)
              {
                // update current queue name
                strcpy(_QueueDesc, pStr);
                // delete the old queue info and replace with new stuff
		if (_SizePRQCurrent != 0)
                {
                  DosSubFreeMem(_pMem, (PPVOID)_pPRQCurrent, _SizePRQCurrent);
                  _SizePRQCurrent = 0;
                  _pPRQCurrent = 0;
		}
                // how much space do we need?
		SplQueryQueue(0, _pPRQArray[Selection].pszName, 3, 0, 0, &_SizePRQCurrent);
                // make enough room for the printer info
		if (_SizePRQCurrent && !(DosSubAllocMem(_pMem, (PPVOID) &_pPRQCurrent, _SizePRQCurrent)))
                {
                  SplQueryQueue(0, _pPRQArray[Selection].pszName, 3, _pPRQCurrent,
                    _SizePRQCurrent, &_SizePRQCurrent);
                }
                // update queue name
                strcpy(_QueueName, _pPRQCurrent->pszName);
                // JobProps will reallocate mem for DD if its null
                // De-allocate memory for the driver data
                if (_SizeDDCurrent)
                {
		  DosSubFreeMem(_pMem, _pDDCurrent, _SizeDDCurrent);
                  _pDDCurrent = 0;
		  _SizeDDCurrent = 0;
                }
		GetJobProps(DPDM_QUERYJOBPROP);
	      }
	    }
            // update the dialog status area for the selected printer
	    if (_pPRQCurrent->fsStatus == PRQ3_PAUSED)
	      sprintf((char*)Temp, "Not Ready,  Queue Paused");
            else if (_pPRQCurrent->fsStatus == PRQ3_PENDING)
              sprintf(Temp, "Not Ready,  Queue Pending Deletion");
            else
	      sprintf(Temp, "Ready,  %u Jobs in Queue", _pPRQCurrent->cJobs);
            WinSetWindowText(WinWindowFromID(hwnd, vID_STATUS), Temp);
            // Queue Type
	    PSZ pch = (PSZ) strchr(_pPRQCurrent->pszDriverName,'.');
	    if ( pch )
	    {
	      strcpy(Temp, ++pch);
	    }
	    else  // some drivers do not have a device name, so use driver name
	      strcpy(Temp, (PSZ)_pPRQCurrent->pszDriverName);
	    WinSetWindowText(WinWindowFromID(hwnd, vID_TYPE), Temp);

	    // Where
//	    WinSetWindowText(WinWindowFromID(hwnd, vID_WHERE), _QueueName);
	    WinSetWindowText(WinWindowFromID(hwnd, vID_WHERE), _pPRQCurrent->pszPrinters);
	    return 0;
	  }
	}
      }
    }
  }
  case WM_COMMAND:
  {
    switch (SHORT1FROMMP(mp1))
    {
      // pushed the properties button
      case vID_JOBPROPS:
      {
        GetJobProps(DPDM_POSTJOBPROP);
        return 0;
      }

      case DID_OK:
      {
	// delete _pPRQArray structure
        if (_SizePRQArray != 0)
        {
          DosSubFreeMem(_pMem, (PPVOID)_pPRQArray, _SizePRQArray);
          _SizePRQArray = 0;
          _pPRQArray = 0;
	}
        // update remaining control vars
	WinSendDlgItemMsg( hwnd, vID_COPIES, SPBM_QUERYVALUE,
          &_copies, MPFROM2SHORT(0, SPBQ_UPDATEIFVALID));
        _Collate = WinQueryButtonCheckstate(hwnd, vID_COLLATE);
	if ( WinQueryButtonCheckstate(hwnd, vID_PRINTALL) )
          _PrintRange = 0;
	else if ( WinQueryButtonCheckstate(hwnd, vID_PRINTPAGES) )
	  _PrintRange = 1;
	else if ( WinQueryButtonCheckstate(hwnd, vID_PRINTSEL) )
          _PrintRange = 2;

        // create INFO DC to update physical capabiliies of current printer
        CreateInfoDC();
        // now return 1 to the calling program
        break;
      }

      case DID_CANCEL:
      {
	// delete _pPRQArray structure
	if (_SizePRQArray != 0)
	{
	  DosSubFreeMem(_pMem, (PPVOID)_pPRQArray, _SizePRQArray);
          _SizePRQArray = 0;
          _pPRQArray = 0;
        }
        // create INFO DC to update physical capabiliies of current printer
        CreateInfoDC();
        // abort printing if pressed cancel
	// now return 2 to the calling program
	break;
      }
    }
  }
  default:
    break;
  }
  return (int) WinDefDlgProc(hwnd, msg, mp1, mp2);
}   /* End of DynamicDlgProc   */


//====================>>> DlgProc <<<=======================
MRESULT EXPENTRY PrintDlgProc(HWND hDlg, ULONG uMsg,MPARAM mp1, MPARAM mp2)
{
  vWinPrinter* useThis;
  if (uMsg == WM_INITDLG) // first time!
    {
      useThis = (vWinPrinter*)mp2;

      _thisList.Add((ThisId)hDlg, (void*)useThis);
    }
  else
    useThis = (vWinPrinter*)_thisList.GetThis((ThisId)hDlg);
  if (!useThis)
    return WinDefDlgProc(hDlg, uMsg, mp1, mp2);

  return (MRESULT) useThis->DynamicDlgProc(hDlg, uMsg, mp1, mp2);
}

//====================>>> vWinPrinter::SplitDotName <<<=======================
//
// function to split device and driver names from unidot format
//
  void vWinPrinter::SplitDotName(PSZ DotName, PSZ DriverName, PSZ DeviceName)
  {
    SysDebug(OS2Dev,"vWinPrinter::SplitDotName() \n" )

    // splits "driver.device" into separate driver and device strings
    strcpy(DriverName, DotName);
    PSZ pch = (PSZ) strchr(DriverName,'.');
    if ( pch )
    {
      *pch = '\0';
      strcpy(DeviceName, ++pch);
    }
    else  // some drivers do not have a device name
      DeviceName[0] = '\0';

  }


