/**********************************************************************

PROGRAM:	PENAPP.C

PURPOSE:	A generic template for a pen-aware application.

COMMENTS:	Example program that uses both the system recognizer 
			GRECO.DLL and the custom recognizer SREC.DLL.  PENAPP
			calls DoDefaultPenInput to demonstrate how to:

			* Recognize text through an HRC object

			* Collect and manipulate ink in an HPENDATA object

			PENAPP accepts pen input through the Input Window.  The
			Raw Data window duplicates the user's actual ink.  The
			Info window displays either:

			1) The recognized ANSI text (for the system recognizer)

			2) An arrow indicating compass direction of the input
			   the input stroke (for the sample custom recognizer)

			3) A mirrored reflection of the input.

REFERENCES:	For a description of PENAPP, see Chapter 7, "A Sample Pen
			Application."  For information about the SREC recognizer,
			see Chapter 8, "Writing a Recognizer."  For a description of
			the messages generated by DoDefaultPenInput, see Chapter 2,
			"Starting Out with System Defaults."

**********************************************************************/

#define	 NOCOMM
#define	 NOPENAPPS

#include <windows.h>
#include <penwin.h>
#include "main.h"
#include "penapp.h"


// Global Variables ***************************************************

int		  viMenuSel  = miSystem;			// Current menu selection
HRCRESULT vrghresult[MAX_GUESS];			// Array of recognizer results
SYV		  vsyvSymbol[MAX_GUESS][MAX_CHAR];	// Array of symbol strings
int		  vcSyv[MAX_GUESS];					// Array of string lengths
HREC	  vhrec;							// HREC for sample recognizer
HPENDATA  vhpendata;						// Copy of the raw input data
HWND	  vhwndMain;						// Window handles
HWND	  vhwndInput;
HWND	  vhwndRaw;
HWND	  vhwndInfo;



/**********************************************************************
FUNCTION:	WinMain( hInstance, hPrevInstance, lpszCommandLine, cmdShow )

PURPOSE:	Main Windows function

RETURNS:	Exit code

**********************************************************************/
int PASCAL	WinMain(
					 HANDLE hInstance,				// Instance handle
					 HANDLE hPrevInstance,			// Previous instance handle
					 LPSTR lpszCommandLine,			// Command line string
					 int cmdShow )					// ShowWindow flag
{
	MSG   	msg;

	// Mention to prevent compiler warnings ....
	lpszCommandLine;

	if (!hPrevInstance)							// If first instance,
		if (!InitApp( hInstance ))				//   register window class
			return FALSE;						// Exit if can't register

	if (!InitInstance( hInstance, cmdShow ))	// Create this instance's window
		return FALSE;							// Exit if error

	if (!GetSystemMetrics( SM_PENWINDOWS ))		// If no Pen Windows,
		return FALSE;							//   exit

	RegisterPenApp( RPA_DEFAULT, PENVER );		// Register PENAPP
	while (GetMessage( (LPMSG) &msg, NULL, 0, 0) )
	{
		TranslateMessage( (LPMSG) &msg );
		DispatchMessage( (LPMSG) &msg );
	}
	RegisterPenApp( RPA_DEFAULT, FALSE );		// Unregister PENAPP

	msg.wParam = 0;
	return msg.wParam;
}



