/************ FILE C Sample Program Source Code File (.C) *******************
 *
 * PROGRAM NAME: GRAPHIC
 * -------------
 *  Presentation Manager Non-Retained Graphics C Sample Program
 *
 * WHAT THIS PROGRAM DEMONSTRATES:
 * -------------------------------
 * Following are all the functions needed to print "GPI", "METAFILE"
 * in a separate thread.
 *
 *  Copyright (C) 1991 IBM Corporation
 *
 *	DISCLAIMER OF WARRANTIES.  The following [enclosed] code is
 *	sample code created by IBM Corporation. This sample code is not
 *	part of any standard or IBM product and is provided to you solely
 *	for  the purpose of assisting you in the development of your
 *	applications.  The code is provided "AS IS", without
 *	warranty of any kind.  IBM shall not be liable for any damages
 *	arising out of your use of the sample code, even if they have been
 *	advised of the possibility of such damages.						       *
 ******************************************************************************/

/* Include the required sections of the PM header file */

#include <stdlib.h>
#include <string.h>

#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES
#define INCL_WINWINDOWMGR
#define INCL_WINSHELLDATA
#define INCL_GPICONTROL
#define INCL_WINPOINTERS
#define INCL_GPIMETAFILES
#define INCL_GPICORRELATION
#define INCL_WINMESSAGEMGR
#define INCL_GPIREGIONS
#define INCL_WINRECTANGLES
#define INCL_GPITRANSFORMS
#define INCL_GPIPRIMITIVES
#define INCL_WINERRORS
#define INCL_GPILCIDS
#define INCL_WINDIALOGS
#define INCL_DEV
#define INCL_WINSTDDLGS
#define INCL_DOSERRORS
#define INCL_GPIERRORS

#include <os2.h>
#include <malloc.h>
#include <sys\types.h>
#include <sys\stat.h>
#include "graphic.h"

#define EXPORT_BUFFER_SIZE 1000

/* static function definitions */
static	 VOID	    PreparePrinting(PHPS hpsPrint,
				    PHDC hdcPrinter,
				    HAB habPrint,
				    PSIZEL sizlPS,
				    PRESOLUTION Resolution,
				    PPRTTXT_DATA PrintData,
				    PSIZEL DevCaps);
static	 VOID	    MetaOutput(PDRAW_PARM PrtParm,
			       PPRTTXT_DATA PrintData,
			       HAB habPrint);
static	 int _Optlink SortQueues(const void *Queue1, const void *Queue2);
static	 PSZ	    GetQueueNamesString(PULONG);
static	 CHAR	    pm_spooler_queue_dd[] = "PM_SPOOLER_QUEUE_DD";
static	 CHAR	    Drive[]={0,0,0};

/*********************************************************************
 *
 *  Name:   OpenFileDialog
 *
 *  Purpose: open the standard file open dialog as file extention
 *	     and return the filename
 *
 *  Usage:   called when the user needs to supply a name for
 *	     the file to be opened
 *
 *  Method:  calls the standard file open dialog to get the
 *	     file name.
 *
 *  Parameters: HWD hwnd	 Handle of the owner window.
 *		PSZ szTitle	 Title of open dialog.
 *		PSZ pszFileExt	 File extention. (for example : *.txt)
 *		PSZ pszFullPath	 PSZ for returning the file name.
 *
 *  Returns: TRUE if successful in getting a file name, FALSE
 *		if not in pushing CANCEL
 *	     PSZ pszFullPath pointer to filename (full path)
 *
 **********************************************************************/
BOOL OpenFileDialog(HWND hwndOwner,
		    PSZ szTitle,
		    PSZ szFileExt,
		    PSZ szFullPath)
{
   static   PSZ	       ppszdefaultdrivelist[] = {NULL};

	    FILEDLG    fdg;		  /* file dialog structure	     */

   fdg.cbSize = sizeof(FILEDLG);	  /* Size of FILEDLG.		     */
   fdg.pszTitle = szTitle;		  /* String to display in title bar. */
   fdg.pszOKButton = (PSZ)"Open";	  /* String to display in OK button. */
   fdg.ulUser = 0L;			  /* User defined field.	     */
   fdg.fl = FDS_HELPBUTTON |		  /* FDS flags.			     */
	    FDS_CENTER |
	    FDS_OPEN_DIALOG;
   fdg.pfnDlgProc = (PFNWP)TemplateOpenFilterProc;/* Entry point to custom*/
						  /*	     dialog proc. */
   fdg.lReturn = 0L;			  /* Result code from dialog dismissal. */
   fdg.lSRC = 0L;			  /* System return code.	  */
   fdg.hMod = 0;			  /* Custom file dialog template. */
   fdg.usDlgId = 0;			  /* Custom file dialog ID.	  */
   fdg.x = 0;				  /* X coordinate of the dialog.  */
   fdg.y = 0;				  /* Y coordinate of the dialog.  */

   /* set selected fully qualified path */
   strcpy( fdg.szFullFile, szFileExt);

   fdg.pszIType = 0L;			  /* Pointer to string containing   */
					  /*   Initial EA type filter.	    */
   fdg.papszITypeList = 0L;		  /* Pointer to table of pointers   */
					  /*  that point to null terminated */
   if (!Drive[0])			  /*  Type strings.		    */
       fdg.pszIDrive = 0L;		  /* Pointer to string containing   */
   else					  /*  initial drive.		    */
       fdg.pszIDrive = Drive;

   fdg.papszIDriveList = (PAPSZ)ppszdefaultdrivelist;
					  /* Pointer to table of pointers   */
					  /*  that point to null terminated */
					  /*  Drive strings.		    */
   fdg.sEAType = 0;			  /* Selected file's EA Type.       */
   fdg.papszFQFilename = 0;		  /* Pointer to table of pointers   */
					  /*  point to null terminated	    */
					  /*  FQFname strings.		    */
   fdg.ulFQFCount = 0;			  /* Numbers of file selected.	    */

   /* get the file */

   if (!WinFileDlg(HWND_DESKTOP, hwndOwner, (PFILEDLG)&fdg))
      return FALSE;

   if (fdg.lReturn == DID_CANCEL)
      return FALSE;

   /* copy file name into file name buffer */

   strcpy(szFullPath, fdg.szFullFile);
   strcpy(szFileExt, fdg.szFullFile);
   strncpy(Drive,fdg.szFullFile,2);	    /* Always Contains Drive letter */

   return TRUE;
} /* End of OpenFileDialog */

/**************************************************************************
 * Function: DrawThread
 *
 * DrawThread is the asynchronous drawing thread.  It performs all
 * drawing, whether to the screen or to the printer.
 * It obtains an anchor block handle and creates a message queue.
 * It then posts a private message to the main thread's client area
 * if successful. NewThread monitors its own message queue, and
 * processes the commands it receives.
 *
 * Parameters: PMAIN_PARM   main parameter structure
 *
 * Result:  none
 *
 ***************************************************************************/
