//*static char *SCCSID = "@(#)hanoi.c   6.22 92/03/18";*/
/*==============================================================*\
 *  HANOI.C - Sample PM application
 *
 *
 *	Copyright  1991	 IBM Corp.					   *
 *									   *
 *	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.			   *				   *
 *--------------------------------------------------------------
 *
 *  This program implements a tower of hanoi program.  This
 *  sample app was written to demonstrate the use of a multi-
 *  threaded program.  The main thread handles the PM interface,
 *  the second thread is started to do the recursive execution
 *  of the hanoi algorithm.
 *
 *--------------------------------------------------------------
 *
 *  This source file contains the following functions:
 *	main(argc, *argv[]) - main routine
 *	MainWndProc(hwnd, msg, mp1, mp2) - main window procedure
 *	MainPaint(hwnd) - WM_PAINT processing of main
 *	MainCommand(mp1, mp2) - WM_COMMAND processing of main
 *	CalcThread() - Sets up and terminates the secondary thread
 *	DrawDisk(hps, cPole, bLevel, fDraw) - Draws or erases a disk
 *					      from poles
 *	MoveDisk(hps, bFrom, bTo) - Moves a disk from one pole to another
 *	Hanoi(bHeight, bFrom, bTo, bTemp) - Recursive routine for tower
 *					    of hanoi
 *	EnableMenuItem(hwnd, sMenuItem, fEnable) - Activates/deactivates
 *						   a menu choice
 *	EntryFldDlgProc(hwnd, msg, mp1, mp2) - Handles the set number of
 *					       disks dialog box
 *	SetupTowers() - Sets up the global tower data
 *	FixSysMenu(hwndDlg) - Remove Restore, Size, Min, & Max from
 *			      system menu
 *	AboutDlgProc(hwnd, msg, mp1, mp2) - processing about dlg box
 *	ObjectWndProc(hwnd, msg, mp1, mp2)
 *	MessageBox(hwndOwner, IdMsg, fsStyle, fBeep) - processing all
 *						   message calling
 *	Init(VOID) - iniitializing the process
 *	ExitProc(usTermCode) - terminating the process
 *	CreateBackgroundThread(VOID) - process the background thread creating
 *	BackgroundThread(ulThreadParam) - handle the background thread process
 *	DestroyBackgroundThread(VOID) - destroy background thread process
 *	InitHelp(VOID) - initialize the help process
 *	HelpHelpForHelp(mp2) - processing the help for help message
 *	HelpExtended(mp2) - processing the help for extended message
 *	HelpKeys(mp2) - processing the help menu of keys
 *	HelpIndex(mp2) - processing the help index message
 *	HelpAbout(mp2) - processing the about dlg box message
 *	DisplayHelpPanel(idPanel) - processing the help pannel display
 *	DestroyHelpInstance(VOID) - destroy the help instance
 *	PostBkThreadMsg(msg, mp1, mp2) - handle the background thread meg.
 *
\*==============================================================*/
/*--------------------------------------------------------------*\
 *  Include files, macros, defined constants, and externs
\*--------------------------------------------------------------*/
#define INCL_WIN
#define INCL_HELP
#define INCL_WINHEAP
#define INCL_WINDIALOGS
#define INCL_WINWINDOWMGR
#define INCL_GPI
#define INCL_GPIPRIMITIVES
#define INCL_GPIBITMAPS
#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES

#include <os2.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "hanoi.h"

/*--------------------------------------------------------------*\
 *  Global variables
\*--------------------------------------------------------------*/
CHAR   szClientClass[] = "Hanoi";
BYTE   abTowers[3][MAXDISKCNT];	   /* Used to hold disk numbers on each post */
BYTE   abTowersNdx[3];		   /* Current number of disks on each post */
BYTE   cTowerSize = DEFAULTSIZE;   /* Holds the total number of disks */
USHORT ausPolePos[3]= {POSTOFFSET, /* Holds offset drawing information */
		       POSTOFFSET + POSTSPACE,
		       POSTOFFSET + 2*POSTSPACE };
ULONG  ulIterations;
TID    tidCalcThread;
BOOL   fContinueCalc;
HWND   hwndMainFrame=0L,	      /* Handle to the main frame window */
       hwndMain;		     /* Handle to the main client window */
CHAR   szAppName[MAXNAMEL];	   /* Buffer for application name string */
HAB    hab;				 /* Anchor block for the process */
HMQ    hmq;			 /* Handle to the process' message queue */
CHAR   szUntitled[MESSAGELEN];		 /* buffer for "Untitled" string */
static BOOL fThreadCreated = FALSE;
static HEV hevThreadInit;	  /* semaphore for thread initialization */
static HAB habBkThread;
static HMQ hmqBkThread;
static TID tidBkThread;
static HWND hwndObject;
static CHAR szObjectClass[MAXNAMEL];
static HEV hevThreadInit;	  /* semaphore for thread initialization */
BOOL   fHelpEnabled;		 /* flag to determine if help is enabled */
static CHAR szLibName[HELPLIBRARYNAMELEN];
static CHAR szWindowTitle[HELPLIBRARYNAMELEN];
static HWND hwndHelpInstance;
BOOL   fViewOn=TRUE;			       /* flag to view on or off */
CHAR   *pszDisk	      ="Disk Information";
CHAR   *pszDisksMoved ="Disks Moved:";

/*--------------------------------------------------------------*\
 *  Entry point declarations					*
\*--------------------------------------------------------------*/
MRESULT EXPENTRY MainWndProc(HWND, ULONG, MPARAM, MPARAM);
MRESULT EXPENTRY EntryFldDlgProc(HWND, ULONG, MPARAM, MPARAM);
MRESULT EXPENTRY AboutDlgProc(HWND, ULONG, MPARAM, MPARAM);
MRESULT EXPENTRY ObjectWndProc(HWND, ULONG, MPARAM, MPARAM);
LONG	MessageBox(HWND, LONG, LONG, BOOL);
VOID APIENTRY ExitProc(ULONG);
BOOL Init(VOID);
VOID MainPaint(HWND);
LONG MainCommand(HWND, MPARAM, MPARAM);
VOID CalcThread(VOID);
VOID DrawDisk(HPS,BYTE,BYTE,BYTE);
VOID MoveDisk(HPS,BYTE,BYTE);
VOID Hanoi(BYTE, BYTE, BYTE, BYTE);
VOID EnableMenuItem(HWND, LONG, BOOL);
VOID SetupTowers(VOID);
VOID FixSysMenu(HWND);

BOOL CreateBackgroundThread(VOID);
VOID BackgroundThread(ULONG);
BOOL PostBkThreadMsg(ULONG, MPARAM, MPARAM);
VOID DestroyBackgroundThread(VOID);

VOID InitHelp(VOID);
VOID HelpHelpForHelp(MPARAM);
VOID HelpExtended(MPARAM);
VOID HelpKeys(MPARAM);
VOID HelpIndex(MPARAM);
VOID HelpAbout(MPARAM);
VOID DisplayHelpPanel(LONG);
VOID DestroyHelpInstance(VOID);