/**********************************************************************
FUNCTION:	InitApp( hInstance )

PURPOSE:	Initialize application data and register window classes

RETURNS:	TRUE if all successful

COMMENT:	There are four window classes: the main application window,
				and three child windows for input, information display, and
				raw data display.

				The input window is the area which receives the handwriting.
				The raw data window displays a copy of the raw input data.
				The infowindow displays the recognizer's interpretation.

**********************************************************************/
BOOL		InitApp( HANDLE hInstance )			// Instance handle
{
	WNDCLASS	wc;

	//
	// Register PenApp window class
	//
	wc.hCursor       = LoadCursor( NULL, IDC_ARROW );
	wc.hIcon         = LoadIcon( hInstance, MAKEINTRESOURCE( iconPenApp ) );
	wc.lpszMenuName  = MAKEINTRESOURCE( menuPenApp );
	wc.lpszClassName = (LPSTR) szPenAppClass;
	wc.hbrBackground = (HBRUSH) (COLOR_APPWORKSPACE+1);
	wc.hInstance     = hInstance;
	wc.style         = CS_VREDRAW | CS_HREDRAW ;
	wc.lpfnWndProc   = MainWndProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	if (!RegisterClass( (LPWNDCLASS) &wc) )
		return FALSE;

	//
	// Register PenApp child window classes
	//
	wc.hCursor       = LoadCursor( NULL, IDC_PEN );
	wc.hIcon         = NULL;
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = (LPSTR) szPenAppInputClass;
	wc.hbrBackground = (HBRUSH) (COLOR_WINDOW+1);
	wc.style         = CS_VREDRAW | CS_HREDRAW | CS_SAVEBITS;
	wc.lpfnWndProc   = InputWndProc;
	if (!RegisterClass( (LPWNDCLASS) &wc ))		// Register Input window
		return FALSE;

	wc.hCursor       = LoadCursor( NULL, IDC_ARROW );
	wc.lpszClassName = (LPSTR) szPenAppInfoClass;
	wc.lpfnWndProc   = InfoWndProc;
	if (!RegisterClass( (LPWNDCLASS) &wc ))		// Register Info window
		return FALSE;

	wc.lpszClassName = (LPSTR) szPenAppRawClass;
	wc.lpfnWndProc   = RawWndProc;
	wc.hbrBackground = (HBRUSH) (COLOR_WINDOW+1);
	if (!RegisterClass( (LPWNDCLASS) &wc ))		// Register Raw window
		return FALSE;

	return TRUE;
}



/**********************************************************************
FUNCTION:	InitInstance( hInstance, cmdShow )

PURPOSE:	Initialize data structures, create windows, load recognizer.

RETURNS:	TRUE if all successful

**********************************************************************/
BOOL		InitInstance(
						  HANDLE hInstance,			// Instance handle
						  int cmdShow )				// ShowWindow flag
{
	int		cxScreen = GetSystemMetrics( SM_CXSCREEN );
	int		cyScreen = GetSystemMetrics( SM_CYSCREEN );
	RECT	rect;

	//
	// Create Main window
	//
	vhwndMain = CreateWindow( (LPSTR) szPenAppClass,
							  (LPSTR) szPenAppWnd,
							  WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
							  0, 0, cxScreen, cyScreen,
							  (HWND) NULL,
							  (HWND) NULL,
							  (HANDLE) hInstance,
							  (LPSTR) NULL );

	if (!vhwndMain)
		return FALSE;

	//
	// Create Input window
	//
	GetClientRect( vhwndMain, &rect );

	vhwndInput = CreateWindow( (LPSTR) szPenAppInputClass,
							   (LPSTR) szInputWnd,
							   WS_CHILD | WS_BORDER | WS_CAPTION |
							   WS_VISIBLE | WS_CLIPSIBLINGS,
							   XInputWnd( rect.right ), YInputWnd( 0 ),
							   DxInputWnd( rect.right ),
							   DyInputWnd( rect.bottom ),
							   vhwndMain,
							   NULL,
							   (HANDLE) hInstance,
							   (LPSTR) NULL );
	if (!vhwndInput)
		return FALSE;

	//
	// Create Raw Data window
	//
	vhwndRaw = CreateWindow( (LPSTR) szPenAppRawClass,
							 (LPSTR) szRawWnd,
							 WS_CHILD | WS_BORDER | WS_CAPTION |
							 WS_VISIBLE | WS_CLIPSIBLINGS,
							 XRawWnd( 0 ), YRawWnd( rect.bottom ),
							 DxRawWnd( rect.right ), DyRawWnd( rect.bottom ),
							 vhwndMain,
							 NULL,
							 (HANDLE) hInstance,
							 (LPSTR) NULL );

	if (!vhwndRaw)
		return FALSE;


	//
	// Create Info window
	//
	vhwndInfo = CreateWindow( (LPSTR) szPenAppInfoClass,
							  (LPSTR) szInfoWnd,
							  WS_CHILD | WS_BORDER | WS_CAPTION |
							  WS_VISIBLE | WS_CLIPSIBLINGS,
							  XInfoWnd( 0 ), YInfoWnd( 0 ),
							  DxInfoWnd( rect.right ), DyInfoWnd( rect.bottom ),
							  vhwndMain,
							  NULL,
							  (HANDLE) hInstance,
							  (LPSTR) NULL );

	if (!vhwndInfo)
		return FALSE;

	ShowWindow( vhwndMain, cmdShow) ;
	UpdateWindow( vhwndMain );

	//
	// Load sample recognizer SREC
	//
	vhrec = InstallRecognizer( (LPSTR) szSampleRec );
	if (vhrec)
		return TRUE;
	else
	{
		MessageBox( vhwndMain, "Could not install sample recognizer SREC",
					szPenAppWnd, MB_OK );
		return FALSE;
	}
}