VOID APIENTRY DrawThread(PMAIN_PARM Parm)
{
static	 CHAR	    DrawThreadErrorTitle[] = "Draw Thread Error:";
static	 CHAR	    DrawThreadError1[] = "WinInitialize failed!";
static	 CHAR	    DrawThreadError2[] = "Cannot Create Message Queue.";
	 QMSG	    qmsgAsync;	    /* Message structure for WinGetMsg	*/
	 QMSG	    qmsgPeek;	    /* Message structure for WinPeekMsg */
	 BOOL	    fFlushQueue;    /* Flush-queue indicator		*/
	 BOOL	    fQueueFlush;
	 RECTL	    rclClient;	    /* Bounding rect for picture xform	*/
	 BOOL	    Loop = TRUE;
static	 PSZ	    dcdatablk[9] = {0,
				    "MEMORY", /* display driver	     */
				    0, 0, 0, 0, 0, 0, 0 };

  /*
   * Obtain an anchor block handle. If this fails, the thread and
   * the application are terminated.
   */

   if ((Parm->DrawParm.hABDraw = WinInitialize((USHORT)0)) == (HAB)0L)
   {
      WinPostMsg(Parm->hWndFrame,
	      WM_USER_END_ON_ERROR,
	      MPFROMP(DrawThreadErrorTitle),
	      MPFROMP(DrawThreadError1));
      DosExit(EXIT_THREAD, 0);
   }

  /*
   * Create a message queue. If this fails, the thread and the
   * application are terminated.
   */

   if ((Parm->hMQDraw = WinCreateMsgQueue(Parm->DrawParm.hABDraw, 0)) == (HMQ)0L)
   {
      WinTerminate(Parm->DrawParm.hABDraw);
      WinPostMsg(Parm->hWndFrame,
	      WM_USER_END_ON_ERROR,
	      MPFROMP(DrawThreadErrorTitle),
	      MPFROMP(DrawThreadError2));
      DosExit(EXIT_THREAD, 0);
   }

  /*
   * The SetTransform function is invoked to set up the transformation
   * for the default picture. The main thread's priority is adjusted
   * to be higher than that of the asynchronous thread. (GPI CIRCLES)
   */

   WinPostMsg(Parm->hWndFrame,
	     WM_COMMAND,
	     MPFROMP(IDM_F_GPI),
	     MPFROMP(NULL));
   DosSetPriority(PRTYS_THREAD, PRTYC_NOCHANGE, 1L, 0L);
   while (Loop)
   {

   /*
    * The semaphore hsemStoppable is obtained so that the flag
    * fWaiting can be set to TRUE. This indicates that the thread is
    * waiting for a message, and prevents the main thread from
    * issuing GpiSetStopDraw.
    * To be sure that it will not be suspended, the asynchronous
    * thread sets the stop-draw condition off. It then reads its
    * message queue.
    */

      WinRequestMutexSem(Parm->hmtxsemStoppable, (ULONG)-1);
      Parm->fWaiting = TRUE;
      DosReleaseMutexSem(Parm->hmtxsemStoppable);

      if (Parm->hpsClient != (HPS)0L)
	 GpiSetStopDraw(Parm->hpsClient, (LONG)FALSE);

      WinGetMsg(Parm->DrawParm.hABDraw, &qmsgAsync, (HWND)NULLHANDLE, 0L, 0L);

   /*
    * When a message has been read from the queue, the flag fWaiting
    * is set the FALSE. This allows the main thread to issue the
    * GpiSetStopDraw call if necessary.
    */

      WinRequestMutexSem(Parm->hmtxsemStoppable, (ULONG)-1);
      Parm->fWaiting = FALSE;
      DosReleaseMutexSem(Parm->hmtxsemStoppable);

   /*
    * Before doing any drawing, the thread examines its message queue
    * to see if it contains any other commands that would force
    * redrawing. If there is a flush command in the queue, all
    * commands, up to and including the flush command, are read from
    * the queue. A SET_TRANSFORM message is treated like a flush
    * command, because it also means that a new picture is required.
    */

      fFlushQueue = WinPeekMsg(Parm->DrawParm.hABDraw,
			    &qmsgPeek,
			    0L,
			    FLUSH_COMMAND,
			    FLUSH_COMMAND,
			    PM_NOREMOVE);

      fQueueFlush = WinPeekMsg(Parm->DrawParm.hABDraw,
			    &qmsgPeek,
			    0L,
			    SET_TRANSFORM,
			    SET_TRANSFORM,
			    PM_NOREMOVE);

      if (fFlushQueue|| fQueueFlush)
	 do
	 {
	    WinGetMsg(Parm->DrawParm.hABDraw, &qmsgAsync, 0L, 0, 0);
	 } while (qmsgAsync.msg != FLUSH_COMMAND &&
		  qmsgAsync.msg != SET_TRANSFORM &&
		  qmsgAsync.msg != STOP_DRAWING
		  ); /* enddo */

      if (WinPeekMsg(Parm->DrawParm.hABDraw,
		  &qmsgPeek,
		  NULLHANDLE,
		  STOP_THREAD,
		  DUMMY_COMMAND,
		  PM_NOREMOVE))
	 Parm->DrawParm.fDrawing = FALSE;
      else
	 Parm->DrawParm.fDrawing = TRUE;

   /* Process the commands.   */

      switch (qmsgAsync.msg)
      {
       case SET_TRANSFORM:
	    /*
	     * Invoke the SetTransform function to define a default viewing
	     * transformation for the picture.
	     */
	   if (*Parm->Message != '\0')
	      break;
	   SetTransform(Parm, qmsgAsync.mp1);
	   break;

       case DRAW_ORDERS:
	    /*
	     * Invoke the DoDraw function. DoDraw uses the region definition
	     * passed to it to repaint the invalidated area of the screen.
	     * The region definition is then destroyed.
	     */
	   if (Parm->CheckedItem == 3)
	   {
	      break;
	   }
	   else
	   {
	      if (*Parm->Message != '\0')
		 break;
	      DoDraw(Parm->hpsClient, &Parm->DrawParm, (HRGN)qmsgAsync.mp1,
		     Parm->InfoWnd.hWnd);
	      if (!Parm->DrawParm.hmfPicture)
		 TextWithGpi(Parm->hpsClient);
	      if (qmsgAsync.mp1 != (MPARAM)NULL)
		 GpiDestroyRegion(Parm->hpsClient, (HRGN)qmsgAsync.mp1);

	      break;
	   }
      case DISPLAY_MESSAGE:
	   DisplayMessage(Parm->hWndClient,
			  Parm->Message);
	   break;

      case SIZING:
	   /* Recalculate the picture transformation. */

	   if (*Parm->Message != '\0')
	      break;
	   WinQueryWindowRect(Parm->hWndClient, &rclClient);
	   Transform(&Parm->DrawParm, Parm->hpsClient, &rclClient);
	   break;

      case ACTIVATE_DRAW_THREAD:
	   WinPostMsg(Parm->hWndFrame,
		      ACTIVATE_DRAW_THREAD,
		      MPFROMSHORT(0),
		      MPFROMSHORT(0));
	   break;

      case STOP_DRAWING:
	   Parm->DrawThreadActivated = FALSE;
	   DosPostEventSem(Parm->hevsemStop);
	   break;

      case STOP_THREAD:
	   /* StopThread is invoked to terminate the thread. */
	   Loop = FALSE;
	   break;

      case FLUSH_COMMAND:
	   /*
	    * Other commands have already been discarded. Set the fAllValid
	    * flag to TRUE.
	    */
	   Parm->DrawParm.fAllValid = TRUE;
	   break;
      }
   }

   DosSetPriority(PRTYS_THREAD, PRTYC_IDLETIME, 0L, 0L);

   WinDestroyMsgQueue(Parm->hMQDraw);  /* Destroy this thread's message queue */

   WinTerminate(Parm->DrawParm.hABDraw);

   DosPostEventSem(Parm->hevsemTerminate);

   DosExit(EXIT_THREAD, 0);
} /* End of DrawThread */

/**************************************************************************
 * Function: SendCommand
 *
 * The SendCommand function posts the required command and its
 * parameters to the asynchronous drawing thread's message queue.
 *
 * Parameters: PMAIN_PARM   main parameter structure
 *	       usCommand    command to send
 *	       ULONG	    long value passed to draw thread as parameter1
 *
 * Result:  BOOL	    TRUE is ok
 *
 ***************************************************************************/
BOOL SendCommand(PMAIN_PARM Parm, ULONG ulCommand, ULONG ulInfo)
{
   APIRET     Ret;

   if (Parm->hMQDraw == (HMQ)NULLHANDLE)
      return FALSE;
 /*
  * Communication between the main thread and the asynchronous thread
  * is controlled by the two flags fWaiting and fStoppable. Access to
  * the two flags is regulated by the semaphore hsemStoppable.
  * The main thread requests access to the semaphore, and waits until
  * it is available. If this command is one that causes drawing, the
  * main thread checks whether the asynchronous thread is waiting for
  * a message. If it is not waiting, the main thread issues
  * GpiSetStopDraw to suspend drawing. It then frees the semaphore,
  * and posts the message to the asynchronous thread's message queue.
  */
   do
   {
      Ret = WinRequestMutexSem(Parm->hmtxsemStoppable, (ULONG)-1);
   } while (Ret == (APIRET)ERROR_INTERRUPT);

   if (ulCommand <= DUMMY_COMMAND)
      if (!Parm->fWaiting)
	 GpiSetStopDraw(Parm->hpsClient, (ULONG)Parm->fStoppable);

   DosReleaseMutexSem(Parm->hmtxsemStoppable);

   return(WinPostQueueMsg(Parm->hMQDraw,
		       ulCommand,
		       MPFROMLONG(ulInfo),
		       (MPARAM)NULL));
} /* End of SendCommand */

/**************************************************************************
 * Function: SetTransform
 *
 * The SetTransform function calculates the original default viewing
 * transformation for the picture.  This function can be interrupted
 * only by another request to set the default viewing transformation,
 * or by a request to terminate the thread. SetTransform invokes the
 * DoDraw function to draw the picture, and then calculates the
 * bounding rectangle of the picture.
 * It then invokes the Transform function to set the appropriate
 * transformation.
 *
 * Parameters: PMAIN_PARM   main parameter structure
 *	       MPARAM	    message parameter  passed from SendCommand
 *
 * Result:  none
 *
 ***************************************************************************/