/****************************************************************\
 *  Main routine
 *--------------------------------------------------------------
 *
 *  Name:    main(argc, *argv[])
 *
 *  Purpose: Initializes the PM environment, calls the initialization
 *	     routine, creates main window, and polls the message queue
 *
 *  Usage:
 *
 *  Method:  - obtains anchor block handle and creates message
 *	       queue
 *	     - calls the initialization routine
 *	     - creates the main frame window which creates the
 *	       main client window
 *	     - polls the message queue via Get/Dispatch Msg loop
 *	     - upon exiting the loop, exits
 *
 *  Returns: 0 - if successful execution completed
 *	     1 - if error
 *
\****************************************************************/
INT main(LONG argc, CHAR *argv[])
{
   QMSG	    qmsg;			       /* Message structure */
   ULONG    flCtlData;			      /* Frame control data */
   LONG	    sHold;

   if(argc > 1)	 /* If command line arg, use as the initial number of disks */
   {
      sHold = (LONG)atoi(argv[1]);
      if(sHold>0 && sHold<=MAXDISKCNT)
      cTowerSize = (BYTE) sHold;
   }

   SetupTowers();

   hab = WinInitialize(0L);

   if(!hab)
   {
       DosBeep(BEEP_WARN_FREQ, BEEP_WARN_DUR);
       return(RETURN_ERROR);
   }

   hmq = WinCreateMsgQueue(hab, 0L);

   if(!hmq)
   {
       DosBeep(BEEP_WARN_FREQ, BEEP_WARN_DUR);
       WinTerminate(hab);
       return(RETURN_ERROR);
   }

   if(!Init())
   {
	MessageBox(HWND_DESKTOP,
		   IDMSG_INITFAILED,
		   MB_OK | MB_ERROR,
		   TRUE);

	return(RETURN_ERROR);
   }
   /* Create the main window */
   flCtlData = FCF_STANDARD;

   hwndMainFrame = WinCreateStdWindow(HWND_DESKTOP,
				      WS_VISIBLE,
				      &flCtlData,
				      szAppName,
				      NULL,
				      WS_VISIBLE,
				      (HMODULE)NULL,
				      ID_RESOURCE,
				      (PHWND)&hwndMain);
   if(hwndMainFrame == NULLHANDLE)
   {
       MessageBox(HWND_DESKTOP,
		  IDMSG_MAINWINCREATEFAILED,
		  MB_OK | MB_ERROR,
		  TRUE);

       return(RETURN_ERROR);
   }
   InitHelp();
   /* Get/Dispatch Message loop */
   while(WinGetMsg(hab, (PQMSG)&qmsg, 0L, 0L, 0L))
       WinDispatchMsg(hab, (PQMSG)&qmsg);

    /* destroy the help instance */
    DestroyHelpInstance();

#ifdef BACKGROUND_THREAD
   DestroyBackgroundThread();
#endif

   return(RETURN_SUCCESS);

}   /* main() */

/****************************************************************\
 *  Initialization routine
 *--------------------------------------------------------------
 *
 *  Name:    Init(VOID)
 *
 *  Purpose: Performs initialization functions required before the main
 *	     window can be created.
 *
 *  Usage:   Called once before the main window is created.
 *
 *  Method:  - installs the routine ExitProc into the DosExitList chain
 *	     - registers all window classes
 *	     - performs any command line processing
 *
 *
 *  Returns: TRUE - initialization is successful
 *	     FALSE - initialization failed
\****************************************************************/
BOOL Init(VOID)
{

    /* Add ExitProc to the exit list to handle the exit processing. If
     * there is an error, then terminate the process since there have
     * not been any resources allocated yet			     */

    if(DosExitList(EXLST_ADD, ExitProc))
    {
	MessageBox(HWND_DESKTOP,
		   IDMSG_CANNOTLOADEXITLIST,
		   MB_OK | MB_ERROR,
		   TRUE);
	DosExit(EXIT_PROCESS, RETURN_ERROR);
    }

     /* load application name from resource file */
    if(!WinLoadString(hab, 0L, IDS_APPNAME, MAXNAMEL, szAppName))
	return FALSE;

	    /* load "untitled" string */
    if(!WinLoadString(hab, 0L, IDS_UNTITLED, MESSAGELEN, szUntitled))
	return FALSE;

	/* register the main client window class */
    if(!WinRegisterClass(hab,
			(PSZ)szAppName,
			(PFNWP)MainWndProc,
			CS_SIZEREDRAW | CS_CLIPCHILDREN,
			0L))
    {
	return FALSE;
    }

    /* If you wish to create a thread for background processing, define
     *	the BACKGROUND_THREAD constant the routine commented out here.
     * The routines for the background thread are in the thrd.c file.
     */

#ifdef BACKGROUND_THREAD
    if(!CreateBackgroundThread())
	return FALSE;
#endif
    return TRUE;
} /* Init() */

/****************************************************************\
 *  Exit list processing procedure
 *--------------------------------------------------------------
 *
 *  Name:    ExitProc(ULONG usTermCode)
 *
 *  Purpose: Cleans up certain resources when the application terminates
 *
 *  Usage:   Routine is called by DosExitList when the application exits
 *
 *  Method:  Global resources, such as the main window and message queue,
 *	     are destroyed and any system resources used are freed.
 *
 *  Returns: Returns EXLST_EXIT to the DosExitList handler
 *
\****************************************************************/
VOID APIENTRY ExitProc(ULONG usTermCode)
			  /* code for the reason for termination */
{

    /* destroy the main window if it exists */
    if(WinIsWindow(hab, hwndMainFrame))	 {
	WinDestroyWindow(hwndMainFrame);
    }
    /*--------------------------------------------------*\
     *	    Any other system resources used
     *	    (e.g. memory or files) should be freed here
    \*--------------------------------------------------*/
    WinDestroyMsgQueue(hmq);

    WinTerminate(hab);

    DosExitList(EXLST_EXIT, (PFNEXITLIST)0L);/* termination complete */
    return;
}					    /* ExitProc() */

/****************************************************************\
 *  Main client window procedure
 *--------------------------------------------------------------
 *
 *  Name:    MainWndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
 *
 *  Purpose: Handles all the messages associated with the main window
 *	     and calls the appropriate handling procedures.
 *
 *  Usage:   Called for each message placed in the window's message queue
 *
 *  Method:  Called only by main().  Note that when WM_PAINT executes,
 *	     the secondary thread may change data during the update
 *	     which may cause a problem.	 However, this is NOT a write
 *	     conflict, as only 1 thread does the writing.
 *
 *  Returns: Return values are determined by each message
 *
\****************************************************************/
MRESULT EXPENTRY MainWndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
   CHAR	  szMsg[MSGBUFSIZE];		/* Sprintf buffer */
   MRESULT sRC;
   SHORT  cxNew,cyNew;


   switch(msg)
   {
      case WM_PAINT:
	 MainPaint(hwnd);
	 break;

      case WM_COMMAND:
	 MainCommand(hwnd, mp1, mp2);
	 break;

      case IDM_DONE:
	 EnableMenuItem(hwnd,IDM_START,TRUE);  /* Reenable Start & set */
	 EnableMenuItem(hwnd,IDM_SET,TRUE);
	 EnableMenuItem(hwnd,IDM_STOP,FALSE);	       /* Disable stop */

	 sprintf(szMsg,"%lu disks were moved.",ulIterations);  /* Print msg */
	 WinMessageBox(HWND_DESKTOP, hwnd, szMsg, "Done!", 0L,
		       MB_OK | MB_MOVEABLE | MB_INFORMATION);
	 SetupTowers();					/* Reset towers */
	 WinInvalidateRect(hwnd,NULL,FALSE);   /* Force a screen redraw */
	 break;

      case HM_QUERY_KEYS_HELP:
	 return ( (MRESULT)PANEL_KEYSHELP);

      default:
	 sRC = WinDefWindowProc(hwnd, msg, mp1, mp2);
	 return sRC;
   }
   return (MRESULT)0L;/* all window procedures should return 0 as a default */
}			  /* MainWndProc() */