/**********************************************************************
FUNCTION:	MainWndProc( hwnd, message, wParam, lParam )

PURPOSE:	Window procedure for main window

RETURNS:	Varies

**********************************************************************/
LRESULT CALLBACK	MainWndProc(
								 HWND hwnd,		// Window handle
								 UINT message,	// Message
								 WPARAM wParam,	// Varies
								 LPARAM lParam )	// Varies
{
	LONG	lRet = 0L;
	int		x, y, dx, dy;

	switch (message)
	{
		case WM_COMMAND:
			switch (wParam)
			{
				case miExit:
					DestroyWindow( vhwndMain );
					break;

				case miSample:
				case miSystem:
				case miMirror:
					ResetWindow( wParam );
					break;

				default:
					break;
			}
			break;

		case WM_SIZE:
			x  = XInputWnd( LOWORD(lParam) );
			y  = YInputWnd( 0 );
			dx = DxInputWnd( LOWORD(lParam) );
			dy = DyInputWnd( HIWORD(lParam) );
			MoveWindow( vhwndInput, x, y, dx, dy, TRUE );

			x  = XRawWnd( 0 );
			y  = YRawWnd( HIWORD(lParam) );
			dx = DxRawWnd( LOWORD(lParam) );
			dy = DyRawWnd( HIWORD(lParam) );
			MoveWindow( vhwndRaw, x, y, dx, dy, TRUE );

			x  = XInfoWnd( 0 );
			y  = YInfoWnd( 0 );
			dx = DxInfoWnd( LOWORD(lParam) );
			dy = DyInfoWnd( HIWORD(lParam) );
			MoveWindow( vhwndInfo, x, y, dx, dy, TRUE );
			break;

		case WM_DESTROY:
			if (vhpendata)
				DestroyPenData( vhpendata );

			//
			// Unload sample recognizer. (Don't
			// unload system default recognizer.)
			//
			UninstallRecognizer( vhrec );
			PostQuitMessage( 0 );
			break;

		default:
			lRet = DefWindowProc( hwnd, message, wParam, lParam );
			break;
	}

	return lRet;
}