VOID SetTransform(PMAIN_PARM Parm, MPARAM mp1)
{
   LONG	      lRc;
   RECTL      rclDest;
   RECTL      rclClient;
   POINTL     ptlWork;
   RECTL      rclWork;

   GpiErase(Parm->hpsClient);
   WinInvalidateRect(Parm->InfoWnd.hWnd, (PRECTL)NULL, TRUE);

   DisplayMessage(Parm->hWndClient,
		  GetString(Parm->DrawParm.hABDraw,
			    ((HMF)mp1 == (HMF)NULLHANDLE) ?
				 TEXT_DRAWTHR_SIZEGPI : TEXT_DRAWTHR_SIZEMETA,
			    STATIC_STRING));

   Parm->fBusy = TRUE;
   if (WinQueryPointerPos(HWND_DESKTOP, &ptlWork))
       if (WinMapWindowPoints(HWND_DESKTOP, Parm->hWndClient, &ptlWork, 1))
	   if (WinQueryWindowRect(Parm->hWndClient, &rclWork))
	       if (WinPtInRect(Parm->DrawParm.hABDraw, &rclWork, &ptlWork))
		   WinSetPointer(HWND_DESKTOP, Parm->hptrGlass);

   if (Parm->DrawParm.hmfPicture != (HMF)NULLHANDLE)
      GpiDeleteMetaFile(Parm->DrawParm.hmfPicture);

   /* The current metafile is deleted before the next one is drawn.	*/
   Parm->DrawParm.hmfPicture = (HMF)mp1;

   WinRequestMutexSem(Parm->hmtxsemStoppable, (ULONG)-1);
   Parm->fStoppable = FALSE;
   DosReleaseMutexSem(Parm->hmtxsemStoppable);

   /*
    * The boundary data is cleared so that the transformation and the
    * presentation space are not reset after the GpiPlayMetaFile call.
    */
   Parm->DrawParm.rclBounds.xRight  = 0xFFFF;
   Parm->DrawParm.rclBounds.xLeft   = 0xFFFF;
   Parm->DrawParm.rclBounds.yTop    = 0xFFFF;
   Parm->DrawParm.rclBounds.yBottom = 0xFFFF;
   GpiResetBoundaryData(Parm->hpsClient);
   /*
    * The presentation space is disassociated from the device context.
    * Boundary-data accumulation is switched on, and the DoDraw
    * function is invoked to draw the picture into the disassociated
    * device context. The picture is drawn, but not displayed. The
    * boundary data is retrieved, and the presentation space is
    * reassociated with the device context. Boundary-data accumulation
    * is switched off.
    */
   GpiAssociate(Parm->hpsClient, (HDC)NULLHANDLE);
   GpiSetDrawControl(Parm->hpsClient, DCTL_BOUNDARY, DCTL_ON);

   GpiSetStopDraw(Parm->hpsClient, (ULONG)Parm->fStoppable);
   Parm->DrawParm.fDrawing = TRUE;
   lRc = DoDraw(Parm->hpsClient, &Parm->DrawParm, (HRGN)NULLHANDLE,
				Parm->InfoWnd.hWnd);

   GpiQueryBoundaryData(Parm->hpsClient, &Parm->DrawParm.rclBounds);
   GpiAssociate(Parm->hpsClient, Parm->hdcClient);
   GpiSetDrawControl(Parm->hpsClient, DCTL_BOUNDARY, DCTL_OFF);
   /*
    * The presentation space is reset so that subsequent
    * GpiPlayMetaFile calls will succeed.
    */
   GpiResetPS(Parm->hpsClient, GRES_ALL);
   /*
    * If the DoDraw function was successful, the Transform function is
    * invoked to set the transformation. WinInvalidateRect invalidates
    * the client area and causes a WM_PAINT message to be issued.
    * The title bar is also updated.
    */
   if (lRc == DRAW_OK)
   {
      WinQueryWindowRect(Parm->hWndClient, &rclClient);
      Transform(&Parm->DrawParm, Parm->hpsClient, &rclClient);
      WinRequestMutexSem(Parm->hmtxsemStoppable, (ULONG)-1);
      Parm->fNotReady = FALSE;
   /*
    * The application can now process paint and erase-background
    * requests.
    */
      DosReleaseMutexSem(Parm->hmtxsemStoppable);
      WinQueryWindowRect(Parm->hWndClient, &rclDest);
      WinInvalidateRect(Parm->hWndClient, &rclDest, FALSE);
   }
   else
   {
      strcpy(Parm->Message,
	     GetString(Parm->DrawParm.hABDraw,
		       ERRMSG_DRAWTHR_MSG2,
		       STATIC_STRING));
      strcat(Parm->Message, Parm->MetafilePath);
      DisplayMessage(Parm->hWndClient, Parm->Message);
      Parm->fNotReady = FALSE;
   }
   /*
    * If the DoDraw function failed, the main thread is prompted to
    * display a warning message and issue another SET_TRANSFORM
    * message for the default picture.
    */
   Parm->fBusy = FALSE;
   if (WinQueryPointerPos(HWND_DESKTOP, &ptlWork))
       if (WinMapWindowPoints(HWND_DESKTOP, Parm->hWndClient, &ptlWork, 1))
	   if (WinQueryWindowRect(Parm->hWndClient, &rclWork))
	       if (WinPtInRect(Parm->DrawParm.hABDraw, &rclWork, &ptlWork))
		   WinSetPointer(HWND_DESKTOP, Parm->hptrArrow);

   /* Allow GpiSetStopDraw to interrupt subsequent drawing.		*/
   WinRequestMutexSem(Parm->hmtxsemStoppable, (ULONG)-1);
   Parm->fStoppable = TRUE;
   DosReleaseMutexSem(Parm->hmtxsemStoppable);
   return;
} /* End of SetTransform */

/**************************************************************************
 * Function: WndProcPaint
 *
 * The WndProcPaint function is invoked from the window procedure
 * ClientWndProc to process the WM_PAINT message. WndProcPaint
 * in turn invokes the asynchronous thread to perform the window
 * painting. If the asynchronous thread has both a drawing and a
 * transformation to process, a region is created that is equal to the
 * update region. This is passed to the asynchronous thread and the
 * region is validated. Otherwise, no painting occurs.
 * If the asynchronous thread does not have time to paint the updated
 * regions, it accumulates them.
 *
 * Parameters: PMAIN_PARM   main parameter structure
 *
 * Result:  MRESULT	    message result
 *
 ***************************************************************************/
MRESULT WndProcPaint(PMAIN_PARM Parm)
{
   HRGN	     hrgnUpdt;
   LONG	     sRgnType;
   APIRET    Ret;
   ULONG     MsgTyp;

   do
   {
      Ret = WinRequestMutexSem(Parm->hmtxsemStoppable, (ULONG)-1);
   } while (Ret == (APIRET)ERROR_INTERRUPT);

   hrgnUpdt = GpiCreateRegion(Parm->hpsPaint, 0L, (PRECTL)NULL);
   sRgnType = WinQueryUpdateRegion(Parm->hWndClient, hrgnUpdt);
   if (!Parm->fNotReady)
   {
      if (*Parm->Message != '\0')
	 MsgTyp = DISPLAY_MESSAGE;
      else
	 MsgTyp = DRAW_ORDERS;

      DosReleaseMutexSem(Parm->hmtxsemStoppable);
      if ((sRgnType == RGN_RECT) ||
	  (sRgnType == RGN_COMPLEX))
      {
	 if (!SendCommand(Parm, MsgTyp, 0L))
	    GpiDestroyRegion(Parm->hpsPaint, hrgnUpdt);
	 WinValidateRegion(Parm->hWndClient, hrgnUpdt, FALSE);
      }
      else
      {
	 SendCommand(Parm, MsgTyp, 0L);
	 WinValidateRegion(Parm->hWndClient, hrgnUpdt, FALSE);
	 GpiDestroyRegion(Parm->hpsPaint, hrgnUpdt);
      } /* endif */
   }
   else
   {
      DosReleaseMutexSem(Parm->hmtxsemStoppable);
      WinValidateRegion(Parm->hWndClient, hrgnUpdt, FALSE);
      GpiDestroyRegion(Parm->hpsPaint, hrgnUpdt);
   } /* endif */
   return(FALSE);
} /* End of WndProcPaint */

/**************************************************************************
 * Function: Transform
 *
 * The Transform function recalculates the default viewing
 * transformation when the picture is scrolled or sized, and when the
 * 'Best Fit' option is changed.
 * If the window handle is NULL, the default viewing transformation is
 * applied to ensure that the picture is contained within the page
 * and that no graphics clipping occurs for printing.
 *
 * Parameters: PDRAW_PARM   draw parameter structure
 *	       HPS	    presentation space handle for drawing
 *	       PRECTL	    rectangle
 *
 * Result:  none
 *
 ***************************************************************************/