/****************************************************************\
 *  Main client painting routine
 *--------------------------------------------------------------
 *
 *  Name:    MainPaint(HWND hwnd)
 *
 *  Purpose: Paints the main client window.
 *
 *  Usage:   Routine is called whenver the client window
 *	     procedure receives a WM_PAINT message
 *
 *
 *  Method:  -begins painting by calling WinBeginPaint and retrieving
 *	      the HPS for the window
 *	     -performs any painting desired
 *	     -ends painting by calling WinEndPaint
 *
 *  Returns: 0 - if successful execution completed
 *	     1 - if error
 *
\****************************************************************/
VOID MainPaint(HWND hwnd)
{
   HPS	  hps;			       /* Handle for painting		*/
   RECTL  rclUpdate, rect;	       /* Rectangle struct for painting */
   POINTL ptl;			       /* Point struct for painting	*/
   BYTE	  cPole,bCnt,cnt;      /* Utility variables		*/
   CHAR	  szBuffer[BUFF_SIZE];
   LONG	  lVert,bHeight;
   FONTMETRICS FontMetrics;

   hps = WinBeginPaint(hwnd, 0L, (PRECTL)&rclUpdate);

   /* Fill update rectangle with window color */
   WinFillRect(hps, (PRECTL)&rclUpdate, SYSCLR_WINDOW);
   WinQueryWindowRect(hwndMain, (PRECTL)&rect);
   /*
    *get the character set size
    */
   GpiQueryFontMetrics(hps,sizeof(FontMetrics),&FontMetrics);
   ptl.x = (rect.xRight - ( strlen(pszDisk) * FontMetrics.lAveCharWidth)) / 2;
   ptl.y = rect.yTop   -  ( (VERT_POS - 2)  * FontMetrics.lEmHeight);
   if(fViewOn) /*view disk information*/
   {
      memset(szBuffer, 0L, sizeof(szBuffer));
      strcpy(szBuffer,pszDisk);
      GpiCharStringAt(hps, &ptl, strlen(szBuffer), szBuffer);
   }
   /*
    *center the base in the middle
    *of our screen
    */
   DrawRect( (rect.xRight - BASELEN) /2,
	     (rect.yTop - BASETHICK)  /2,
	     (rect.xRight + BASELEN) /2,
	     (rect.yTop + BASETHICK)  /2,
	      CLR_DARKGREEN );

   /* Draw the 3 posts */
   lVert   = (rect.yTop - BASETHICK)/2 +BASETHICK ;
   bHeight  = lVert + (cTowerSize *DISKSPACE + POSTEXTRAHT) ;
   for(cnt=0;cnt<3;cnt++)
   {
      ausPolePos[cnt] =
	 ((rect.xRight - BASELEN) /2)+ ((BASELEN -(POSTHALF+POSTWIDTH))/2   * cnt);
      DrawRect(ausPolePos[cnt],
	       bHeight,
	       ausPolePos[cnt]+(POSTHALF+POSTWIDTH),
	       lVert,
	       CLR_DARKGREEN);
   }

   /* Place the appropriate disks on each pole */
   for(cPole=0;cPole<3;cPole++)
   {
      for(bCnt=0;bCnt<abTowersNdx[cPole];bCnt++)
      {
	 DrawDisk(hps,cPole,bCnt,fDRAW);
      }
   }
   WinEndPaint(hps);
   return;
}				      /* MainPaint() */

/****************************************************************\
 *  Main window WM_COMMAND processing procedure
 *--------------------------------------------------------------
 *
 *  Name:    MainCommand(HWND hwnd, MPARAM mp1, MPARAM mp2)
 *
 *  Purpose: Calls the appropriate procedures that deal with the
 *	     selected menu item.
 *
 *  Usage:   Routine is called whenever a WM_COMMAND message is posted
 *	     to the main window.
 *
 *  Method:  a switch statement branches on the id of the menu item
 *	     that posted the message and the appropriate action for
 *	     that item is taken.  Any menu ids that are not part of
 *	     the standard menu set are passed onto the user defined
 *	     WM_COMMAND processing procedure.
 *
 *  Returns: Return values are determined by each message
 *
\****************************************************************/
LONG MainCommand(HWND hwnd, MPARAM mp1, MPARAM mp2)
{
   switch(SHORT1FROMMP(mp1))
   {
      case IDM_START:
			      /* Set continuation fContinueCalc */
	 fContinueCalc = TRUE;
	 ulIterations = 0L;	       /* Zero iteration counter */
	 if (DosCreateThread (&tidCalcThread, (PFNTHREAD)CalcThread,
				   0L, 0L, STACK))
	    DosBeep (440, 175);
	 else		    /* Disallow menu items that could change data */
	 {			    /* while the second thread is running */
	    EnableMenuItem(hwnd, IDM_START, FALSE); /* Disable Start item */
	    EnableMenuItem(hwnd, IDM_SET, FALSE);   /* Disable Set item */
	    EnableMenuItem(hwnd, IDM_STOP, TRUE);   /* Enable Stop item */
	 }
	 break;

      case IDM_STOP:
	 fContinueCalc = FALSE;	      /* Notify thread to quit */
	 break;

      case IDM_SET:		   /* Pop up the query/set box */
	 if(WinDlgBox(HWND_DESKTOP, hwnd,
		   (PFNWP)EntryFldDlgProc, (HMODULE)0L,IDD_SETCOUNT,0L))
	 {
	    SetupTowers();		       /* Reset towers */
	    WinInvalidateRect(hwnd,NULL,FALSE);
	 }
	 break;

      case IDM_ON:
	 fViewOn = TRUE;
	 WinInvalidateRect(hwnd,NULL,FALSE);
	 break;

      case IDM_OFF:
	 fViewOn = FALSE;
	 WinInvalidateRect(hwnd,NULL,FALSE);
	 break;


      case IDM_HELPHELPFORHELP:
	 HelpHelpForHelp(mp2);
	 break;

      case IDM_HELPEXTENDED:
	 HelpExtended(mp2);
	 break;

      case IDM_HELPKEYS:
	 HelpKeys(mp2);
	 break;

      case HM_QUERY_KEYS_HELP:
	 return (PANEL_KEYSHELP);

      case IDM_HELPINDEX:
	 HelpIndex(mp2);
	 break;

      case IDM_HELPPRODUCTINFO:
	 HelpAbout(mp2);
	 break;
   }
   return 0;
}