/**********************************************************************
FUNCTION:	InputWndProc( hwnd, message, wParam, lParam )

PURPOSE:	Window procedure for the input child window

RETURNS:	Varies

COMMENT:	This is a template of a typical pen-aware window procedure.

			As described in Chapter 5, "The Recognition Process," the 
			general interaction with recognition API services is:

			  1) Install recognizer(s) using InstallRecognizer
			  2) Initialize HRC object(s) and bind to recognizer(s)
			  3) Retrieve recognized data on WM_RCRESULT message
			  4) Unload recognizer(s) using UninstallRecognizer

			Step 1 has already been done in InitInstance.
			Step 4 takes place in MainWndProc on receipt of WM_DESTROY.

			If current selection is "Mirror", this procedure does no
			recognition.  Instead, it collects ink into an HPENDATA 
			block, as described in Chapter 4, "The Inking Process."

**********************************************************************/
LRESULT CALLBACK	InputWndProc(
								  HWND hwnd,		// Window handle
								  UINT message,		// Message
								  WPARAM wParam,	// Varies
								  LPARAM lParam )	// Varies
{
	LONG		lRet = 0L;				// Initialize return code to FALSE
	HRC			hrc;					// HRC object
	HDC			hdc;
	PAINTSTRUCT	ps;
	DWORD		dwInfo;
	int			i, cGuess;

	switch (message)
	{
		case WM_SYSCOMMAND:
			switch (wParam & 0xFFF0)
			{
				case SC_MOVE:		// Don't allow window to be moved
					break;

				default:
					lRet = DefWindowProc( hwnd, message, wParam, lParam );
					break;
			}
			break;

		case WM_PAINT:
			hdc = BeginPaint( hwnd, &ps );
			EndPaint( hwnd, &ps );
			break;

		case WM_LBUTTONDOWN:
			//
			// Two possibilities exist: user is using mouse or the pen.
			// The latter case indicates the user is starting to write.
			//
			dwInfo = GetMessageExtraInfo();
			if (IsPenEvent( message, dwInfo ))
			{
				if (DoDefaultPenInput( vhwndInput, (UINT)dwInfo ) == PCMR_OK)
					lRet = TRUE;
				else
					lRet = DefWindowProc( hwnd, message, wParam, lParam );
			}
			break;

		case WM_PENEVENT:
			switch (wParam)
			{
				case PE_GETPCMINFO:
					//
					// If using SREC recognizer, ensure session ends
					// on pen-up.
					//
					if (viMenuSel == miSample)
						((LPPCMINFO) lParam)->dwPcm |= PCM_PENUP;
					lRet = DefWindowProc( hwnd, message, wParam, lParam );
					break;

				case PE_BEGINDATA:
					//
					// Action based on current menu selection:
					//
					// 1) If currently using sample recognizer, create an HRC
					// for it and specify it in the TARGET structure pointed
					// to by lParam.  This tells DoDefaultPenInput to use
					// the sample recognizer rather than the system default.
					//
					// 2) If displaying mirror image of ink, create an 
					// HPENDATA for it and specify it in the TARGET structure
					// pointed to by lParam.  This tells DoDefaultPenInput to
					// collect data into the HPENDATA block instead of
					// passing it to a recognizer.
					//
					// 3) If using default recognizer, pass to DefWindowProc.
					// DefWindowProc sets maximum number of guesses to 1; the
					// code below shows how to access the HRC that DefWindowProc
					// creates and reset the number of guesses to MAX_GUESS.
					//
					if (vhpendata)
					{
						DestroyPenData( vhpendata );
						vhpendata = 0;
					}

					switch (viMenuSel)
					{
						case miSample:
							hrc = CreateCompatibleHRC( NULL, vhrec );
							if (hrc)
							{
								((LPTARGET) lParam)->dwData = hrc;
								lRet = LRET_HRC;
							}
							break;

						case miMirror:
							vhpendata = CreatePenData( NULL, 0,
													   PDTS_HIENGLISH, 0 );
							if (vhpendata)
							{
								((LPTARGET) lParam)->dwData = vhpendata;
								lRet = LRET_HPENDATA;
							}
							break;

						case miSystem:
							lRet = DefWindowProc( hwnd, message,
												  wParam, lParam );
							//
							// On return, lParam->dwData points to HRC.
							// Use it to reset max number of guesses.
							//
							SetMaxResultsHRC( ((LPTARGET) lParam)->dwData,
												MAX_GUESS );
							break;
					}
					break;

				case PE_ENDDATA:
					//
					// DefWindowProc will destroy vhpendata, so if collecting
					// mirror image, don't let DefWindowProc handle message
					//
					if (viMenuSel != miMirror)
						lRet = DefWindowProc( hwnd, message, wParam, lParam );
					break;

				case PE_RESULT:
					//
					// At end of input, collect recognition results (if any)
					// into symbol strings.  DoDefaultPenInput generates the
					// PE_RESULT submessage only when using a recognizer.
					// The lParam contains the HRC for the recognition process.
					//
					// NOTE:
					//    Do not destroy HRC, even after getting results!
					//    DefWindowProc takes care of destroying the object.
					//

					// Collect pen data for DrawRawData
					vhpendata = CreatePenDataHRC( (HRC) lParam );

					// Initialize array to zero
					for (i = 0; i < MAX_GUESS; i++)
						vcSyv[i] = SYV_NULL;

					// Get number of guesses available
					cGuess = GetResultsHRC( (HRC) lParam,
											 GRH_ALL,
											 (LPHRCRESULT) vrghresult,
											 MAX_GUESS );

					// Get guesses (in vsyvSymbol) and their lengths (in vcSyv)
					if (cGuess != HRCR_ERROR)
						for (i = 0; i < cGuess; i++)
							vcSyv[i] = GetSymbolsHRCRESULT( vrghresult[i],
													  0,
													  (LPSYV) vsyvSymbol[i],
													  MAX_CHAR );
					break;

				case PE_TERMINATED:
					//
					// Invalidate all child windows to send WM_PAINT 
					// message to them.  This clears the Input window 
					// and displays new data in Info and Raw windows.
					//
					InvalidateRect( vhwndInfo, NULL, TRUE );
					InvalidateRect( vhwndInput, NULL, TRUE );
					InvalidateRect( vhwndRaw, NULL, TRUE );

					// Fall through to DefWindowProc

				default:
					lRet = DefWindowProc( hwnd, message, wParam, lParam );

			}					// End switch (wParam)
			break;

		default:
			lRet = DefWindowProc( hwnd, message, wParam, lParam );

	}						// End switch (message)
	return lRet;
}