VOID Transform(PDRAW_PARM Parm, HPS hpsXForm, PRECTL prclXform)
{
	 LONG	    lxPictRight, lxPictLeft;
	 LONG	    lyPictTop, lyPictBottom; /* picture boundary information */
	 LONG	    clxPict, clyPict;	     /* picture extents		     */
	 POINTL	    ptlPictCenter;	     /* center of picture	     */
	 LONG	    clxBounds, clyBounds;    /* bounds area extent	     */
	 LONG	    alScale[2];		     /* scaling factors		     */
	 LONG	    sxWork, syWork;	     /* work areas		     */
	 LONG	    lxWork, lyWork;	     /* work areas		     */
static	 MATRIXLF   matlfViewTransform = {65536,0,0,0,65536,0,0,0,1};

/* Calculate the center of the bounding rectangle of the picture. */
   lxPictRight = Parm->rclBounds.xRight + 5;
   lxPictLeft = Parm->rclBounds.xLeft - 5;
   lyPictTop = Parm->rclBounds.yTop + 5;
   lyPictBottom = Parm->rclBounds.yBottom - 5;

   ptlPictCenter.x = (lxPictRight - lxPictLeft)/2 + lxPictLeft;
   ptlPictCenter.y = (lyPictTop - lyPictBottom)/2 + lyPictBottom;


   /* Get the picture extents. */
   clxPict = lxPictRight - lxPictLeft;
   clyPict = lyPictTop - lyPictBottom;

   /* Get the current bounding rectangle extent. */
   clxBounds = prclXform->xRight - prclXform->xLeft;
   clyBounds = prclXform->yTop - prclXform->yBottom;

   /* Calculate the x/y ratio of the current bounding area. */
   sxWork = (LONG)(clyBounds/clxBounds);
   lxWork = clyBounds%clxBounds;
   alScale[0] = MAKEULONG((USHORT)((lxWork * 0x10000)/clxBounds), sxWork);

   /* Calculate the x/y ratio of the picture. */
   syWork = clyPict/clxPict;
   lyWork = clyPict%clxPict;
   alScale[1] = MAKEULONG((USHORT)((lyWork * 0x10000)/clxPict), syWork);
   /*
    * Compare the two ratios to determine the dimension along which
    * scaling is to be provided, so that the picture fits into the
    * area and its aspect ratio is preserved.  The same scaling is
    * then applied along both the x and y axes.
    */
   if (alScale[0] > alScale[1])
   {
      sxWork = clxBounds/clxPict;
      lxWork = clxBounds%clxPict;
      alScale[0] = MAKEULONG((USHORT)((lxWork * 0x10000)/clxPict), sxWork);
      alScale[1] = alScale[0];
   }
   else
   {
      syWork = clyBounds/clyPict;
      lyWork = clyBounds%clyPict;
      alScale[1] = MAKEULONG((USHORT)((lyWork * 0x10000)/clyPict), syWork);
      alScale[0] = alScale[1];
   }

   GpiScale(hpsXForm,
	    &matlfViewTransform,
	    TRANSFORM_REPLACE,
	    (PFIXED)alScale,
	    &ptlPictCenter);
   /*
    * Translate the center of the picture to the center of the
    * bounding area, based on the page origin.
    */
   ptlPictCenter.x = (prclXform->xRight - prclXform->xLeft)/2 - ptlPictCenter.x;
   ptlPictCenter.y = (prclXform->yTop - prclXform->yBottom)/2 - ptlPictCenter.y;

   GpiTranslate(hpsXForm,
		&matlfViewTransform,
		TRANSFORM_ADD,
		&ptlPictCenter);

   /* Now set the default viewing transform. */
   GpiSetDefaultViewMatrix(hpsXForm,
			   9L,
			   &matlfViewTransform,
			   TRANSFORM_REPLACE);
/*
 * If the picture has been transformed for the screen, we need to
 * reset the scroll bar thumb size, so convert the picture height
 * from world to page coordinates and together with the rect area
 * height send the visible/total sizes to the scroll bar.
 */
} /* End of Transform */

/*************************************************************************
 * Function: DoDraw
 *
 * The DoDraw function draws the metafile contents. The supplied
 * update rectangle is used to define a clipping region, so that the
 * amount of drawing required is restricted.
 *
 * Parameters: HPS	    presentation space handle for drawing
 *	       PDRAW_PARM   draw parameter structure
 *	       HRGN	    region for	drawing
 *	       HWND	    window handle of information window
 *
 * Result:  LONG	    return code
 *
 ***************************************************************************/
LONG DoDraw(HPS hpsDraw, PDRAW_PARM Parm, HRGN rgn, HWND InfoWnd)
{
	 LONG	    lRc;
	 LONG	    lOptionCount;
	 LONG	    alOptionArray[8];
	 LONG	    lSegCount;
	 LONG	    lDescLen;
	 CHAR	    achDescBuff[251];
	 LONG	    lSaveMode;
	 HRGN	    hrgnOld;
	 MATRIXLF   matlfSaveTransform;
	 FIXED	    fxSize;
static	 AREABUNDLE PatternAttr =
		    {CLR_BACKGROUND, 0L, 0, 0, 0, 0, 0L};
static	 POINTL	    ptlCurr = {0, 0};

   if (rgn != (HRGN)0L)
   {
  /*
   * A valid region has been passed to DoDraw. Depending on the
   * setting of the flag fAllValid, the region is either copied or
   * is combined with the current region using a logical OR
   * operation.
   */
      if (Parm->fAllValid)
      {
	 GpiCombineRegion(hpsDraw,
		       Parm->hrgnInvalid,
		       rgn,
		       Parm->hrgnInvalid,
		       CRGN_COPY);
	 Parm->fAllValid = FALSE;
      }
      else
      {
	 GpiCombineRegion(hpsDraw,
		       Parm->hrgnInvalid,
		       Parm->hrgnInvalid,
		       rgn,
		       CRGN_OR);
      } /* endif */
   } /* endif */

  /* Stop here if the flag fDrawing is set to FALSE. */
   if (!Parm->fDrawing)
      return(FALSE);
 /*
  * If an invalid region exists, define a clipping region and clear
  * the area within the region. If there is no invalid region, the
  * whole client area is cleared.
  */
   if (rgn != (HRGN)NULLHANDLE)
   {
      lSaveMode = GpiQueryAttrMode(hpsDraw);
      GpiSetAttrMode(hpsDraw, AM_PRESERVE);
      GpiSetAttrs(hpsDraw,
	       PRIM_AREA,
	       ABB_COLOR,
	       0L,
	       (PBUNDLE)&PatternAttr);
      GpiPaintRegion(hpsDraw, Parm->hrgnInvalid);
      GpiSetAttrMode(hpsDraw, lSaveMode);
      GpiPop(hpsDraw, 1L);
      GpiSetClipRegion(hpsDraw, Parm->hrgnInvalid, &hrgnOld);
   }
   else
   {
      GpiErase(hpsDraw);
      if (InfoWnd != (HWND)NULLHANDLE)
      WinInvalidateRect(InfoWnd, (PRECTL)NULL, TRUE);
   } /* endif */
 /*
  * If a metafile has been loaded, it is played into the presentation
  * space. If GpiPlayMetaFile is successful, the setting of the
  * stop-draw flag is checked. If the flag is set, it is assumed that
  * GpiPlayMetaFile did not complete successfully.
  * If no metafile has been loaded, a simple default picture is drawn.
  */
if (Parm->hmfPicture != (HMF)NULLHANDLE)
   {
   lDescLen = 250;
   lOptionCount = 8L;
   alOptionArray[PMF_SEGBASE] = 0L;
   alOptionArray[PMF_LOADTYPE] = LT_DEFAULT;
   alOptionArray[PMF_RESOLVE] = RS_DEFAULT;
   alOptionArray[PMF_LCIDS] = LC_LOADDISC;
   alOptionArray[PMF_RESET] = RES_DEFAULT;
   alOptionArray[PMF_SUPPRESS] = SUP_DEFAULT;
   alOptionArray[PMF_COLORTABLES] = CTAB_REPLACE;
   alOptionArray[PMF_COLORREALIZABLE] = CREA_DEFAULT;
   lRc = GpiPlayMetaFile(hpsDraw,
			 Parm->hmfPicture,
			 lOptionCount,
			 (PLONG)alOptionArray,
			 &lSegCount,
			 lDescLen,
			 (PSZ)&achDescBuff[0]
			 );
   if (lRc == GPI_OK)
      if (LOUSHORT(WinGetLastError(Parm->hABDraw)) ==
	  PMERR_STOP_DRAW_OCCURRED)
	 lRc = DRAW_STOPPED;
     /*
      * Reset the presentation space, including the default viewing
      * transformation, and then reapply the required transformation.
      * This is to ensure that the metafile can be replayed without
      * causing errors due to stored segments or new character sets,
      * for example. The presentation space is not reset if called
      * from the SetTransform function, as that function needs the
      * accumulated boundary information.
      */
   if (Parm->rclBounds.xRight != 0xFFFF)
      {
      GpiQueryDefaultViewMatrix(hpsDraw,
				9L,
				&matlfSaveTransform);
      GpiResetPS(hpsDraw, GRES_ALL);
      GpiSetDefaultViewMatrix(hpsDraw,
			      9L,
			      &matlfSaveTransform,
			      TRANSFORM_REPLACE);
      }
   }
else
   {
   GpiSetColor(hpsDraw, CLR_BLACK);
   fxSize = 0x100000;
   if (Circles(hpsDraw, &ptlCurr, fxSize))
      {
      lRc = DRAW_OK;
      }
   else
      {
      lRc = DRAW_STOPPED;
      }
   } /* endif */

  /* Clear any clipping region defined earlier. */
   if (rgn != (HRGN)NULLHANDLE)
      GpiSetClipRegion(hpsDraw, (HRGN)NULLHANDLE, &hrgnOld);
    /*
     * If the drawing failed, the flag fAllValid is not set. This
     * ensures that, the next time through, the region will be combined
     * with the new invalid region.
     */
   if (lRc == DRAW_OK)
      if (rgn != (HRGN)NULLHANDLE)
	 Parm->fAllValid = TRUE;
   return(lRc);
} /* End of DoDraw */