/****************************************************************\
 *  Creates the Background thread
 *--------------------------------------------------------------
 *
 *  Name:    CreateBackgroundThread(VOID)
 *
 *  Purpose: Routine creates the background thread
 *
 *  Usage:   Routine is called at initialization time to create a
 *	     background processing thread.
 *
 *  Method:  The routine calls DosCreateThread with the Background
 *	     thread routine.
 *
 *  Returns: TRUE if thread is created successfully.
 *	     FALSE if not or if the thread was already created
 *
\****************************************************************/
BOOL CreateBackgroundThread(VOID)
{
    LONG sRet;

    /*------------------------------------------------------------------*
     *	Note: C-runtime considerations:
     *	If you use non-reentrant C runtime functions in the second thread,
     *	then you will need to use _beginthread and _endthread instead of
     *	DosCreateThread and DosExit.
     *------------------------------------------------------------------*/
    if(!fThreadCreated)
    {
	DosCreateEventSem((PSZ)NULL, &hevThreadInit, 0L, FALSE);
	sRet = DosCreateThread(&tidBkThread,
				      (PFNTHREAD)BackgroundThread,
				      0L,
				      0L,
				      THREADSTACKSIZE);
    }
    else
    {
	return FALSE;
    }

		  /* wait until the thread has finished initialization */
    if(DosWaitEventSem(hevThreadInit, SEM_TIMEOUT))
	return FALSE;

    return((BOOL)(sRet == 0));

}   /* CreateBackgroundThread() */

/****************************************************************\
 *  Destroys the Background thread
 *--------------------------------------------------------------
 *
 *  Name:    DestroyBackgroundThread(VOID)
 *
 *  Purpose: Routine destroys the background thread
 *
 *  Usage:   Routine is called at exit time to destroy the background
 *	     processing thread.
 *
 *  Method:  The routine posts a WM_CLOSE message to the object window
 *	     to end its message loop.  It then waits to make sure that
 *	     the thread has been terminated before it returns.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID DestroyBackgroundThread(VOID)
{
   PostBkThreadMsg(WM_CLOSE, NULL, NULL);

   DosWaitThread(&tidBkThread, 0L);
   return;
}				   /* DestroyBackgroundThread() */

/****************************************************************\
 *  DrawDisk procedure
 *--------------------------------------------------------------
 *
 *  Name:    DrawDisk(HPS hps, BYTE cPole, BYTE bLevel, BYTE fDraw)
 *
 *  Purpose: This routine takes a PS handle, the hanoi spindle and
 *	     disk level and draws an appropriately sized disk.
 *
 *  Usage:   Routine is called whenever the disk need to display.
 *
 *  Method:  Does not grab exclusive access to the screen before
 *	     drawing which may cause a problem.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID DrawDisk(HPS hps, BYTE cPole, BYTE bLevel, BYTE fDraw)
{
   USHORT usXstart,usYstart,usWidth;
   POINTL ptl;
   RECTL  rect;		    /* Rectangle struct for painting */
   /* Calculate Bottom of disk */
   WinQueryWindowRect(hwndMain,&rect);
   usYstart = (rect.yTop + BASETHICK) /2 + (DISKSPACE * bLevel);


   /* Calculate the disk's width */
   usWidth = (MAXDISKWIDTH-MINDISKWIDTH) * abTowers[cPole][bLevel]
	      /cTowerSize + MINDISKWIDTH;
   /* Center disk on pole */
   usXstart = (ausPolePos[cPole] - usWidth/2 ) + DISKTHICK;

   if(fDraw == fDRAW)		  /* If we are to draw the disk */
   {
      DrawRect(usXstart,usYstart,usXstart+usWidth,
	       usYstart+DISKTHICK-1,CLR_RED);
   }
   else	      /* We are to erase the disk, then redraw the pole */
   {
      DrawRect(usXstart,usYstart,usXstart+usWidth,
	       usYstart+DISKTHICK-1,CLR_WHITE);
#if 0
      DrawRect(ausPolePos[cPole]-POSTHALF,
	       usYstart,
	       ausPolePos[cPole]-POSTHALF+POSTWIDTH,
	       usYstart+DISKTHICK-1,
	       CLR_DARKGREEN);
#endif
      DrawRect(ausPolePos[cPole],
	       usYstart,
	       ausPolePos[cPole]+POSTHALF+POSTWIDTH,
	       usYstart+DISKTHICK-1,
	       CLR_DARKGREEN);
   }
   return;
}

/****************************************************************\
 *  CalcThread procedure
 *--------------------------------------------------------------
 *
 * Name:    CalcThread(VOID)
 *
 * Purpose: Calls the recursive Hanoi with initial setting of 0,2,1
 *	    meaning from pole 0, to pole 2, using pole 1 as a temporary.
 *	    Hanoi returns when finished, or the user says stop.
 *	    This proc then sets a critical section so the posted
 *	    message won't be handled until the thread is terminated.
 *
 * Usage:   Routine is called whenever a IDM_START message is posted to
 *	    the main window.
 *
 * Method:  No DosExitCritSec() is called since _endthread() supposedly
 *	    clears the critical section when the thread is terminated.
 *
 * Returns: VOID
 *
\****************************************************************/
VOID CalcThread(VOID)
{
   HAB	  habb;			 /* Anchor block for the process */

   habb = WinInitialize(0L);	 /* Called to increase Ring 2 stack size */
   Hanoi(cTowerSize,0,2,1);    /* Execute the recursive routine */
   WinTerminate(habb);

   DosEnterCritSec(); /* Set Crit so the IDM_DONE isn't processed */
		      /* until this thread has completely terminated  */
   WinPostMsg(hwndMain,IDM_DONE,NULL,NULL);

   DosExit (EXIT_THREAD, 0L);		     /* Terminate thread */
   return;
}

/****************************************************************\
 *  EnableMenuItem procedure
 *--------------------------------------------------------------
 *
 * Name:    EnableMenuItem(HWND hwnd, LONG sMenuItem, BOOL fEnable)
 *
 * Purpose: This routine handles enabling/disabling of menu items.  This
 *	    is done by getting Parent and Menu hwnd handles then sending
 *	    the appropriate message.
 *
 * Usage:   Routine is called when enable or disable one item from menu.
 *
 * Method:
 *
 * Returns: VOID
 *
\****************************************************************/
VOID EnableMenuItem(HWND hwnd, LONG sMenuItem, BOOL fEnable)
{
   HWND hwndParent = WinQueryWindow(hwnd, QW_PARENT);
   HWND hwndMenu   = WinWindowFromID(hwndParent, FID_MENU);

   WinSendMsg(hwndMenu, MM_SETITEMATTR,
	      MPFROM2SHORT(sMenuItem, TRUE),
	      MPFROM2SHORT(MIA_DISABLED, fEnable ? 0 : MIA_DISABLED));
   return;
}