/**********************************************************************
FUNCTION:	InfoWndProc( hwnd, message, wParam, lParam )

PURPOSE:	Window procedure for the info child window

RETURNS:	Varies

**********************************************************************/
LRESULT CALLBACK	InfoWndProc(
								 HWND hwnd,		// Window handle
								 UINT message,	// Message
								 WPARAM wParam,	// Varies
									 LPARAM lParam )	// Varies
{
	HDC			hdc;
	PAINTSTRUCT	ps;
	LONG		lRet = 0L;		// Initialize return code to FALSE

	switch (message)
	{
		case WM_SYSCOMMAND:
			switch (wParam & 0xFFF0)
			{
				case SC_MOVE:		// Don't allow window to be moved
					break;

				default:
					lRet = DefWindowProc( hwnd, message, wParam, lParam );
					break;
			}
			break;

		case WM_PAINT:
			hdc = BeginPaint( hwnd, &ps );
			switch (viMenuSel)
			{
				case miSample:
					//
					// If sample recognizer, display arrow
					//
					DrawArrow( hwnd, hdc );
					break;

				case miSystem:
					//
					// If default recognizer, display all guesses
					//
					DisplayGuesses( hdc );
					break;

				case miMirror:
					//
					// If mirror image, display it
					//
					DrawMirrorImage( hdc );
					break;

			}				// End switch (viMenuSel)
			EndPaint( hwnd, &ps );
			break;

		default:
			lRet = DefWindowProc( hwnd, message, wParam, lParam );
			break;
	}

	return lRet;
}



/**********************************************************************
FUNCTION:	RawWndProc( hwnd, message, wParam, lParam )

PURPOSE:	Window procedure for the raw child window

RETURNS:	Varies

**********************************************************************/
LRESULT CALLBACK	RawWndProc(
								HWND hwnd,		// Window handle
								UINT message,	// Message
								WPARAM wParam,	// Varies
								LPARAM lParam )	// Varies
{
	LONG		lRet = 0L;		// Initialize return code to FALSE
	HDC			hdc;
	PAINTSTRUCT	ps;

	switch (message)
	{
		case WM_SYSCOMMAND:
			switch (wParam & 0xFFF0)
			{
				case SC_MOVE:		// Don't allow window to be moved
					break;

				default:
					lRet = DefWindowProc( hwnd, message, wParam, lParam );
					break;
			}
			break;

		case WM_PAINT:
			hdc = BeginPaint( hwnd, &ps );
			DrawRawData( hdc );
			EndPaint( hwnd, &ps );
			break;

		default:
			lRet = DefWindowProc( hwnd, message, wParam, lParam );
			break;
	}

	return lRet;
}



/**********************************************************************
FUNCTION:	DisplayGuesses( hdc )

PURPOSE:	Write up to MAX_GUESS symbols returned by default recognizer

RETURNS:	nothing

**********************************************************************/
VOID		DisplayGuesses( HDC hdc )	// DC handle
{
	TEXTMETRIC	tm;
	int			nX, nY;					// Text coords
	char		szText[MAX_CHAR];		// Results converted to ASCII
	int			i, cChar;				// Loop count, string length

	SetTextColor( hdc, GetSysColor( COLOR_WINDOWTEXT ) );
	SetBkMode( hdc, TRANSPARENT );

	//
	// Write first (and best) guess at top left of window.  Subsequent
	// guesses appear below it in descending order of confidence.
	//
	GetTextMetrics( hdc, (LPTEXTMETRIC) &tm );
	nX = GetDeviceCaps( hdc, LOGPIXELSX )/4;
	nY = GetDeviceCaps( hdc, LOGPIXELSY )/4;

	for (i = 0; i < MAX_CHAR; i++)
	{
		if (vcSyv[i])
		{
			SymbolToCharacter( (LPSYV) vsyvSymbol[i], 
								vcSyv[i],
								(LPSTR) szText,
								(LPINT) &cChar );
			TextOut( hdc, nX, nY, (LPSTR) szText, cChar );
			nY += tm.tmExternalLeading + tm.tmHeight;
		}
	}
}