/*************************************************************************
 * Function: TextWithGpi
 *
 *  This function draws text into the GPI area.
 *
 * Parameters: HPS	    presentation space handle for drawing
 *
 * Result:  none
 *
 ***************************************************************************/
VOID TextWithGpi(HPS hps)
{
	 POINTL	    ptlText;
static	 CHAR	    Text[] = "ITSC Boca Raton";

   GpiSetColor(hps, CLR_DARKPINK);
   ptlText.x = -16L;
   ptlText.y = 17L;
   GpiCharStringAt(hps, &ptlText, (LONG)(sizeof(Text) - 1), Text);
} /* End of TextWithGpi */

/*************************************************************************
 * Function: Circles
 *
 *  The Circles function draws a circle and then calls itself to
 *  draw four further circles within the first one. It will
 *  repeat this until the circles reach a certain size or if StopDraw
 *  has been set for the presentation space.
 *
 * Parameters: HPS	    presentation space handle for drawing
 *	       PPOINTL	    point  structure
 *	       FIXED	    size
 *
 * Result:  BOOL	    return code, TRUE if ok
 *
 ***************************************************************************/
BOOL Circles(HPS hpsDraw, PPOINTL pptlCur, FIXED fxSize)
{
	 LONG	   i;
	 POINTL	    ptlWork;
	 FIXED	    fxWork;
static	 LONG	    xOff[4] = {0, 1, 0, -1};
static	 LONG	    yOff[4] = {1, 0, -1, 0};
static	 LONG	    ColorTab[] =
		    {
		    CLR_BLACK,
		    CLR_RED,
		    CLR_GREEN,
		    CLR_BLUE
		    };

  /* Don't continue if StopDraw is set */
   if (GpiQueryStopDraw(hpsDraw) == SDW_ON)
      return(FALSE);
  /* Don't continue if the circles are getting too small */
   if (fxSize < 0x10000)
      return(TRUE);
  /* Draw a circle of the required size at the required position. */
   GpiSetCurrentPosition(hpsDraw, pptlCur);
   GpiFullArc(hpsDraw, DRO_OUTLINE, fxSize);
 /*
  * Now draw a further 4 circles, half the current size, within the
  * one just drawn.
  */
   fxWork = fxSize >> 1;
   for (i = 0; i < 4; i++)
   {
      ptlWork.x = pptlCur->x + xOff[i]*(FIXEDINT(fxSize)>>1);
      ptlWork.y = pptlCur->y + yOff[i]*(FIXEDINT(fxSize)>>1);
      GpiSetColor(hpsDraw, ColorTab[i]);
      if (!Circles(hpsDraw, &ptlWork, fxWork))
	 return(FALSE);
   }
   return(TRUE);
} /* End of Circles */

/*************************************************************************
 * Function: DisplayMessage
 *
 * Display a message in the client area.
 *
 * Parameters: HWND	    window handle
 *	       PSZ	    message to display
 *
 * Result:  none
 *
 ***************************************************************************/
VOID DisplayMessage(HWND hwndDraw, PSZ Message)
{
   RECTL      rDraw;
   PFONTMETRICS FontMetrics;
   POINTL     MessagePos;
   HPS	      hpsDraw;

   WinQueryWindowRect(hwndDraw, &rDraw);	       /* query window size */

   hpsDraw = WinGetPS(hwndDraw);

   WinFillRect(hpsDraw, &rDraw, CLR_BACKGROUND);	  /* clear window   */

   FontMetrics = (PFONTMETRICS)malloc(sizeof(FONTMETRICS)); /* query fontsize */

   GpiQueryFontMetrics(hpsDraw, (LONG)sizeof(FONTMETRICS), FontMetrics);

   MessagePos.x = 4L;					/* set message position*/
   MessagePos.y = rDraw.yTop - FontMetrics->lMaxAscender;

   GpiCharStringAt(hpsDraw, &MessagePos,		     /* display message*/
		   (LONG)strlen(Message), Message);

   WinReleasePS(hpsDraw);

   free((PVOID)FontMetrics);
} /* End of DisplayMessage */

/**************************************************************************
 * Function: PrintGpiMeta
 *
 * Create a structure with the printer specific data and send a message to
 * the print thread to print GPI or METAFILE.
 *
 * Parameters: PMAIN_PARM   main parameter structure
 *	       ULONG	    identifier if GPI or METAFILE
 *
 * Result:  none
 *
 ***************************************************************************/
VOID PrintGpiMeta(PMAIN_PARM Parm, ULONG Message)
{
   PPRTTXT_DATA PrintData;
   PDRAW_PARM PrtParm;
   /* definitions for print */

   /* Allocate Space for PrintData */
   PrintData = malloc(sizeof(PRTTXT_DATA));

   /* Allocate Space for PrtParm which is used in Draw-Functions */
   PrtParm = (PDRAW_PARM)malloc(sizeof(DRAW_PARM));

   /* now copy all additional information to the print structure */
   strcpy(PrintData->QueueName, Parm->QueueName);
   strcpy(PrintData->DriverName, Parm->DriverName);

   /* Allocate space for DriverData */
   PrintData->DriverData = malloc(Parm->DriverDataLength);

   memcpy(PrintData->DriverData, Parm->DriverData, Parm->DriverDataLength);
   memcpy((PSZ)PrtParm, (PSZ)&Parm->DrawParm, sizeof(DRAW_PARM));
   PrtParm->fDrawing = TRUE;
   PrintData->hWndFrame = Parm->hWndFrame;

   strcpy(PrintData->Jobname, ((Message == DRAW_GPI) ? "GPI" : "METAFILE"));

   /* now send the print thread a message to print GPI or METAFILE */
   WinPostQueueMsg(Parm->hMQPrint, Message,
		MPFROMP(PrintData), MPFROMP(PrtParm));
} /* End of PrintGpiMeta */

/*************************************************************************
 * Function: PrintThread
 *
 * PrintThread is the asynchronous printing thread.  It performs all
 * printing, text and graphic.
 * It obtains an anchor block handle and creates a message queue.
 *
 * Parameters: PMAIN_PARM   main parameter structure
 *
 * Result:  none
 *
 ***************************************************************************/
VOID APIENTRY PrintThread(PMAIN_PARM Parm)
{
	 HAB	    habPrint;
static	 CHAR	    PrintThreadErrorTitle[] = "Print Thread Error:";
static	 CHAR	    PrintThreadError1[] = "WinInitialize failed!";
static	 CHAR	    PrintThreadError2[] = "Cannot Create Message Queue.";
	 BOOL	    Loop = TRUE;
	 QMSG	    qmsgAsync;	    /* Message structure for WinGetMsg	*/
 /*
  * Obtain an anchor block handle. If this fails, the thread and
  * the application are terminated.
  */
   if ((habPrint = WinInitialize(0L)) == (HAB)NULLHANDLE)
   {
      WinPostMsg(Parm->hWndFrame,
	      WM_USER_END_ON_ERROR,
	      MPFROMP(PrintThreadErrorTitle),
	      MPFROMP(PrintThreadError1));
      DosExit(EXIT_THREAD, 0);
   }
 /*
  *Create a message queue. If this fails, the thread and the
  * application are terminated.
  */
   if ((Parm->hMQPrint = WinCreateMsgQueue(habPrint, 0L)) == (HMQ)NULLHANDLE)
   {
      WinTerminate(habPrint);
      WinPostMsg(Parm->hWndFrame,
	      WM_USER_END_ON_ERROR,
	      MPFROMP(PrintThreadErrorTitle),
	      MPFROMP(PrintThreadError2));
      DosExit(EXIT_THREAD, 0);
   }

   DosSetPriority(PRTYS_THREAD, PRTYC_NOCHANGE, 1L, 0L);

   while (Loop)
   {
      WinGetMsg(habPrint, &qmsgAsync, (HWND)NULLHANDLE, 0L, 0L);
      switch (qmsgAsync.msg)
      {
      case STOP_THREAD:		       /* end thread	    */
	   Loop = FALSE;
	   break;
      case DRAW_GPI:		       /* print GPI/METAFILE   */
      case DRAW_META:
	   MetaOutput((PDRAW_PARM)PVOIDFROMMP(qmsgAsync.mp2),
		      (PPRTTXT_DATA)PVOIDFROMMP(qmsgAsync.mp1),
		      habPrint);
	   break;
      }
   }

   DosSetPriority(PRTYS_THREAD, PRTYC_IDLETIME, 0L, 0L);

   WinDestroyMsgQueue(Parm->hMQPrint); /* Destroy this thread's message queue */

   WinTerminate(habPrint);

   DosPostEventSem(Parm->hevsemPrintEnds);

   DosExit(EXIT_THREAD, 0);
} /* End of PrintThread */