/****************************************************************\
 *  Hanoi recursion procedure
 *--------------------------------------------------------------
 *
 *  Name:    Hanoi(BYTE bHeight, BYTE bFrom, BYTE bTo, BYTE bTemp)
 *
 *  Purpose: This routine implements a recursive hanoi program that
 *	     works as follows:	By recursion, move all the disks,
 *	     except for the bottom disk to the temporary stack.
 *	     Then move the bottom disk to the target spindle.
 *	     Now recursively move the stack on the temporary spindle
 *	     to the target spindle.  The limiting case is when Hanoi()
 *	     is called with a bHeight of 0 in which case the depth
 *	     recursion is terminated.
 *
 *  Usage:
 *
 *  Method:  This routine checks the ->fContClac fContinueCalcFlag,
 *	     which is set by the main thread when the user selects
 *	     stop, to see if the user wishes to abort the algorithm.
 *	     If so, it backs out and exits.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID Hanoi(BYTE bHeight, BYTE bFrom, BYTE bTo, BYTE bTemp)
	     /* bHeight - The number of disks in the from stack to move */
	     /* bFrom	- The from spindle number, 0-2 */
	     /* bTo	- The to spindle number, 0-2 */
	     /* bTemp	- The temporary spindle number */
{
   HPS	  hps;				 /* Handle for painting */
   RECTL  rect;		       /* Rectangle struct for painting */
   CHAR	  szBuffer[BUFF_SIZE];
   LONG	  lIncrement = 1;
   POINTL ptl;
   USHORT usCenter;
   FONTMETRICS FontMetrics;

   if(bHeight<=0 || !fContinueCalc)	  /* Exit up if no more */
      return;			 /* disks or the user said Stop */

   Hanoi((BYTE)(bHeight-1),	    /* Move all but bottom disk */
	 (BYTE)bFrom,
	 (BYTE)bTemp,
	 (BYTE)bTo);

   if(!fContinueCalc)			/* If user said to stop */
      return;
   /* Display bFrom -> bTo */
   hps = WinGetPS(hwndMain);
   MoveDisk(hps,bFrom,bTo);		/* Move the bottom disk */
   if (fViewOn)
   {
      WinQueryWindowRect(hwndMain, (PRECTL)&rect);
      GpiQueryFontMetrics(hps,sizeof(FontMetrics),&FontMetrics);
      /*
       *set  center
       */
      usCenter =  (rect.xRight/2);
      /*
       *left hand side of text to blank out
       */
      rect.xLeft = ptl.x =( usCenter +( ( strlen(pszDisksMoved) ) / 2) * FontMetrics.lAveCharWidth);

      /*
       *bottom
       */
      rect.yBottom = ptl.y = rect.yTop - (VERT_POS * FontMetrics.lEmHeight);
      /*
       *top
       */
      rect.yTop	   = rect.yTop -   ((VERT_POS - 1)  * FontMetrics.lEmHeight);


      rect.yBottom = ptl.y;
      /*
       *clear off the previous	numbers
       *by drawing a  opaque rectangle
       */
      memset(szBuffer,' ', sizeof(szBuffer) );

      GpiCharStringPosAt(hps,
			 &ptl,
			 &rect,
			 (ULONG)CHS_OPAQUE,
			 DELETE_WIDTH,
			 szBuffer,
			 &lIncrement);

      GpiCharStringAt(hps, &ptl, strlen(szBuffer), szBuffer);
      strcpy(szBuffer,pszDisksMoved);
      /*
       *display the number of disks moved
       */
      /*
       *left hand side of text to draw
       */
      ptl.x = usCenter - (( (strlen(pszDisksMoved) + MAX_DIGITS)/ 2)* FontMetrics.lAveCharWidth);
      GpiCharStringAt(hps, &ptl, strlen(szBuffer), szBuffer);
      sprintf(szBuffer,"%05lu",
		       ulIterations+ 1 );

      ptl.x = usCenter + ( ((strlen(pszDisksMoved ) /2) + 1) * FontMetrics.lAveCharWidth);
      GpiCharStringAt(hps, &ptl, strlen(szBuffer), szBuffer);
   }
   WinReleasePS(hps);
   ulIterations++;
   /* Move disks over */
   Hanoi((BYTE)(bHeight-1),(BYTE)bTemp,(BYTE)bTo,(BYTE)bFrom);
   return;
}

/****************************************************************\
 *  SetupTowers procedure
 *--------------------------------------------------------------
 *
 *  Name:    SetupTowers(VOID)
 *
 *  Purpose: This routine initializes the global arrays that represent
 *	     the hanoi stacks.	This involves placing all the disks on
 *	     the first peg, emptying the other 2 pegs and setting the
 *	     associated counts.
 *
 *  Usage:   Routine is called when program started or restarted.
 *
 *  Method:  Calling uses the global variable cTowerSize to determine
 *	     how many disks there are.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID SetupTowers(VOID)
{
   USHORT cnt;

   for(cnt=0; cnt<cTowerSize; cnt++)	 /* Setup the intial post with disks */
      abTowers[0][cnt] = cTowerSize - (BYTE)cnt - (BYTE)1;

   abTowersNdx[0] = (BYTE)cTowerSize;	  /* Set disk count for initial post */
   abTowersNdx[1] = abTowersNdx[2] = (BYTE)0;	   /* Zero other post counts */
   return;
}

/****************************************************************\
 *  MoveDisk procedure
 *--------------------------------------------------------------
 *
 *  Name:    MoveDisk(HPS hps, BYTE bFrom, BYTE bTo)
 *
 *  Purpose: This routine moves the top disk from the bFrom spindle
 *	     to the top of the bTo spindle.
 *
 *  Usage:
 *
 *  Method:  Does error checking for trying to move a disk from a
 *	     spindle that does not have any disks on it.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID MoveDisk(HPS hps, BYTE bFrom, BYTE bTo)
			/* hps - Handle for painting */
			/* bFrom - The from spindle number, 0-2 */
			/* bTo - The to spindle number, 0-2 */
{
   CHAR bTOSndx;			  /* Top of stack index */
   BYTE bDiskNum;			 /* Disk number to move */

   bTOSndx = abTowersNdx[bFrom]-(CHAR) 1;

   DrawDisk(hps,bFrom,bTOSndx,fERASE);	 /* Remove disk off from stack */

   bDiskNum = abTowers[bFrom][bTOSndx]; /* Get move disk number */
   abTowersNdx[bFrom]--;      /* Decrease count on from spindle */

	/* Offset to place the disk at Place on stack in memory */
   bTOSndx = abTowersNdx[bTo]++;
   abTowers[bTo][bTOSndx] = bDiskNum;
				   /* Draw disk on the to stack */
   DrawDisk(hps,bTo,bTOSndx,fDRAW);
   return;
}