/**********************************************************************
FUNCTION:	DrawArrow( hwnd, hdc )

PURPOSE:	Draw an arrow in the direction of the endpoint line

RETURNS:	nothing

COMMENT:	Direction is specified in vszResult, and are one of
				the four compass directions.

**********************************************************************/
VOID		DrawArrow(
					   HWND hwnd,		// Window handle
					   HDC hdc )		// DC handle
{
	HPEN	hpen, hpenSave;				// GDI pens
	BOOL	fDraw = TRUE;				// Set if a line exists
	RECT	rect;						// Window rectangle
	int		xEnd;
	int		yEnd;
	int		xArrow[2];
	int		yArrow[2];

	//
	// Create new GDI pen
	//
	hpen     = CreatePen( PS_SOLID, 2, GetSysColor( COLOR_WINDOWTEXT ) );
	hpenSave = SelectObject( hdc, hpen );

	//
	// Set up window for drawing
	//
	GetClientRect( hwnd, &rect );
	SetMapMode( hdc, MM_ISOTROPIC );
	SetWindowExt( hdc, 100, 100 );
	SetViewportExt( hdc, rect.right/2, rect.bottom/2 );
	SetViewportOrg( hdc, rect.right/2, rect.bottom/2 );

	//
	// Draw arrow
	//
	switch ((int) vsyvSymbol[0][0])
	{
		case (syvEast & 0xFFFF):
			xEnd      = dwLength;
			yEnd      = 0;
			xArrow[0] = xEnd-dxArrow;
			yArrow[0] = -dyArrow;
			xArrow[1] = xEnd-dxArrow;
			yArrow[1] = dyArrow;
			break;

		case (syvSouth & 0xFFFF):
			xEnd      = 0;
			yEnd      = dwLength;
			xArrow[0] = dyArrow;
			yArrow[0] = yEnd-dxArrow;
			xArrow[1] = -dyArrow;
			yArrow[1] = yEnd-dxArrow;
			break;

		case (syvWest & 0xFFFF):
			xEnd      = -dwLength;
			yEnd      = 0;
			xArrow[0] = xEnd+dxArrow;
			yArrow[0] = -dyArrow;
			xArrow[1] = xEnd+dxArrow;
			yArrow[1] = dyArrow;
			break;

		case (syvNorth & 0xFFFF):
			xEnd      = 0;
			yEnd      = -dwLength;
			xArrow[0] = dyArrow;
			yArrow[0] = yEnd+dxArrow;
			xArrow[1] = -dyArrow;
			yArrow[1] = yEnd+dxArrow;
			break;

		case (syvDot & 0xFFFF):
			Ellipse( hdc, -dwLength/10, -dwLength/10, 
					 dwLength/10, dwLength/10 );
			fDraw = FALSE;
			break;

		default:
			fDraw = FALSE;
			break;
	}

	if (fDraw)
	{
		MoveTo( hdc, 0, 0 );
		LineTo( hdc, xEnd, yEnd );
		LineTo( hdc, xArrow[0], yArrow[0] );
		MoveTo( hdc, xEnd, yEnd );
		LineTo( hdc, xArrow[1], yArrow[1] );
	}

	//
	// Restore original GDI pen
	//
	SelectObject( hdc, hpenSave );
	DeleteObject( hpen );
	return;
}