/**************************************************************************
 * Function: PreparePrinting
 *
 * Create device context and presentation space for printing
 *
 * Parameters: PHPS	    presentations space handle for printing(return)
 *	       PHDC	    device context handle for printing (return)
 *	       HAB	    anchor block handle of the print process
 *	       PSIZEL	    size of the presentation space (return)
 *	       PRESOLUTION  resolution of the device (return)
 *	       PPRTTXT_DATA print data structure
 *	       PSIZEL	    device capabilities
 *
 * Result:  none
 *
 ***************************************************************************/
static VOID PreparePrinting(PHPS hpsPrint,
			    PHDC hdcPrinter,
			    HAB habPrint,
			    PSIZEL sizlPS,
			    PRESOLUTION Resolution,
			    PPRTTXT_DATA PrintData,
			    PSIZEL DevCaps)
{
	 DEVOPENSTRUC dopPrinter;
	 ULONG	   i1;
static	 CHAR	    Comment[] = "Graphic";
	 LONG	    alDevInfo[DEVINFOSIZE];

   /* initialize DEVOPENSTRUC */
   memset((PVOID)&dopPrinter, '\0', sizeof(DEVOPENSTRUC));
   dopPrinter.pszLogAddress = PrintData->QueueName;
   i1 = strcspn(PrintData->DriverName, ".");
   PrintData->DriverName[i1] = '\0';
   dopPrinter.pszDriverName = PrintData->DriverName;
   dopPrinter.pdriv = (PDRIVDATA)PrintData->DriverData;
   dopPrinter.pszDataType = NULL;

   dopPrinter.pszComment = Comment;
   /* Open device context for printer */
   *hdcPrinter = DevOpenDC(habPrint,
			   OD_QUEUED,
			   (PSZ)"*",
			   5L,
			   (PDEVOPENDATA)&dopPrinter,
			   (HDC)NULLHANDLE);
   /* Query Height and Width of the printer */

   DevQueryCaps(*hdcPrinter,
		0L,
		(LONG)DEVINFOSIZE,
		alDevInfo);

   sizlPS->cx = (alDevInfo[CAPS_WIDTH] * 10000) /
		alDevInfo[CAPS_HORIZONTAL_RESOLUTION];

   sizlPS->cy = (alDevInfo[CAPS_HEIGHT] * 10000) /
		alDevInfo[CAPS_VERTICAL_RESOLUTION];

   /* return device capabilities if pointer not NULL */
   if (DevCaps != (PSIZEL)NULL)
      {
      DevCaps->cx = alDevInfo[CAPS_WIDTH];
      DevCaps->cy = alDevInfo[CAPS_HEIGHT];
      }

   Resolution->x = (alDevInfo[CAPS_HORIZONTAL_FONT_RES] * 10000) /
			   alDevInfo[CAPS_HORIZONTAL_RESOLUTION];
   Resolution->y = (alDevInfo[CAPS_VERTICAL_FONT_RES] * 10000) /
			   alDevInfo[CAPS_VERTICAL_RESOLUTION];

   /* create presentation space */
   *hpsPrint = GpiCreatePS(habPrint,
			   *hdcPrinter,
			   sizlPS,
			   PU_LOMETRIC | GPIA_ASSOC | GPIT_NORMAL);
} /* End of PreparePrinting */

/**************************************************************************
 * Function: MetaOutput
 *
 * Create spoolfile for GPI or METAFILE.
 *
 * Parameters: PDRAW_PARM   draw parameter structure
 *	       PPRTTXT_DATA print parameter structure
 *	       HAB	    anchor block handle of the print process
 *
 * Result:  none
 *
 ***************************************************************************/
static VOID MetaOutput(PDRAW_PARM PrtParm,
		       PPRTTXT_DATA PrintData,
		       HAB habPrint)
{
   HDC	      hdcPrinter;
   RESOLUTION Resolution;
   SIZEL      sizlPS;
   HPS	      hpsPrint;
   ULONG      JobNr;
   ULONG      Dummy;
   RECTL      rclPrinter;

   PrtParm->hABDraw = habPrint;

   /* create device context and presentations space for printing */
   PreparePrinting(&hpsPrint, &hdcPrinter, habPrint,
		   &sizlPS, &Resolution, PrintData, (PSIZEL)NULL);

   /* disable menuitem "Print" */
   WinPostMsg(PrintData->hWndFrame, SET_PRINT_STATUS, MPFROMSHORT(1),
	      MPFROMLONG(hpsPrint));

   /* Start Document */
   DevEscape(hdcPrinter,
	     DEVESC_STARTDOC,
	     (LONG)strlen(PrintData->Jobname),
	     (PSZ)PrintData->Jobname,
	     0L,
	     (PSZ)NULL);

   rclPrinter.xLeft = rclPrinter.yBottom = 0;
   rclPrinter.xRight = sizlPS.cx;
   rclPrinter.yTop = sizlPS.cy;

   /* draw GPI/METAFILE to the print presentation space */
   Transform(PrtParm, hpsPrint, &rclPrinter);

   if (DoDraw(hpsPrint, PrtParm, (HRGN)0L, (HWND)0) != DRAW_OK)
      DevEscape(hdcPrinter,
		DEVESC_ABORTDOC,
		(LONG)0,
		(PSZ)NULL,
		(PLONG)NULL,
		(PSZ)NULL);
   else
      {
      if (!PrtParm->hmfPicture)
	 TextWithGpi(hpsPrint);
      DevEscape(hdcPrinter, DEVESC_NEWFRAME,
		0L, (PSZ)NULL, (PLONG)NULL, (PSZ)NULL);
      DevEscape(hdcPrinter,
		DEVESC_ENDDOC,
		(LONG)0,
		(PSZ)NULL,
		(PLONG)&Dummy,
		(PSZ)&JobNr);
      }

   GpiAssociate(hpsPrint, (HDC)NULLHANDLE);
   GpiDestroyPS(hpsPrint);
   DevCloseDC(hdcPrinter);

   /* Release Data fields */
   free((PVOID)PrintData->DriverData);

   WinPostMsg(PrintData->hWndFrame, SET_PRINT_STATUS, MPFROMSHORT(0),
	      MPFROMLONG(0L));

   free((PVOID)PrintData);
} /* End of MetaOutput */

/*************************************************************************
 * Function: QueryPrinterQueue
 *
 * query all printer queues, display them in a listbox an do a selection.
 *
 * Parameters: HAB	    anchor block handle of main process
 *	       HWND	    window handle of frame window
 *	       PVOID	    pointer to driver data structure
 *	       PULONG	    length of driver data
 *	       PSZ	    (pre)selected queue name
 *	       PSZ	    (pre)selected printer driver name
 *
 * Result:  BOOL	    TRUE if printer changed, otherwise FALSE
 *
 ***************************************************************************/