/****************************************************************\
 *  EntryFldDlgProc procedure
 *--------------------------------------------------------------
 *
 *  Name:   EntryFldDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
 *
 *
 * Purpose: Handles all the messages associated with the set entry field
 *	    and calls the appropriate handling procedures.  The purpose
 *	    of this dialog box is to get a new number of disks for the
 *	    hanoi routine.
 *
 * Usage:
 *
 * Method:  If the value entered is valid, global cTowerSize is changed
 *	    to the new value, and TRUE is returned.
 *
 * Returns: Terminates with a TRUE if a new valid Tower Size has been entered.
 *
\****************************************************************/
MRESULT EXPENTRY EntryFldDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
   SHORT sNewSize=0;			    /* Holds new number of disks */
   MRESULT sRC;

   switch(msg)
   {
      case WM_INITDLG:
	 FixSysMenu(hwnd);

	 WinSendDlgItemMsg(hwnd, IDD_ENTRYFLD, EM_SETTEXTLIMIT,
			   MPFROM2SHORT(2,0),NULL);  /* Limit len */
	 WinSetDlgItemShort(hwnd, IDD_ENTRYFLD, cTowerSize,TRUE);
	 return (MRESULT)0L;		   /* Allow normal focus setting */

      case WM_COMMAND:
	 switch(SHORT1FROMMP(mp1))
	 {
	    case DID_OK:
	       WinQueryDlgItemShort(hwnd, IDD_ENTRYFLD, &sNewSize, TRUE);
	       if(sNewSize>0 && sNewSize<=MAXDISKCNT) /* Set new Tower size */
	       {
		  cTowerSize = (BYTE) sNewSize;
		  WinDismissDlg(hwnd,TRUE);
	       }
	       else			       /* Invalid value */
		  MessageBox(hwndMainFrame,
			     IDMSG_DISKNUMBERERROR,
			     MB_OK | MB_ICONEXCLAMATION,
			     TRUE);
	       return (MRESULT)0L;

	    case DID_CANCEL:
	       WinDismissDlg(hwnd,FALSE);
	       return (MRESULT)0L;

	    default:
	       sRC = WinDefDlgProc(hwnd, msg, mp1, mp2);
	       return sRC;
	 }
	 break;

      default:
	 sRC = WinDefDlgProc(hwnd, msg, mp1, mp2);
	 return sRC;
   }
   return (MRESULT)0L;
}

/****************************************************************\
 *  Dialog procedure for the About dialog box
 *--------------------------------------------------------------
 *
 *  Name:    AboutDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
 *
 *  Purpose: Handles all the messages associated with the About Box
 *
 *  Usage:   Called for each message sent to the About Box dialog box.
 *
 *  Method:  The about box only has a button control so this routine
 *	     only processes WM_COMMAND messages. Any WM_COMMAND
 *	     posted must have come from the Ok button so we dismiss
 *	     the dialog upon receiving it.
 *
 *  Returns: Dependent upon message sent
 *
\****************************************************************/
MRESULT EXPENTRY AboutDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
   MRESULT sRC;

   switch(msg)
   {
      case WM_INITDLG:
	 FixSysMenu(hwnd);
	 return (MRESULT)0L;

      case WM_COMMAND:
	 WinDismissDlg(hwnd, TRUE);
	 return (MRESULT)0L;

      default:
	 sRC = WinDefDlgProc(hwnd, msg, mp1, mp2);
	 return sRC;
   }
   return (MRESULT)0L;
}

/****************************************************************\
 *  FixSysMenu procedure
 *--------------------------------------------------------------
 *
 *  Name:    FixSysMenu(HWND hwndDlg)
 *
 *  Purpose: This routine removes the Restore, Size, Minimize, and
 *	     Maximize options from the system menu of a dialog.
 *
 *  Usage
 *
 *  Method:  Called during the WM_INITDLG of a dialog procedure.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID FixSysMenu(HWND hwndDlg)
{
   HWND hwndMenu;		       /* Handle to system menu */

   hwndMenu = WinWindowFromID(hwndDlg, FID_SYSMENU);

   WinSendMsg(hwndMenu,	 /* Delete Restore from the system menu */
	      MM_DELETEITEM,
	      MPFROM2SHORT(SC_RESTORE, TRUE),
	      MPFROMP(NULL));

   WinSendMsg(hwndMenu,	    /* Delete Size from the system menu */
	      MM_DELETEITEM,
	      MPFROM2SHORT(SC_SIZE, TRUE),
	      MPFROMP(NULL));

   WinSendMsg(hwndMenu, /* Delete Minimize from the system menu */
	      MM_DELETEITEM,
	      MPFROM2SHORT(SC_MINIMIZE, TRUE),
	      MPFROMP(NULL));

   WinSendMsg(hwndMenu, /* Delete Maximize from the system menu */
	      MM_DELETEITEM,
	      MPFROM2SHORT(SC_MAXIMIZE, TRUE),
	      MPFROMP(NULL));
   return;
}

/****************************************************************\
 *  Message Box procedure
 *--------------------------------------------------------------
 *
 *  Name:    MessageBox(HWND hwndOwner, LONG IdMsg, LONG fsStyle, BOOL fBeep)
 *
 *  Purpose: Displays the message box with the message given in idMsg
 *	     retrieved from the message table and using the style
 *	     flags in fsStyle
 *
 *  Usage:   Called whenever a MessageBox is to be displayed
 *
 *  Method:  - Message string is loaded from the process' message table
 *	     - Alarm beep is sounded if desired
 *	     - Message box with the message is displayed
 *	     - WinMessageBox return value is returned
 *
 *  Returns: Return value from WinMessageBox()
 *
\****************************************************************/
LONG MessageBox(HWND hwndOwner, LONG IdMsg, LONG fsStyle, BOOL fBeep)
	   /* hwndOwner - Handle of the message box's owner */
	   /* idMsg - ID if the message in the message table */
	   /* fsStyle - Style of the message box */
	   /* fBeep - If TRUE, beep before message box is displayed */
{
    CHAR szText[MESSAGELEN];
    LONG sRC;

    if(!WinLoadMessage(hab,
		      (HMODULE)NULL,
		      IdMsg,
		      MESSAGELEN,
		      (PSZ)szText))
    {
	WinAlarm(HWND_DESKTOP, WA_ERROR);
	return RETURN_ERROR;
    }

    if(fBeep)
	WinAlarm(HWND_DESKTOP, WA_ERROR);

    sRC = WinMessageBox(HWND_DESKTOP,
			hwndOwner,
			szText,
			(PSZ)NULL,
			IDD_MSGBOX,
			fsStyle);
    return sRC;
}				       /* MessageBox() */

/****************************************************************\
 *  Routine for initializing the help manager
 *--------------------------------------------------------------
 *
 *  Name:    InitHelp(VOID)
 *
 *  Purpose: Initializes the IPF help facility
 *
 *  Usage:   Called once during initialization of the program
 *
 *  Method:  Initializes the HELPINIT structure and creates the help
 *	     instance. If successful, the help instance is associated
 *	     with the main window
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID InitHelp(VOID)
{
    HELPINIT hini;

     /* If we return because of an error, Help will be disabled */
    fHelpEnabled = FALSE;
			      /* Initialize help init structure */
    hini.cb = sizeof(HELPINIT);
    hini.ulReturnCode = 0;
			    /* If tutorial added, add name here */
    hini.pszTutorialName = (PSZ)NULL;

    hini.phtHelpTable = (PHELPTABLE)MAKELONG(HANOI_HELP_TABLE, 0xFFFF);
    hini.hmodHelpTableModule = 0;
    hini.hmodAccelActionBarModule = 0;
    hini.idAccelTable = 0;
    hini.idActionBar = 0;

    if(!WinLoadString(hab,
		      0,
		      IDS_HELPWINDOWTITLE,
		      HELPLIBRARYNAMELEN,
		      (PSZ)szWindowTitle))
    {
	MessageBox(hwndMain, IDMSG_CANNOTLOADSTRING,
			      MB_OK | MB_ERROR, FALSE);
	return;
    }
    hini.pszHelpWindowTitle = (PSZ)szWindowTitle;

		    /* If debugging, show panel ids, else don't */