/**********************************************************************
FUNCTION:	DrawRawData( hdc )

PURPOSE:	Draw the actual ink taken by the recognizer

RETURNS:	nothing

COMMENT:	This function demonstrates how to use DrawPenDataEx to
			draw pen-up and pen-down strokes with different ink
			characteristics.  Down strokes appear in the same color
			as originally drawn, while up strokes appear blue.

			See the DrawMirrorImage function below for an example of
			how to conveniently display pen-down strokes with 
			DrawPenDataFmt.

**********************************************************************/
VOID		DrawRawData( HDC hdc )
{
	PENDATAHEADER	pendataheader;		// Header for vhpendata
	HPEN			hpenUp, hpenSave;	// GDI pen for pen-up strokes
	RECT			rectWnd;			// Rectangle of Raw window
	UINT			fPen;				// Pen flag
	UINT			iStroke=0;			// Stroke counter
	int				nWidth;				// Ink width
	int				iRet;				// Return code

	GetClientRect( vhwndRaw, &rectWnd) ;
	if (vhpendata      == NULL ||		// If bad handle or
	rectWnd.right  <= 3*cBorder ||		//   window too small,
	rectWnd.bottom <= 3*cBorder)		//   exit
		return;

	if ( !GetPenDataInfo( vhpendata, &pendataheader, NULL, 0 ))
		return;

	nWidth   = NSetExtents( hdc, &pendataheader, &rectWnd );
	hpenUp   = CreatePen( PS_SOLID, nWidth, rgbBlue );
	hpenSave = SelectObject( hdc, hpenUp );

	do			// Loop for each stroke, beginning with first
	{
		//
		// If down stroke, use same ink characteristics as original.
		// If up stroke, first call SetStrokeAttributes to convert it
		// to a down stroke, then draw it in blue ink with GDI pen.
		//
		if (GetStrokeAttributes( vhpendata, iStroke, NULL, GSA_DOWN ))
			fPen = DPD_DRAWSEL;
		else
		{
			SetStrokeAttributes( vhpendata, iStroke, 1, SSA_DOWN );
			fPen = DPD_HDCPEN;
		}

		iRet = DrawPenDataEx( hdc, NULL, vhpendata, iStroke, iStroke,
							  0, IX_END, NULL, NULL, fPen );

		// If the strokes were changed from up to down, set them back to up
		if (fPen == DPD_HDCPEN)
			SetStrokeAttributes( vhpendata, iStroke, 0, SSA_DOWN );

		iStroke++;

	} while (iRet == PDR_OK);

	SelectObject( hdc, hpenSave );
	DeleteObject( hpenUp );
	return;
}



/**********************************************************************
FUNCTION:	DrawMirrorImage( hdc )

PURPOSE:	Draw the mirror image of the ink

RETURNS:	nothing

COMMENT:	This function demonstrates how to access and change
			the point data in an HPENDATA object.  Refer to 
			Chapter 4, "The Inking Process," for a detailed look
			at the HPENDATA object.

			This function also demonstrates the convenience of the API 
			function DrawPenDataFmt.  Given an HDC and an HPENDATA, 
			DrawPenDataFmt draws all strokes contained in the HPENDATA 
			object.

			See the DrawRawData function above for an example of how to
			draw strokes with more control (and correspondingly more 
			effort) with the DrawPenDataEx function.

**********************************************************************/
VOID		DrawMirrorImage( HDC hdc )
{
	HPENDATA		hMirrorData;		// Data block with mirror coords
	PENDATAHEADER	pendataheader;		// Header for mirror HPENDATA
	RECT			rectWnd;			// Info window rectangle

	if (!vhpendata)						// If no data,
		return;							//   exit

	//
	// Create new pen data, called hMirrorData, for the mirror image
	//
	hMirrorData = DuplicatePenData( vhpendata, 0 );

	//
	// We need only coordinate data, so throw everything else away;
	// then change points in hMirrorData to mirror image of original data
	//
	TrimPenData( hMirrorData, TPD_EVERYTHING, 0 );
	MakeMirrorImage( hMirrorData );

	//
	// If bad handle or window too small, exit
	//
	GetClientRect( vhwndInfo, &rectWnd) ;
	if (hMirrorData    == NULL ||
	rectWnd.right  <= 3*cBorder ||
	rectWnd.bottom <= 3*cBorder)
		return;

	//
	// Set extents for Info window and draw mirror image
	//
	if ( !GetPenDataInfo( hMirrorData, &pendataheader, NULL, 0 ))
		return;
	NSetExtents( hdc, &pendataheader, &rectWnd );
	DrawPenDataFmt( hdc, NULL, hMirrorData );
	DestroyPenData( hMirrorData );

	return;
}