BOOL QueryPrinterQueue(HAB hAB,
		       HWND hWnd,
		       PVOID *DriverData,
		       PULONG DriverDataLength,
		       PSZ QueueName,
		       PSZ DriverName)
{
	 PSZ	    pszQueueNames;
	 PSZ	    pszQueueName;
	 CHAR	    DefaultQueue[10];
static	 CHAR	    pm_spooler_queue_descr[] = "PM_SPOOLER_QUEUE_DESCR";
	 PRINTER_SEL_PARM PrtSelParm;
	 ULONG	   i1;
	 BOOL	    rc;
	 LONG	   SelectedItem;

   /* initialize printer selection structure */
   PrtSelParm.ulLen = sizeof(PRINTER_SEL_PARM);
   PrtSelParm.ulType = 0;		     /* reserved, must be NULL	   */
   /*
    * First query all printer queues with a default driver name defined on it
    * If it fails, bring up a Message Box
    */
   if ((pszQueueNames = GetQueueNamesString(&PrtSelParm.QueuesCount)) == NULL)
      {
      WinMessageBox(HWND_DESKTOP,
		    hWnd,
		    (PSZ)"There are no Printer Queues defined, use PrintManager to define at least one Queue and Printer.",
		    (PSZ)NULL,
		    0L,
		    MB_OK | MB_MOVEABLE | MB_CUAWARNING | MB_APPLMODAL);
      return(FALSE);
      }

   /* if no Queue is connected to a Printer Driver, bring up a message */
   if (PrtSelParm.QueuesCount == 0)
      {
      WinMessageBox(HWND_DESKTOP,
		    hWnd,
		    (PSZ)"There are no Printer Queues connected to a Printer Driver, use PrintManager to connect a Printer to the Queue.",
		    (PSZ)NULL,
		    0L,
		    MB_OK | MB_MOVEABLE | MB_CUAWARNING | MB_APPLMODAL);
      free(pszQueueNames);
      return(FALSE);
      }

   /* Allocate Printer Selection Table */
   PrtSelParm.QueueSelTable = malloc(PrtSelParm.QueuesCount * sizeof(QUEUE_SEL));
   /*
    * Fill Printer Selection Table with Queue-Names and connected Printer
    * Driver Names
    */
   for (pszQueueName = pszQueueNames, i1 = 0;
	*pszQueueName;
	pszQueueName = &pszQueueName[strlen(pszQueueName) + 1], i1++)
      {
      PrfQueryProfileString(HINI_PROFILE,
			    (PSZ)pm_spooler_queue_dd,
			    (PSZ)pszQueueName,
			    (PSZ)NULL,
			    (PVOID)PrtSelParm.QueueSelTable[i1].DriverName,
			    (ULONG)DRIVERNAME_LENGTH);

      strcpy(PrtSelParm.QueueSelTable[i1].QueueName, pszQueueName);
      /* eliminate ; */
      PrtSelParm.QueueSelTable[i1].DriverName[strlen(
	     PrtSelParm.QueueSelTable[i1].DriverName) - 1] = '\0';

      /* Query Queue-Description */
      PrfQueryProfileString(HINI_PROFILE,
			    (PSZ)pm_spooler_queue_descr,
			    (PSZ)pszQueueName,
			    (PSZ)NULL,
			    (PVOID)PrtSelParm.QueueSelTable[i1].QueueDescription,
			    (ULONG)DESCRIPTION_LENGTH);
      /* eliminate ; */
      PrtSelParm.QueueSelTable[i1].QueueDescription[strlen(
	     PrtSelParm.QueueSelTable[i1].QueueDescription) - 1] = '\0';
      PrtSelParm.QueueSelTable[i1].DriverData = NULL;
      PrtSelParm.QueueSelTable[i1].DriverDataLength = 0;
      }

   free(pszQueueNames);		       /* free queue names */

   /* Sort Queue-Descriptions */
   qsort((void *)PrtSelParm.QueueSelTable,
	 (size_t)PrtSelParm.QueuesCount,
	 (size_t)sizeof(QUEUE_SEL),
	  SortQueues);

   /* Define preselected queue */
   PrtSelParm.SelectedItem = -1;

   strcpy(DefaultQueue, QueueName);
   /*
    * if Queue Name not given or not found in table, query Default Queue
    * if queue name defined, test if the queue is valid
    */
   if (QueryDefaultQueue(DefaultQueue, (PSZ)NULL))
      {
      /* If default Queue defined and connected to Driver, search table */
      for (i1 = 0; i1 < PrtSelParm.QueuesCount; i1++)
	 {
	 /* if found in table, set Selected Item to entry */
	 if (!strcmp(DefaultQueue, PrtSelParm.QueueSelTable[i1].QueueName))
	    {
	    PrtSelParm.SelectedItem = SelectedItem = i1;
	    PrtSelParm.QueueSelTable[i1].DriverData = *DriverData;
	    PrtSelParm.QueueSelTable[i1].DriverDataLength = *DriverDataLength;
	    break;
	    }
	 }
      }

   /* if no queue defined or queue not valid, free driver data if allocated */
   if ((SelectedItem = PrtSelParm.SelectedItem) == -1)
      if (*DriverData != NULL)
      {
	 free(*DriverData);
	 *DriverDataLength = 0;
	 *DriverData = NULL;
      }

   PrtSelParm.hAB = hAB;

   /* if an other printer is selected, copy the data to the function parameters */
   if (PrtSelParm.SelectedItem != SelectedItem)
      {
      rc = TRUE;
      strcpy(QueueName,
	     PrtSelParm.QueueSelTable[PrtSelParm.SelectedItem].QueueName);
      strcpy(DriverName,
	     PrtSelParm.QueueSelTable[PrtSelParm.SelectedItem].DriverName);
      *DriverData = PrtSelParm.QueueSelTable[PrtSelParm.SelectedItem].DriverData;
      *DriverDataLength = PrtSelParm.QueueSelTable[PrtSelParm.SelectedItem].DriverDataLength;
      }
   else
      rc = FALSE;

   /* free fields in queue selection table */
   for (i1 = 0; i1 < PrtSelParm.QueuesCount; i1++)
      if ((i1 != (ULONG)PrtSelParm.SelectedItem) &&
	  (PrtSelParm.QueueSelTable[i1].DriverData != NULL))
      {
	 free(PrtSelParm.QueueSelTable[i1].DriverData);
      }

   free(PrtSelParm.QueueSelTable);

   return(rc);
} /* End of QueryPrinterQueue */

/**************************************************************************
 * Function: SortQueues
 *
 * Compare routine for sorting the queue names table.
 *
 * Parameters: PQUEUE_SEL   pointer to queue selection structure 1
 *	       PQUEUE_SEL   pointer to queue selection structure 2
 *
 * Result:  int		    result of comparison
 *
 ***************************************************************************/
static int _Optlink SortQueues(const void *Queue1, const void *Queue2)
{
   return(strcmp(((PQUEUE_SEL)Queue1)->QueueDescription,
	      ((PQUEUE_SEL)Queue2)->QueueDescription));
} /* End of SortQueues */

/**************************************************************************
 * Function: QueryDefaultQueue
 *
 * if queue name passed, test if queue is valid, otherwise query default
 * printer queue.
 *
 * Parameters: PSZ	    queue name or nullstring
 *	       PSZ	    queue driver name or nullstring
 *
 * Result:  BOOL	    TRUE if driver is connected to queue
 *
 ***************************************************************************/
BOOL QueryDefaultQueue(PSZ QueueName, PSZ QueueDriver)
{
	 PSZ	    pszQueueNames;
	 PSZ	    pszQueueName;
	 CHAR	    QueueNameTemp[DRIVERNAME_LENGTH];
	 BOOL	    rc = FALSE;
static	 CHAR	    pm_spooler[] = "PM_SPOOLER";

/* if no queue name passed, query default printer queue */
   if (!*QueueName)
   {
      if (!PrfQueryProfileString(HINI_PROFILE,
			      (PSZ)pm_spooler,
			      (PSZ)"QUEUE",
			      NULL,
			      (PSZ)QueueNameTemp,
			      (LONG)sizeof(QueueNameTemp)))
      {
	 *QueueName = '\0';	 /* if no default printer queue found,	*/
	 return(FALSE);		 /* return FALSE			*/
      }

   /* cut ; */
      QueueNameTemp[strlen(QueueNameTemp) - 1] = '\0';

   /* return Queue Name */
      strcpy(QueueName, QueueNameTemp);
   }

/* Get String with all Queue Names to which a driver is connected */
   if ((pszQueueNames = GetQueueNamesString((PULONG)NULL)) != NULL)
   {
   /* Compare all Queue Names with default Queue Name */
      for (pszQueueName = pszQueueNames;
	*pszQueueName;
	pszQueueName = &pszQueueName[strlen(pszQueueName) + 1])
      {
	 if (!strcmp(pszQueueName, QueueName))
	 {
	 /* if Queue found in string, set rc = TRUE */
	    rc = TRUE;
	    break;
	 }
      }
   /* Free Queue Names string */
      free(pszQueueNames);
   }

/* if queue driver defined and Queue found, query queue driver name  */
   if ((QueueDriver != (PSZ)NULL) && rc)
   {
      PrfQueryProfileString(HINI_PROFILE,
			 (PSZ)pm_spooler_queue_dd,
			 (PSZ)QueueName,
			 NULL,
			 (PSZ)QueueNameTemp,
			 DRIVERNAME_LENGTH);
      QueueNameTemp[strlen(QueueNameTemp) - 1] = '\0';
      strcpy(QueueDriver, QueueNameTemp);
   }

   return(rc);
} /* End of QueryDefaultQueue */