#ifdef DEBUG
    hini.fShowPanelId = CMIC_SHOW_PANEL_ID;
#else
    hini.fShowPanelId = CMIC_HIDE_PANEL_ID;
#endif

    if(!WinLoadString(hab,
		      0,
		      IDS_HELPLIBRARYNAME,
		      HELPLIBRARYNAMELEN,
		      (PSZ)szLibName))
    {
	MessageBox(hwndMain, IDMSG_CANNOTLOADSTRING,
			      MB_OK | MB_ERROR, FALSE);
	return;
    }
    hini.pszHelpLibraryName = (PSZ)szLibName;
				      /* Creating help instance */
    hwndHelpInstance = WinCreateHelpInstance(hab, &hini);

    if(hwndHelpInstance == 0L || hini.ulReturnCode)
    {
	MessageBox(hwndMainFrame, IDMSG_HELPLOADERROR, MB_OK | MB_ERROR, TRUE);
	return;
    }
		     /* Associate help instance with main frame */
    if(!WinAssociateHelpInstance(hwndHelpInstance, hwndMainFrame))
    {
	MessageBox(hwndMainFrame, IDMSG_HELPLOADERROR,
				  MB_OK | MB_ERROR, TRUE);
	return;
    }
/* Help manager is successfully initialized so set flag to TRUE */
    fHelpEnabled = TRUE;
   return;
}						  /* InitHelp() */

/****************************************************************\
 *  Processes the Help for Help command from the menu bar
 *--------------------------------------------------------------
 *
 *  Name:    HelpHelpForHelp(MPARAM mp2)
 *
 *  Purpose: Processes the WM_COMMAND message posted by the Help for
 *	     Help item of the Help menu
 *
 *  Usage:   Called from MainCommand when the Help for Help menu item
 *	     is selected
 *
 *  Method:  Sends an HM_DISPLAY_HELP message to the help instance so
 *	     that the default Help For Help is displayed.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID HelpHelpForHelp(MPARAM mp2)
{
	   /* This just displays the system help for help panel */
   if(fHelpEnabled)
     if((LONG)WinSendMsg(hwndHelpInstance, HM_DISPLAY_HELP,
					(MPARAM)0L, (MPARAM)0L))
	   MessageBox(hwndMain,
		      IDMSG_HELPDISPLAYERROR,
		      MB_OK | MB_ERROR,
		      FALSE);

   /* This routine currently doesn't use the mp2 parameter but     *\
    *  it is referenced here to prevent an 'Unreferenced Parameter'
   \*  warning at compile time.					   */
   return;
}					   /* HelpHelpForHelp() */

/****************************************************************\
 *  Processes the Extended Help command from the menu bar
 *--------------------------------------------------------------
 *
 *  Name:    HelpExtended(MPARAM mp2)
 *
 *  Purpose: Processes the WM_COMMAND message posted by the Extended
 *	     Help item of the Help menu.
 *
 *  Usage:   Called from MainCommand when the Extended Help menu item
 *	     is selected.
 *
 *  Method:  Sends an HM_EXT_HELP message to the help instance so that
 *	     the default Extended Help is displayed.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID HelpExtended(MPARAM mp2)
{
	   /* This just displays the system extended help panel */
   if(fHelpEnabled)
	if((LONG)WinSendMsg(hwndHelpInstance, HM_EXT_HELP,
					       (MPARAM)0L,(MPARAM)0L))
	    MessageBox(hwndMain,
		       IDMSG_HELPDISPLAYERROR,
		       MB_OK | MB_ERROR,
		       FALSE);

    /* This routine currently doesn't use the mp2 parameter but     *\
     *	it is referenced here to prevent an 'Unreferenced Parameter'
    \*	warning at compile time.				    */
   return;
}					      /* HelpExtended() */

/****************************************************************\
 *  Processes the Keys Help command from the menu bar
 *--------------------------------------------------------------
 *
 *  Name:    HelpKeys(MPARAM mp2)
 *
 *  Purpose: Processes the WM_COMMAND message posted by the Keys Help
 *	     item of the Help menu.
 *
 *  Usage:   Called from MainCommand when the Keys Help menu item is
 *	     selected
 *
 *  Method:  Sends an HM_KEYS_HELP message to the help instance so that
 *	     the default Keys Help is displayed.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID HelpKeys(MPARAM mp2)
{
	       /* This just displays the system keys help panel */
    if(fHelpEnabled)
	if((LONG)WinSendMsg(hwndHelpInstance, HM_KEYS_HELP,
					     (MPARAM)0L,(MPARAM)0L))
	    MessageBox(hwndMain,
		       IDMSG_HELPDISPLAYERROR,
		       MB_OK | MB_ERROR,
		       FALSE);

    /* This routine currently doesn't use the mp2 parameter but     *\
     *	it is referenced here to prevent an 'Unreferenced Parameter'
    \*	warning at compile time.				    */
    return;
}						  /* HelpKeys() */

/****************************************************************\
 *  Processes the Index Help command from the menu bar
 *--------------------------------------------------------------
 *
 *  Name:    HelpIndex(MPARAM mp2)
 *
 *  Purpose: Processes the WM_COMMAND message posted by the Index Help
 *	     item of the Help menu
 *
 *  Usage:   Called from MainCommand when the Index Help menu item
 *	     is selected
 *
 *  Method:  Sends an HM_INDEX_HELP message to the help instance so
 *	     that the default Index Help is displayed.
 *
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID HelpIndex(MPARAM mp2)
{
	      /* This just displays the system help index panel */
    if(fHelpEnabled)
	if((LONG)WinSendMsg(hwndHelpInstance, HM_HELP_INDEX,
					     (MPARAM)0L,(MPARAM)0L))
	    MessageBox(hwndMain,
		       IDMSG_HELPDISPLAYERROR,
		       MB_OK | MB_ERROR,
		       FALSE);

    /* This routine currently doesn't use the mp2 parameter but     *\
     *	it is referenced here to prevent an 'Unreferenced Parameter'
    \*	warning at compile time.				    */
    return;
}						 /* HelpIndex() */

/****************************************************************\
 *  Processes the About command from the Help menu
 *--------------------------------------------------------------
 *
 *  Name:    HelpAbout(MPARAM mp2)
 *
 *  Purpose: Processes the WM_COMMAND message posted by the About item
 *	     of the Help menu
 *
 *  Usage:   Called from MainCommand when the About menu item is selected
 *
 *  Method:  Calls WinDlgBox to display the about box dialog.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID HelpAbout(MPARAM mp2)
{
				 /* Display the AboutBox dialog */
    WinDlgBox(HWND_DESKTOP,
	      hwndMain,
	      (PFNWP)AboutDlgProc,
	      0,
	      IDD_PRODUCTINFO,
	      (PVOID)NULL);

    /* This routine currently doesn't use the mp2 parameter but     *\
     *	it is referenced here to prevent an 'Unreferenced Parameter
    \*	warning at compile time.				    */
    return;
}						 /* HelpAbout() */