/**********************************************************************
FUNCTION:	MakeMirrorImage( hpendata )

PURPOSE:	Rewrites coordinate data in pen data block to
				form mirror image of original data

COMMENT:	Calls ExtractPenDataPoints to remove points one by one
			from HPENDATA block, alters each x-coordinate in the 
			data, then replaces the new coordinate back into the
			block with InsertPenDataPoints.

			The x-coordinate of each point is replaced with

				x' = width - x

			where x     = original x-coordinate
						  width = tablet width

RETURNS:	Nothing

**********************************************************************/
VOID		MakeMirrorImage ( HPENDATA hpendata )	// Mirror image data
{
	PENDATAHEADER	penheader;		// Header for mirror HPENDATA
	PENINFO			peninfo;		// PENINFO struct
	POINT			point;			// Coord points extracted from pen data
	UINT			iStroke;		// Stroke counter
	UINT			iPoint;			// Point counter

	//
	// Get tablet width (peninfo.cxRawWidth) and
	// total number of strokes (penheader.cStrokes)
	//
	if (!GetPenDataInfo( hpendata, (LPPENDATAHEADER) &penheader,
						   (LPPENINFO) &peninfo, 0 ))
		return;

	// For each stroke in pen data block ...
	for (iStroke=0; iStroke < penheader.cStrokes; iStroke++)
	{
		iPoint = 0;

		// For each point in stroke ...
		while (ExtractPenDataPoints( hpendata, iStroke, iPoint, 1,
									 (LPPOINT) &point, NULL, 
									 EPDP_REMOVE ) == PDR_OK)
		{
			//
			// Revise x-coordinate and replace back into pen data block
			//
			point.x = peninfo.cxRawWidth - point.x;
			InsertPenDataPoints( hpendata, iStroke, iPoint++, 1,
								 (LPPOINT) &point, NULL );
		}
	}

	return;
}



/**********************************************************************
FUNCTION:	NSetExtents( hdc, lppndt, lprectWnd )

PURPOSE:	Set the window extents for the given DC handle

RETURNS:	Width of ink to use when redrawing

**********************************************************************/
int		NSetExtents(
					HDC hdc,				// DC handle
					LPPENDATAHEADER lppndt,	// Ptr to PENDATAHEADER struct
					LPRECT lprectWnd )		// Ptr to rectangle of window
{
	RECT	rectDP;
	LPRECT	lprect = &lppndt->rectBound;	// In tablet coordinates
	int		nWidth;

	//
	// Set mapping to fit raw data into window
	//
	SetMapMode( hdc, MM_ISOTROPIC );

	//
	// Window extents are the tablet-coord dimensions of drawing
	//
	SetWindowExt( hdc, lprect->right-lprect->left, lprect->bottom-lprect->top );

	rectDP = *lprect;
	TPtoDP( (LPPOINT)&rectDP, 2 );

	//
	// Now set viewport extents.  Check for special case when rectDP
	// is empty (otherwise GDI won't display anything)
	//
	if (IsRectEmpty( &rectDP ))
	{
		SetViewportExt( hdc, lprect->right-lprect->left,
						lprect->bottom-lprect->top );
		nWidth = lppndt->nInkWidth;
	}
	else
	{
		if (rectDP.right-rectDP.left > lprectWnd->right-2*cBorder ||
			rectDP.bottom-rectDP.top > lprectWnd->bottom-2*cBorder)
		{
			SetViewportExt( hdc, lprectWnd->right-2*cBorder,
							lprectWnd->bottom - 2*cBorder );
		}
		else						// Drawing is smaller than raw window
		{
			SetViewportExt( hdc, rectDP.right-rectDP.left,
							rectDP.bottom-rectDP.top );
		}

		//
		// Convert ink width to logical coordinates (tablet coordinates)
		//
		GetLPWidth( lppndt->nInkWidth, &nWidth );
	}

	SetWindowOrg( hdc, lprect->left, lprect->top );
	SetViewportOrg( hdc, cBorder, cBorder );
	return nWidth;
}



/**********************************************************************
FUNCTION:	ResetWindow( mi )

PURPOSE:	Set up windows for new menu selection

RETURNS:	nothing

**********************************************************************/
VOID		ResetWindow( int mi )		// Current menu selection
{
	HMENU	hmenu;

	if (mi == viMenuSel)								// If selection already
		return;											//   current, exit

	hmenu = GetMenu( vhwndMain );						// Get menu handle
	CheckMenuItem( hmenu, viMenuSel, MF_UNCHECKED );	// Uncheck old selection
	CheckMenuItem( hmenu, mi, MF_CHECKED );				// Check new selection
	viMenuSel = mi;										// Update flag

	InvalidateRect( vhwndInput, NULL, TRUE );			// Force repaint
	InvalidateRect( vhwndRaw, NULL, TRUE );				//   of all child
	InvalidateRect( vhwndInfo, NULL, TRUE );			//   windows
}