/**************************************************************************
 * Function: GetQueueNamesString
 *
 * Get a string with all Queue Names with a connected Driver Name.
 *
 * Parameters: PULONG	    pointer to ULONG for returning the queue count
 *
 * Result:  PSZ		    string with all queuenames
 *
 ***************************************************************************/
static PSZ GetQueueNamesString(PULONG QCount)
{
   ULONG      QueueNamesLength;
   PSZ	      pszQueueNames;
   PSZ	      pszQueueNamesWithDriver;
   PSZ	      pszQueueName;
   PSZ	      pszQueueStringPtr;
   PSZ	      pszQueueDriver;
   ULONG     QueuesCt;

   /* Query size of Queues-String, if not defined return NULL */
   if (!PrfQueryProfileSize(HINI_PROFILE,
			    (PSZ)pm_spooler_queue_dd,
			    (PSZ)NULL,
			    (PULONG)&QueueNamesLength))
      return((PSZ)NULL);

   /* Allocate Space for Queue Names, if it fails return NULL */
   if ((pszQueueNames = malloc(QueueNamesLength)) == NULL)
      return((PSZ)NULL);

   /* Allocate Space for Queue Names with driver, if it fails return NULL */
   if ((pszQueueNamesWithDriver = malloc(QueueNamesLength)) == NULL)
      {
      free(pszQueueNames);
      return((PSZ)NULL);
      }

   /* Get Queue Names */
   PrfQueryProfileString(HINI_PROFILE,
			 (PSZ)pm_spooler_queue_dd,
			 (PSZ)NULL,
			 (PSZ)NULL,
			 (PVOID)pszQueueNames,
			 QueueNamesLength);
   /*
    * Query Count of Queues to which a Printer Driver is connected and
    * copy all Queue Names to pszQueueNamesWithDriver
    * Allocate space for Queue Driver Name, if it fails return NULL
    */
   if ((pszQueueDriver = malloc(DRIVERNAME_LENGTH)) == NULL)
      {
      free(pszQueueNames);
      free(pszQueueNamesWithDriver);
      return((PSZ)NULL);
      }

   pszQueueStringPtr = pszQueueNamesWithDriver;

   for (pszQueueName = pszQueueNames, QueuesCt = 0;
	*pszQueueName;
	pszQueueName = &pszQueueName[strlen(pszQueueName) + 1])
   {
      /* Query Queue Driver */
      PrfQueryProfileString(HINI_PROFILE,
			    (PSZ)pm_spooler_queue_dd,
			    (PSZ)pszQueueName,
			    (PSZ)NULL,
			    (PVOID)pszQueueDriver,
			    DRIVERNAME_LENGTH);
      /*
       * if Queue Driver isn't a null-string, increment Queues Count
       * and copy queue name to string
       */
      if ((*pszQueueDriver != ';') && (*pszQueueDriver != '\0'))
      {
	 strcpy(pszQueueStringPtr, pszQueueName);
	 pszQueueStringPtr = &pszQueueStringPtr[strlen(pszQueueStringPtr) + 1];
	 QueuesCt++;
      }
   }

   /* add a x'00' to the end of the string */
   *pszQueueStringPtr = '\0';

   /* free temporary fields */
   free(pszQueueDriver);
   free(pszQueueNames);

   /* Return Queues Count, if Pointer != NULL */
   if (QCount != (PULONG)NULL)
      *QCount = QueuesCt;

   /* return string with Queue Names */
   return(pszQueueNamesWithDriver);
} /* End of GetQueueNamesString */

/*************************************************************************
 * Function: QueryJobProperties
 *
 * Query job properties (print properties) of the printer driver.
 *
 * Parameters: HAB	    anchor block handle of the process
 *	       PVOID	    pointer to driver data
 *	       PULONG	    driver data length
 *	       PSZ	    queue driver name
 *	       PSZ	    printer queue name
 *	       PSZ	    printer driver name
 *	       BOOL	    flag, if FALSE don't display the dialog
 *
 * Result:  BOOL	    TRUE if ok
 *
 ***************************************************************************/
BOOL QueryJobProperties(HAB hAB,
			PVOID *DriverData,
			PULONG DriverDataLength,
			PSZ QueueDriverName,
			PSZ QueueName,
			PSZ PrtName,
			BOOL Flag)
{
	 CHAR	    DriverName[10];
	 CHAR	    DeviceName[64];
	 LONG	    rc;
	 LONG	   i1;
	 ULONG	    Length;
	 PSZ	    PrinterNames = NULL;
	 PSZ	    PrinterName;
	 PSZ	    PrinterInfo;
	 PSZ	    QueueInfo;
	 PSZ	    DriverInfo;
	 PSZ	    Queue;
	 PSZ	    Driver;
	 BOOL	    found;
	 ULONG	    flOptions;
static	 CHAR	    pm_spooler_printer[] = "PM_SPOOLER_PRINTER";
static	 CHAR	    separator1[] = ";";
static	 CHAR	    separator2[] = ",";

/* if flag=TRUE display the dialog, otherwise only query default properties */
   if (Flag)
      flOptions = DPDM_POSTJOBPROP;
   else
      flOptions = DPDM_QUERYJOBPROP;

   /* separate	driver name at the dot (e.g PLOTTERS.IBM6180) */
   if ((i1 = (LONG)strcspn(QueueDriverName, ".")) != 0L)
      {
      strncpy(DriverName, QueueDriverName, i1);
      DriverName[i1] = '\0';
      strcpy(DeviceName, &QueueDriverName[i1 + 1]);
      }
   else
      {
      strcpy(DriverName, QueueDriverName);
      *DeviceName = '\0';
      }
   /*
    * Query device name because some printer drivers need this information
    * in the DevPostDeviceModes call
    */

   /* Query the size needed to get the printer names */
   PrfQueryProfileSize(HINI_PROFILE,
		       (PSZ)pm_spooler_printer,
		       (PSZ)NULL,
		       (PULONG)&Length);

   /* Allocate space for printer names */
   PrinterNames = malloc((LONG)Length);

   /* Get the printer names */
   PrfQueryProfileString(HINI_PROFILE,
			 (PSZ)pm_spooler_printer,
			 (PSZ)NULL,
			 (PSZ)NULL,
			 (PVOID)PrinterNames,
			 Length);

   /* get Printer information and search for the specific queue */
   for (PrinterName = PrinterNames, found = FALSE;
	*PrinterName;
	PrinterName = &PrinterName[strlen(PrinterName) + 1])
      {
      /* Query the size needed to get the printer information */
      PrfQueryProfileSize(HINI_PROFILE,
			  (PSZ)pm_spooler_printer,
			  (PSZ)PrinterName,
			  (PULONG)&Length);

      PrinterInfo = malloc((ULONG)Length);

      PrfQueryProfileString(HINI_PROFILE,
			    (PSZ)pm_spooler_printer,
			    (PSZ)PrinterName,
			    (PSZ)NULL,
			    (PVOID)PrinterInfo,
			    Length);

      /* Get Driver Info and Queue Info of the string */
      DriverInfo = &PrinterInfo[strcspn(PrinterInfo, separator1) + 1];
      QueueInfo = &DriverInfo[strcspn(DriverInfo, separator1) + 1];
      QueueInfo[strcspn(QueueInfo, separator1)] = '\0';
      QueueInfo[-1] = '\0';

      /* Verify Queues with specific Queue Name */
      Queue = strchr(QueueInfo, separator2[0]);

      while (Queue)
	 {
	 /* if Queue found, compare Driver Names */
	 if (!strcmp(Queue, QueueName))
	    {
	    Driver = strchr(DriverInfo, separator2[0]);
	    while (Driver)
	       {
	       /* if Driver found, stop loop */
	       if (!strcmp(Driver, QueueDriverName))
		  {
		  found = TRUE;
		  break;
		  }
	       Driver = strchr(NULL, separator2[0]);
	       }
	    break;
	    }
	 Queue = strchr(NULL,separator2[0]);
	 }

      free(PrinterInfo);

      if (found)
	 break;
      }

   if (*DriverData == NULL)
      {
      /* Query length for Job-Properties */
      rc = DevPostDeviceModes(hAB,
			      NULL,
			      DriverName,
			      DeviceName,
			      PrinterName,
			      flOptions);

      if (rc <= (LONG)0)
	 {
	 free(PrinterNames);
	 return(FALSE);
	 }

      *DriverData = malloc((LONG)rc);

      *DriverDataLength = (LONG)rc;
      }

   /* call DevPostDeviceModes */
   rc = DevPostDeviceModes(hAB,
			   *DriverData,
			   DriverName,
			   DeviceName,
			   PrinterName,
			   flOptions);

   if ((PrtName != (PSZ)NULL) && (PrinterName != NULL))
      strcpy(PrtName, PrinterName);

   if (PrinterNames != NULL)
      free(PrinterNames);

   return TRUE;
} /* End of QueryJobProperties */
/************************** End of File.c *********************************/