/****************************************************************\
 *  Displays the help panel indicated
 *--------------------------------------------------------------
 *
 *  Name:    DisplayHelpPanel(LONG idPanel)
 *
 *  Purpose: Displays the help panel whose id is given
 *
 *  Usage:   Called whenever a help panel is desired to be displayed,
 *	     usually from the WM_HELP processing of the dialog boxes
 *
 *  Method:  Sends HM_DISPLAY_HELP message to the help instance
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID DisplayHelpPanel(LONG idPanel)
			/* ID of the help panel to be displayed */
{
    if(fHelpEnabled)
	if((LONG)WinSendMsg(hwndHelpInstance,
		      HM_DISPLAY_HELP,
		      MPFROMLONG(MAKELONG(idPanel, NULL)),
		      MPFROMSHORT(HM_RESOURCEID)))

	    MessageBox(hwndMainFrame,
		       IDMSG_HELPDISPLAYERROR,
		       MB_OK | MB_ERROR,
		       TRUE);
    return;
}					  /* DisplayHelpPanel() */

/****************************************************************\
 *  Destroys the help instance
 *--------------------------------------------------------------
 *
 *  Name:    DestroyHelpInstance(VOID)
 *
 *  Purpose: Destroys the help instance for the application
 *
 *  Usage:   Called after exit from message loop
 *
 *  Method:  Calls WinDestroyHelpInstance() to destroy the help instance
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID DestroyHelpInstance(VOID)
{
    if(hwndHelpInstance != 0L)
    {
	WinDestroyHelpInstance(hwndHelpInstance);
    }
    return;
}				       /* DestroyHelpInstance() */

/****************************************************************\
 *  Posts a message to the Background thread
 *--------------------------------------------------------------
 *
 *  Name:    PostBkThreadMsg(ULONG msg, MPARAM mp1, MPARAM mp2)
 *
 *  Purpose: Routine posts a message to the object window of the
 *	     background thread.
 *
 *  Usage:   Routine is called whenever a message is to be posted to
 *	     the background processing thread.
 *
 *  Method:  The routine posts the message to the object window of the
 *	     thread.
 *
 *  Returns:  the return value from WinPostMsg().
 *
\****************************************************************/
BOOL PostBkThreadMsg(ULONG msg, MPARAM mp1, MPARAM mp2)
{
   BOOL sRC;

   sRC = WinPostMsg(hwndObject, msg, mp1, mp2);
   return sRC;
}					   /* PostBkThreadMsg() */

/****************************************************************\
 *  Background thread routine
 *--------------------------------------------------------------
 *
 *  Name:    BackgroundThread(ULONG ulThreadParam)
 *
 *  Purpose: Routine is a background thread used for tasks to be
 *	     completed in the background.
 *
 *  Usage:   Routine is called at initialization time to create a
 *	     background processing thread.
 *
 *  Method:  The routine initializes itself as a PM thread and creates
 *	     a message queue.  It then creates an object window through
 *	     which it will receive and send messages.  It then polls
 *	     through a message loop, processing any messages it receives
 *	     in the object window's window procedure. When the loop
 *	     ends, it terminates.
 *
 *  Returns: VOID
 *
\****************************************************************/
VOID BackgroundThread(ULONG ulThreadParam)
		     /* Parameter passed into DosCreateThread() */
{
    QMSG qmsg;
			     /* Create message queue for thread */
    habBkThread = WinInitialize(0L);

    do
    {

	 if(habBkThread == 0L)
	 {
	     WinPostMsg(hwndMain, TM_THREADINITFAILED, NULL, NULL);
	     DosExit(EXIT_THREAD,(ULONG)NULL);
	 }
	 hmqBkThread = WinCreateMsgQueue(habBkThread, 0L);
	 if(hmqBkThread == 0L)
	 {
	     WinPostMsg(hwndMain, TM_THREADINITFAILED, NULL, NULL);
	     WinTerminate(habBkThread);
	     DosExit(EXIT_THREAD, (ULONG)NULL);
	 }
	 /* Load the string for the object
	  *window class and register the class
	  */
	 if(!WinLoadString(habBkThread,
			   0,
			   IDS_OBJECTCLASS,
			   MAXNAMEL,
			   (PSZ)szObjectClass))
	 {
	     WinPostMsg(hwndMain, TM_THREADINITFAILED, NULL, NULL);
	     break;
	 }
	  /* Register the main client window class */
	 if(!WinRegisterClass(habBkThread,
			     (PSZ)szObjectClass,
			     (PFNWP)ObjectWndProc,
			     0L,
			     0L))
	 {
	     WinPostMsg(hwndMain, TM_THREADINITFAILED, NULL, NULL);
	     break;
	 }
	  /* Create the object window */
	 hwndObject = WinCreateWindow(HWND_OBJECT,
				      szObjectClass,
				      NULL,
				      0L,
				      0,
				      0,
				      0,
				      0,
				      0L,
				      HWND_TOP,
				      OBJECTID,
				      NULL,
				      NULL);

	 if(hwndObject == 0L)
	 {
	     WinPostMsg(hwndMain, TM_THREADINITFAILED, NULL, NULL);
	     break;
	 }
	 /* Set thread created flag so another
	  *thread of this type cannot be created
	  */

	 fThreadCreated = TRUE;

				   /* Clear initialization semaphore */
	 DosPostEventSem(hevThreadInit);


						     /* Message loop */
	 while(WinGetMsg(hmqBkThread, (PQMSG)&qmsg, 0L, 0L, 0L))
	     WinDispatchMsg(hmqBkThread, (PQMSG)&qmsg);

    }ONCE;

    /* Destroy object window, clean up message queue and terminate */
    if(WinIsWindow(habBkThread, hwndObject))
	WinDestroyWindow(hwndObject);

    WinDestroyMsgQueue(hmqBkThread);
    WinTerminate(habBkThread);

 /* If termination is due to an error initializing the thread, then clear the
    initialization semaphore so that the main thread can continue. */

    if(!fThreadCreated)
	DosPostEventSem(hevThreadInit);
    DosExit(EXIT_THREAD, 0L);
		 /* The thread parameter is not currently used */
    return;
}					 /* BackgroundThread() */

/****************************************************************\
 *  Dialog procedure for the Object window
 *--------------------------------------------------------------
 *
 *  Name:    ObjectWndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
 *
 *  Purpose: Processes all messages sent to the Object window
 *
 *  Usage:   Called for each message sent to the Object window.
 *
 *  Method:  The Object window processes the messages that tell the
 *	     background thread what action to take. Since the object
 *	     window is not visible, it will not process any of the
 *	     standard window messages.
 *
 *  Returns: Dependent upon message sent
 *
\****************************************************************/
MRESULT EXPENTRY ObjectWndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
   MRESULT  sRC;

   switch(msg)	{
      /*-------------------------------------------------------*\
       *  Include any user defined messages to determine which
       *  action the background thread should take
      \*-------------------------------------------------------*/
   default:
      sRC = WinDefWindowProc(hwnd, msg, mp1, mp2);
      return sRC;
    }
    /* This routine currently doesn't use the hwnd, mp1, mp2 parameter  *\
     *	but it is referenced here to prevent an 'Unreferenced Parameter'*
    \*	warning at compile time.					*/
    return (MRESULT)0L;
}					     /* ObjectWndProc() */
