/* AN.c

	THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
	KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
	IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
	PURPOSE.

	Copyright (c) 1993, 1994  Microsoft Corporation.  All Rights Reserved.


	This sample program illustrates the AnimatePenData, DrawPenData, and
	DrawPenDataEx APIs. It has not been written to be localizable: to do
	so, move strings to an.rc and use the Windows LoadString API.

	Tabs 1, 4, 7...
*/


/******************* Includes and Controlling Defines ***********************/
#include <windows.h>
#include <penwin.h>
#include <commdlg.h>
#include <stdlib.h>
#include "anres.h"

/******************* Defines ************************************************/
#define cbSzTMax			256	// size of temp string buffers

#if (WINVER >= 0x0400)
#define cbSzFileMax			260	// max size of file names
#else
#define cbSzFileMax			128	// max size of file names
#endif

#define wExitError		1

#define CALLBACKNEVER	0
#define CALLBACKSHORT	1
#define CALLBACKLONG		2
#define CALLBACKSTROKE	3
#define SBMIN				0		// min scroll pos
#define SBMAX				10000	// ditto max
#define SBINC				10		// scroll increment
#define SBPGINC			100	// scroll page increment

#define cbPtBuf			32
#define cbBufMax			1024	// for file transfer

#define DPDEX				(IDM_DRAWEX - IDM_DRAWEX)
#define DPD					(IDM_DRAW - IDM_DRAWEX)
#define DPDPART			(IDM_DRAWPARTIAL - IDM_DRAWEX)

/******************* Typedefs ***********************************************/
typedef struct tagANDLG	// dialog init [default values in brackets]
	{
	UINT uStrk0;			// first stroke to render [0]
	UINT uPt0;				// point offset in first stroke [0]
	UINT uStrk1;			// last stroke to render [IX_END]
	UINT uPt1;				// point offset in last stroke [IX_END]
	BOOL fSkipUp;			// [F] to animate upstrokes, T to skip them
	BOOL fAutoRepeat;		// [F] to end after one rendering, T to repeat to tap
	UINT uCBPeriodCode;	// callback Period code [CALLBACKNEVER]
	UINT uSpeedPct;		// speed of animation [100%]
	BOOL fTermTimeout;	// [T] to terminate input on timeout, F for rectbound
	BOOL fRenderScale;	// [T] to scale pendata to output window, F to clip
	}
	ANDLG, FAR *LPANDLG;

/******************* Macros *************************************************/
// window refresh shorthand:
#define RefreshWindow(hwnd) do {\
	if (IsWindow(hwnd))\
		{\
		InvalidateRect(hwnd, NULL, TRUE);\
		UpdateWindow(hwnd);\
		}\
	} while (0)

#define MenuEnable(hmenu, id, f)	EnableMenuItem((HMENU)(hmenu), (id), \
	((f)? MF_ENABLED: MF_DISABLED | MF_GRAYED))

#define mGetEditVal(u) do {\
	if (wNotifyCode == EN_KILLFOCUS)\
		{\
		GetDlgItemText(hdlg, wParam, (LPSTR)sz, cbSzTMax);\
		u = (UINT)atoi(sz);\
		}\
	} while (0)

#define ErrBox(sz)\
	MessageBox(hwnd, (LPSTR)sz, (LPSTR)"DrawPenDataEx",\
		MB_TASKMODAL|MB_ICONSTOP|MB_OK)

// make callback code into a duration in ms:
#define MakeMs(u)\
	((u)==CALLBACKSTROKE? AI_CBSTROKE:\
	(u)==CALLBACKLONG? 1000:\
	(u)==CALLBACKSHORT? 200: 0)

// draw only part of pendata:
#define DrawPenDataPartial(hdc, lprect, hpndt, s0, s1, p0, p1)\
	DrawPenDataEx(hdc, lprect, hpndt, s0, s1, p0, p1, NULL, NULL, 0)

/******************* Variables **********************************************/
HWND vhwndAN = NULL;					// Main wnd
HWND vhdlg = NULL;					// modeless dialog
HWND vhwndIn = NULL;					// input window
HWND vhwndOut = NULL;				// drawing window

HINSTANCE vhInstanceCur = NULL;
HINSTANCE vhPenWin = NULL;

ANIMATEPROC vlpfnAnimateProc = NULL;

HPENDATA vhpndt = NULL;					// input pendata
HPCM vhpcmInp = NULL;					// collection

PSTR vpszWndMain = "Input";			// main window title
PSTR vpszWndIn = "_ANIn";				// input window title
PSTR vpszWndOut = "Output";			// draw window title
PSTR vpszClassMain = "ANclass";		// class name
PSTR vpszClassIn = "ANInclass";		// class name of drawing window
PSTR vpszClassOut = "ANOutclass";	// class name of drawing window
PSTR vpszEmpty = "";						// empty string

DLGPROC vlpDlgProc = NULL;

char *vszIniFile = "animpd.ini";
ANDLG vandlg =
	{
	0,					// uStrk0			=from first stroke
	0,					// uPt0				=from first point
	IX_END,			// uStrk1			=to last stroke
	IX_END,			// uPt1				=to last point
	FALSE,			// fSkipUp			=wait during up periods too
	FALSE,			// fAutoRepeat		=cycle only once
	CALLBACKSHORT,	// uCBPeriodCode	=callback every 250 ms
	100,				// uSpeedPct		=original speed
	TRUE,				// fTermTimeout	=terminate ink input on timeout
	FALSE				// fRenderScale	=render as drawn, not scaled to window
	};

int nDrawProc = DPDEX;
UINT vcCB = 0;					// for callback display count
BOOL vfCB = FALSE;			// enable callback display, initially off
BOOL vfUserCxl = FALSE;		// dialog redraw cancel
BOOL vfDrawing = FALSE;		// TRUE when drawing

char vszFile[cbSzFileMax];
char *vszSaveFileDef = "an.pdt";

/******************* Export Prototypes **************************************/
BOOL CALLBACK ANDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL _export CALLBACK AnimateProc(HPENDATA, UINT, UINT, UINT FAR*, LPARAM);
LRESULT CALLBACK ANInWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ANOutWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ANWndProc(HWND, UINT, WPARAM, LPARAM);

/******************* Local prototypes ***************************************/
VOID NEAR PASCAL ClearAppQueue(VOID);
BOOL NEAR PASCAL FInitAN(HINSTANCE, HINSTANCE, LPSTR);
BOOL NEAR PASCAL FInitInstance(HINSTANCE, HINSTANCE, int);
BOOL NEAR PASCAL MakeOutputWindow(HWND, int, int, int, int, int);
VOID NEAR PASCAL TermAN(VOID);
LPSTR NEAR PASCAL LpszFromN(int);
BOOL NEAR PASCAL FBeginPenInput(HWND, DWORD);
BOOL NEAR PASCAL FGetFileName(HWND, BOOL, LPSTR);
VOID NEAR PASCAL ShowCancel(BOOL);
VOID NEAR PASCAL TPtoCP(HWND, LPPOINT, int, LPRECT);
HPENDATA NEAR PASCAL ReadPenData(HFILE hfile);
BOOL NEAR PASCAL WritePenData(HFILE hfile, HPENDATA hpndt);

/******************* EXPORT FUNCTIONS ***************************************/

/*+-------------------------------------------------------------------------*/
BOOL CALLBACK				// ret T if handled
ANDlgProc(					// main modal dialog
	HWND hdlg, 				// std dialog params
	UINT message,
	WPARAM wParam,
	LPARAM lParam)
	{
	UINT wNotifyCode;
	UINT u;
	char sz[cbSzTMax];
	HWND hSBSpeed = GetDlgItem(hdlg, IDD_SBSPEED);
	static RECT rectDlg;

	switch (message)
		{
	case WM_INITDIALOG:	// in lieu of WM_CREATE
		SetDlgItemText(hdlg, IDD_ETSTRK0, LpszFromN((int)vandlg.uStrk0));
		SetDlgItemText(hdlg, IDD_ETPT0, LpszFromN((int)vandlg.uPt0));
		SetDlgItemText(hdlg, IDD_ETSTRK1, LpszFromN((int)vandlg.uStrk1));
		SetDlgItemText(hdlg, IDD_ETPT1, LpszFromN((int)vandlg.uPt1));

		CheckDlgButton(hdlg, IDD_CHSKIPUP, vandlg.fSkipUp);
		CheckDlgButton(hdlg, IDD_CHAUTOREPEAT, vandlg.fAutoRepeat);

		CheckRadioButton(hdlg, IDD_RBCBNEVER, IDD_RBCBSTROKE,
			IDD_RBCBNEVER + vandlg.uCBPeriodCode);

		SetDlgItemText(hdlg, IDD_ETSPEED, LpszFromN((int)vandlg.uSpeedPct));

		SetScrollRange(hSBSpeed, SB_CTL, SBMIN, SBMAX, FALSE);
		SetScrollPos(hSBSpeed, SB_CTL, vandlg.uSpeedPct, TRUE);

		CheckRadioButton(hdlg, IDD_RBTERMTIMEOUT, IDD_RBTERMRECT,
			IDD_RBTERMTIMEOUT + !vandlg.fTermTimeout);
		CheckRadioButton(hdlg, IDD_RBRENDERSCALE, IDD_RBRENDERCLIP,
			IDD_RBRENDERSCALE + !vandlg.fRenderScale);
		if (!IsRectEmpty(&rectDlg))
			SetWindowPos(hdlg, NULL, rectDlg.left, rectDlg.top, 0, 0,
				SWP_NOSIZE | SWP_NOZORDER);

		EnableWindow(GetDlgItem(hdlg, IDD_PBREDRAW), vhpndt != NULL);
		return FALSE;		// let system set focus

	case WM_COMMAND:		// user actions
		wNotifyCode = HIWORD(lParam);

		switch (wParam)
			{
		case IDD_ETSTRK0:
			mGetEditVal(vandlg.uStrk0);
			break;

		case IDD_ETPT0:
			mGetEditVal(vandlg.uPt0);
			break;

		case IDD_ETSTRK1:
			mGetEditVal(vandlg.uStrk1);
			break;

		case IDD_ETPT1:
			mGetEditVal(vandlg.uPt1);
			break;

		case IDD_CHSKIPUP:
			vandlg.fSkipUp = IsDlgButtonChecked(hdlg, IDD_CHSKIPUP)==1;
			break;

		case IDD_CHAUTOREPEAT:
			vandlg.fAutoRepeat = IsDlgButtonChecked(hdlg, IDD_CHAUTOREPEAT)==1;
			break;

		case IDD_RBCBNEVER:
		case IDD_RBCB250MS:
		case IDD_RBCB1SEC:
		case IDD_RBCBSTROKE:
			for (u = IDD_RBCBSTROKE - IDD_RBCBNEVER; u > 0; u--)
				if (IsDlgButtonChecked(hdlg, IDD_RBCBNEVER+u))
					break;
			vandlg.uCBPeriodCode = u;
			break;

		case IDD_ETSPEED:
			mGetEditVal(vandlg.uSpeedPct);
			if ((UINT)GetScrollPos(hSBSpeed, SB_CTL) != vandlg.uSpeedPct)
				PostMessage(hdlg, WM_HSCROLL, SB_THUMBPOSITION,
					MAKELPARAM(vandlg.uSpeedPct, hSBSpeed));
			break;

		case IDD_SBSPEED:
			// see WM_HSCROLL
			break;

		case IDD_RBTERMTIMEOUT:
		case IDD_RBTERMRECT:
			vandlg.fTermTimeout = IsDlgButtonChecked(hdlg, IDD_RBTERMTIMEOUT)==1;
			break;

		case IDD_RBRENDERSCALE:
		case IDD_RBRENDERCLIP:
			vandlg.fRenderScale = IsDlgButtonChecked(hdlg, IDD_RBRENDERSCALE)==1;
			break;

		case IDD_PBCLEAR:
			DestroyPenData(vhpndt);
			vhpndt = NULL;
			RefreshWindow(vhwndOut);
			break;

		case IDD_PBREDRAW:
			RefreshWindow(vhwndOut);
			break;

		case IDD_PBEXIT:
			PostMessage(hdlg, WM_CLOSE, 0, 0L);
			break;

		case IDCANCEL:
			vfUserCxl = TRUE;
			if (vandlg.fAutoRepeat)
				CheckDlgButton(hdlg, IDD_CHAUTOREPEAT, vandlg.fAutoRepeat = FALSE);
			break;

		default:
			return TRUE;
			}
		return FALSE;	// WM_COMMAND processed

	case WM_HSCROLL:
		if ((HWND)HIWORD(lParam) == hSBSpeed)
			{
			int jT, j = (int)vandlg.uSpeedPct;

			switch (wParam)
				{
			case SB_LEFT:
				j = SBMIN;
				break;

			case SB_RIGHT:
				j = SBMAX;
				break;

			case SB_LINELEFT:
				j = j-SBINC > SBMIN? j-SBINC: SBMIN;
				break;

			case SB_LINERIGHT:
				j = j+SBINC < SBMAX? j+SBINC: SBMAX;
				break;

			case SB_PAGELEFT:
				j = j-SBPGINC > SBMIN? j-SBPGINC: SBMIN;
				break;

			case SB_PAGERIGHT:
				j = j+SBPGINC < SBMAX? j+SBPGINC: SBMAX;
				break;

			case SB_THUMBTRACK:
			case SB_THUMBPOSITION:
				j = (int)LOWORD(lParam);

				// adjust to nearest:
				if (jT = (j-SBMIN) % SBINC)
					j += SBINC - jT < jT? SBINC - jT: -jT;
				break;

				}
			SetScrollPos(hSBSpeed, SB_CTL, vandlg.uSpeedPct = (UINT)j, TRUE);

			GetDlgItemText(hdlg, IDD_ETSPEED, (LPSTR)sz, cbSzTMax);
			jT = atoi(sz);	// value in edit control
			if (j != jT)
				{
				HWND hwndEdit = GetDlgItem(hdlg, IDD_ETSPEED);

				PostMessage(hwndEdit, WM_SETTEXT,
					0, (LPARAM)LpszFromN((int)vandlg.uSpeedPct));
				}
			}
		break;	// WM_HSCROLL

	case WM_CLOSE:
		PostMessage(vhwndAN, WM_COMMAND, IDM_DLG, 0L);	// toggle dlg off
		break;

	case WM_DESTROY:
		GetWindowRect(hdlg, &rectDlg);
		break;

	default:
		break;
		}

	return FALSE;
	}


/*+-------------------------------------------------------------------------*/
BOOL _export CALLBACK 			// ret LRESULT; NB _export to ensure correct ds
AnimateProc(						// animation callback proc
	HPENDATA hpndt,				// pendata
	UINT wStroke,					// current stroke
	UINT cPnts,						// number of points yet to draw
	UINT FAR *lpuSpeedPct,		// addr of speed pct
	LPARAM lParam)					// app value
	{
	BOOL fRet = !vfUserCxl;		// set in dialog
	hpndt, wStroke, cPnts, lParam;	// unused

	if (fRet)					
		{
		char sz[cbSzTMax];

		if (!vcCB)
			ShowCancel(TRUE);

		ClearAppQueue();		// handle message backlog in app queue

		*lpuSpeedPct = vandlg.uSpeedPct;		// get latest speed setting

		wsprintf((LPSTR)sz, (LPSTR)"CB=%u", ++vcCB);
		if (vfCB && vhwndOut)
			SetWindowText(vhwndOut, (LPSTR)sz);

		// vfUserCxl may have gotten set in ANOutWndProc's WM_PAINT, if 
		// the user changed the window size during a callback for example:
		fRet = !vfUserCxl;
		}

	return fRet;
	}


/*+-------------------------------------------------------------------------*/
LRESULT CALLBACK 			// ret LRESULT
ANInWndProc(				// input window proc
	HWND hwnd, 				// std wndproc params
	UINT message,
	WPARAM wParam,
	LPARAM lParam)
	{
	DWORD dwExtraInfo;
   int iRet = 0;
   STROKEINFO si;
	RECT r;
	POINT rgPnt[cbPtBuf];

	switch (message)
		{
	case WM_LBUTTONDOWN:
		if (vfDrawing)
			{
			MessageBeep(0);
			return 0L;
			}

		dwExtraInfo = GetMessageExtraInfo();
		if (IsPenEvent(message, dwExtraInfo))
			{
			if (FBeginPenInput(hwnd, dwExtraInfo))
				{
				if (IsWindow(vhdlg))
					EnableWindow(GetDlgItem(vhdlg, IDD_PBREDRAW), TRUE);
				return 1L;
				}
			return 0L;
			}
		break;

	case WM_PENEVENT:
		switch (wParam)
			{
		case PE_PENDOWN:
		case PE_PENUP:
		case PE_MOREPACKETS:
		case PE_TERMINATING:
			while ((iRet = GetPenInput(vhpcmInp, rgPnt, NULL, 0,
				cbPtBuf, &si)) > 0)
				{
				TPtoCP(hwnd, rgPnt, iRet, &r);
				AddPointsPenData(vhpndt, rgPnt, NULL, &si);
				}
			return 1L;	// we handled it, do not allow into DefWindowProc

		case PE_TERMINATED:
			if (vhdlg)
				SetFocus(vhdlg);
			InvalidateRect(hwnd, NULL, TRUE);
			UpdateWindow(hwnd);
			RefreshWindow(vhwndOut);
			return 1L;	// we handled it

		default:
			return 0L;
			}
		break;

	default:
		break;
		}

	return DefWindowProc(hwnd, message, wParam, lParam);
	}


/*+-------------------------------------------------------------------------*/
LRESULT CALLBACK 			// ret LRESULT
ANOutWndProc(				// drawing window proc
	HWND hwnd, 				// std wndproc params
	UINT message,
	WPARAM wParam,
	LPARAM lParam)
	{
	LRESULT lRet = 1L;

	switch (message)
		{
	case WM_USER:
		RefreshWindow(hwnd);	// invalidate and redraw
		break;

	case WM_PAINT:
		if (vfDrawing)
			{
			// since vfDrawing is TRUE, we are repainting during a 
			// callback, so we have to cancel the current animation first:
			vfUserCxl = TRUE;	// this is picked up in AnimateProc()
			break;
			}

		if (IsWindow(vhdlg))
			EnableWindow(GetDlgItem(vhdlg, IDD_PBREDRAW), vhpndt != NULL);

		if (vhpndt)
			{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hwnd, &ps);

			if (hdc)
				{
				HPEN hpen, hpenOld;
				RECT r;
				int iRet;
				int nWidthDPD = 1;
				COLORREF crDPD = RGB(0, 255, 255);	// cyan default
				ANIMATEINFO ai =
					{
					vandlg.uSpeedPct,								// speed percent
					MakeMs(vandlg.uCBPeriodCode),				// callback Period in ms
					vandlg.fSkipUp? AI_SKIPUPSTROKES: 0,	// options
					0L,												// lParam
					0L													// reserved
					};


				if (vandlg.fRenderScale)				// scale to window
					GetClientRect(hwnd, &r);
				else // clip
					{
					GetPenDataAttributes(vhpndt, (LPRECT)&r, GPA_RECTBOUND);
					TPtoDP((LPPOINT)&r, 2);				// pendata is in tablet coords
					}

				switch (nDrawProc)
					{
				case DPDEX:		// animation
					vcCB = 0;								// animate callback counter
					vfUserCxl = FALSE;					// reset
					ShowCancel(vandlg.uCBPeriodCode != CALLBACKNEVER);

					vfDrawing = TRUE;						// set semaphore
					iRet = DrawPenDataEx(
						hdc,									// handle to DC
						&r,									// rect for scaling/clipping
						vhpndt,								// the pendata
						vandlg.uStrk0,						// first stroke
						vandlg.uStrk1,						// last stroke
						vandlg.uPt0,						// first point in first stroke
						vandlg.uPt1,						// last point in last stroke
						vlpfnAnimateProc,					// Animate Callback function
						&ai,
						0);
					vfDrawing = FALSE;					// synchronous finish

					ShowCancel(FALSE);	// hide cancel button again

					if (iRet < 0 && iRet >= -10)
						{
						static char *szErr[] =
							{
							"PDR_ERROR",			// -1
							"PDR_PNDTERR",			// -2
							"PDR_VERSIONERR",		// -3
							"PDR_COMPRESSED",		// -4
							"PDR_STRKINDEXERR",	// -5
							"PDR_PNTINDEXERR",	// -6
							"PDR_MEMERR",			// -7
							"PDR_INKSETERR",		// -8
							"PDR_ABORT",			// -9
							"PDR_NA",				// -10
							};

						ErrBox(szErr[-iRet-1]);
						break;
						}

					if (!iRet && !vfUserCxl)
						ErrBox("PDR_CANCEL: callback impasse");

					vfUserCxl = FALSE;
					break;

				case DPD:		// 1.0 style drawing
					hpen = CreatePen(PS_SOLID, nWidthDPD, crDPD);
					hpenOld = SelectObject(hdc, hpen);
					DrawPenData(hdc,		// DC
						&r,					// rect for scaling/clipping
						vhpndt);				// the pendata
					SelectObject(hdc, hpenOld);
					DeleteObject(hpen);
					break;

				case DPDPART:	// partial drawing
				default:
					DrawPenDataPartial(hdc,	// DC
						&r,					// rect for scaling/clipping
						vhpndt,				// the pendata
						vandlg.uStrk0,		// first stroke to draw
						vandlg.uStrk1,		// last stroke to draw
						vandlg.uPt0,		// first point in first stroke to draw
						vandlg.uPt1);		// last point in last stroke to draw
					break;
					}

				ClearAppQueue();	// handle message backlog if any

				if (vandlg.fAutoRepeat)
					PostMessage(hwnd, WM_USER, 0, 0L);	// redraw

				}
			EndPaint(hwnd, &ps);
			}
		break;

	case WM_DESTROY:
		vhwndOut = NULL;
		break;

	default:
		break;
		}

	return DefWindowProc(hwnd, message, wParam, lParam);
	}


/*+-------------------------------------------------------------------------*/
LRESULT CALLBACK 			// ret LRESULT
ANWndProc(					// main window proc
	HWND hwnd, 				// std wndproc params
	UINT message,
	WPARAM wParam,
	LPARAM lParam)
	{
	LRESULT lRet = 1L;

	switch (message)
		{
	case WM_SETFOCUS:
		if (IsWindow(vhwndIn))
			SetFocus(vhwndIn);
		return lRet;

	case WM_SIZE:
		if (IsWindow(vhwndIn))
			MoveWindow(vhwndIn, 0, 0, LOWORD(lParam), HIWORD(lParam), FALSE);
		break;

	case WM_INITMENU:
		MenuEnable((HMENU)wParam, IDM_OUTPUT, vhwndOut == NULL);
		CheckMenuItem((HMENU)wParam, IDM_CBDISP, vfCB? MF_CHECKED: MF_UNCHECKED);
		break;

	case WM_COMMAND:
		{
		HMENU hmenu = GetMenu(vhwndAN);

		switch (wParam)
			{
		case IDM_DRAWEX:
		case IDM_DRAW:
		case IDM_DRAWPARTIAL:
			CheckMenuItem(hmenu, IDM_DRAWEX+nDrawProc, MF_UNCHECKED);
			nDrawProc = wParam - IDM_DRAWEX;
			CheckMenuItem(hmenu, wParam, MF_CHECKED);
			RefreshWindow(vhwndOut);
			break;

		case IDM_DLG:
			{
			BOOL fCheck = GetMenuState(hmenu, wParam, MF_BYCOMMAND) == MF_CHECKED;

			if (fCheck)
				{
				DestroyWindow(vhdlg);
				vhdlg = NULL;
				}
			else
				{
				vhdlg = CreateDialog(vhInstanceCur,
					MAKEINTRESOURCE(IDD_ANIMATE), hwnd, vlpDlgProc);
				}

			CheckMenuItem(hmenu, wParam, fCheck? MF_UNCHECKED: MF_CHECKED);
			}
			break;

		case IDM_OUTPUT:
			MakeOutputWindow(vhwndAN, CW_USEDEFAULT, CW_USEDEFAULT,
				0, 0, SW_SHOW);
			break;

		case IDM_CBDISP:
			vfCB = !vfCB;
			break;

		case IDM_SAVE:
		case IDM_OPEN:
			{
			HCURSOR hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
			BOOL fOpen = wParam == IDM_OPEN;
			HFILE hfile;

			if (!*vszFile)
				lstrcpy((LPSTR)vszFile, vszSaveFileDef);

			if (FGetFileName(hwnd, fOpen, vszFile))
				{
				OFSTRUCT of;

				if ((hfile = OpenFile((LPSTR)vszFile, &of,
					fOpen? OF_READ: OF_CREATE)) != HFILE_ERROR)
					{
					if (fOpen)
						{
						if (vhpndt)
							DestroyPenData(vhpndt);
						vhpndt = ReadPenData(hfile);
						RefreshWindow(vhwndOut);
						}
					else
						WritePenData(hfile, vhpndt);
					_lclose(hfile);
					}
				else ErrBox("error opening file");
				}
			else ErrBox("did not get file");

			SetCursor(hCursor);
			}
			break;

		case IDM_EXIT:
			PostMessage(hwnd, WM_CLOSE, 0, 0L);
			break;

		default:
			break;
			}
		}
		break;

	case WM_ERASEBKGND:
		return 1L;	// skip irritating flash

	case WM_DESTROY:
		TermAN();
		vhwndAN = NULL;
		PostQuitMessage(0);
		break;

	default:
		break;
		}

	return DefWindowProc(hwnd, message, wParam, lParam);
	}


/******************* LOCAL FUNCTIONS ****************************************/

/*+-------------------------------------------------------------------------*/
BOOL NEAR PASCAL 				// ret T if successful init
FInitAN(							// init class[es], icon
	HINSTANCE hInstance,		// this instance
	HINSTANCE hPrevInstance,// prev instance if any
	LPSTR lpszCommandLine)	// ptr to command line
	{
	WNDCLASS wndClass;
	LPSTR lpszIcon = (LPSTR)"iconAN";
	WORD wSysFlags;

	if (!hPrevInstance)
		{
		vhInstanceCur = hInstance;

		// Main Window
		wndClass.hCursor			= LoadCursor(NULL, IDC_ARROW);
		wndClass.hIcon				= LoadIcon(hInstance, lpszIcon);
		wndClass.lpszMenuName	= NULL;
		wndClass.lpszClassName	= (LPSTR)vpszClassMain;
		wndClass.hbrBackground	= (HBRUSH)COLOR_WINDOW+1;
		wndClass.hInstance		= hInstance;
		wndClass.style				= CS_VREDRAW | CS_HREDRAW;
		wndClass.lpfnWndProc		= (WNDPROC)ANWndProc;
		wndClass.cbClsExtra		= 0;
		wndClass.cbWndExtra		= 0;

		if (!RegisterClass((LPWNDCLASS) &wndClass))
			return FALSE;

		// Input Window: use pen cursor if actual pen is present:
		GetPenMiscInfo(PMI_SYSFLAGS, (LPARAM)(LPWORD)&wSysFlags);
		wndClass.hCursor			= wSysFlags & PWF_PEN?
			LoadCursor(vhPenWin, IDC_PEN): LoadCursor(NULL, IDC_ARROW);
		wndClass.hIcon				= NULL;
		wndClass.lpszMenuName	= NULL;
		wndClass.lpszClassName	= (LPSTR)vpszClassIn;
		wndClass.hbrBackground	= (HBRUSH)COLOR_WINDOW+1;
		wndClass.hInstance		= hInstance;
		wndClass.style				= CS_VREDRAW | CS_HREDRAW;
		wndClass.lpfnWndProc		= (WNDPROC)ANInWndProc;
		wndClass.cbClsExtra		= 0;
		wndClass.cbWndExtra		= 0;

		if (!RegisterClass((LPWNDCLASS) &wndClass))
			return FALSE;

		// Output Window
		wndClass.hCursor			= LoadCursor(NULL, IDC_ARROW);
		wndClass.hIcon				= NULL;
		wndClass.lpszMenuName	= NULL;
		wndClass.lpszClassName	= (LPSTR)vpszClassOut;
		wndClass.hbrBackground	= (HBRUSH)COLOR_WINDOW+1;
		wndClass.hInstance		= hInstance;
		wndClass.style				= CS_VREDRAW | CS_HREDRAW;
		wndClass.lpfnWndProc		= (WNDPROC)ANOutWndProc;
		wndClass.cbClsExtra		= 0;
		wndClass.cbWndExtra		= 0;

		if (!RegisterClass((LPWNDCLASS) &wndClass))
			return FALSE;
		}

	return TRUE;
	}

/*+-------------------------------------------------------------------------*/
BOOL NEAR PASCAL 					// ret T if init successful
FInitInstance(						// create windows and init
	HINSTANCE hInstance, 		// this instance
	HINSTANCE hPrevInstance,	// prev instance
	int cmdShow)					// show command
	{
	DWORD dwStyle = WS_OVERLAPPEDWINDOW;
	DWORD dwExStyle = (DWORD)0;	// could be topmost
	HMENU hMenu = NULL;
	int xWnd = 0;
	int yWnd = 224;
	int cxWnd = GetSystemMetrics(SM_CXSCREEN) / 3;
	int cyWnd = GetSystemMetrics(SM_CYSCREEN) / 4;

	hPrevInstance;	// noref

	hMenu = LoadMenu(hInstance, "ANMenu");
	vlpDlgProc = (DLGPROC)MakeProcInstance((FARPROC)ANDlgProc, vhInstanceCur);

	vlpfnAnimateProc = (ANIMATEPROC)MakeProcInstance(
		(FARPROC)AnimateProc, vhInstanceCur);

	// Create main window:
	if (!(vhwndAN = CreateWindowEx(
		dwExStyle,					// extended style
		(LPSTR)vpszClassMain,	// class name
		(LPSTR)vpszWndMain,		// window title
		dwStyle,						// main style
		xWnd, yWnd,					// pos
		cxWnd, cyWnd,				// size
		(HWND)NULL,					// no parent
		hMenu,						// menu if any
		hInstance,					// instance handle
		(LPSTR)NULL					// no params
		)))
		return FALSE;

	// Create CHILD input window:
	if (!(vhwndIn = CreateWindow(
		(LPSTR)vpszClassIn,		// class name
		(LPSTR)NULL,				// window title
		WS_CHILD,					// style
		0, 0,
		0, 0,
		vhwndAN,
		NULL,							// no menu
		hInstance,					// instance handle
		(LPSTR)NULL					// no params
		)))
		return FALSE;

	// Create output window:
	if (!MakeOutputWindow(vhwndAN, xWnd, yWnd + cyWnd + 1, 0, 0, cmdShow))
		return FALSE;

	// start with dialog open:
	PostMessage(vhwndAN, WM_COMMAND, IDM_DLG, 0L);

	ShowWindow(vhwndAN, cmdShow);
	ShowWindow(vhwndIn, cmdShow);
	UpdateWindow(vhwndAN);
	UpdateWindow(vhwndIn);
	return TRUE;
	}

/*+-------------------------------------------------------------------------*/
BOOL NEAR PASCAL 			// ret T if the window is successfully created
MakeOutputWindow(			// Creates a popup window into which to draw pendatas
	HWND hwndParent,		// Parent
	int x,					// x location
	int y,					// y location
	int cx,					// Window width, or 0 for default
	int cy,					// Window height, or 0 for default
	int nCmdShow)			// how to show window
	{
	DWORD dwExStyle = (DWORD)0;	// could be topmost
	DWORD dwStyle = WS_OVERLAPPEDWINDOW;

	if (!(vhwndOut = CreateWindowEx(
		dwExStyle,					// extended style
		(LPSTR)vpszClassOut,	// class name
		(LPSTR)vpszWndOut,		// window title
		dwStyle,						// style
		x, y,
		cx? cx: GetSystemMetrics(SM_CXSCREEN) / 3,
		cy? cy: GetSystemMetrics(SM_CYSCREEN) / 4,
		hwndParent,
		NULL,							// no menu
		vhInstanceCur,				// instance handle
		(LPSTR)NULL					// no params
		)))
		return FALSE;

	ShowWindow(vhwndOut, nCmdShow);
	UpdateWindow(vhwndOut);
	return TRUE;
	}

/*+-------------------------------------------------------------------------*/
VOID NEAR PASCAL 			// no ret
TermAN(						// terminate app
	VOID)						// no params
	{
	static BOOL fTerminated = FALSE;

	if (!fTerminated)
		{
		FreeProcInstance((FARPROC)vlpDlgProc);
		vlpDlgProc = NULL;
		fTerminated = TRUE;
		}
	}


/*+-------------------------------------------------------------------------*/
VOID NEAR PASCAL 			// no ret
ClearAppQueue(				// cycles through pending messages to yield.
	VOID)						// no params
	{
	MSG msg;

	while (PeekMessage(&msg, (HWND)NULL, NULL, NULL, PM_REMOVE))
		{
		if (!vhdlg || !IsDialogMessage(vhdlg, &msg))
			{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
			}
		}
	}


/*+-------------------------------------------------------------------------*/
LPSTR NEAR PASCAL 		// ret a pointer to a string.
LpszFromN(					// returns a string corresponding to a supplied integer.
	int n)					// value
/*------------------------
:	replaces itoa
------------------------*/
	{
	static char sz[cbSzTMax];	// always reused: caller must copy not assign

	if (n)
		{
		BOOL fNeg = n < 0;
		int i = 0, nSave;

		if (fNeg)
			n = -n;
		for (nSave = n, i = fNeg; n; n /= 10)
			i++;	// to strlen
		sz[i--] = '\0';
		for (n = nSave; n; n /= 10, i--)
			sz[i] = n%10 + '0';
		if (fNeg)
			sz[i] = '-';
		}
	else
		{
		sz[0] = '0';
		sz[1] = '\0';
		}
		
	return (LPSTR)sz;
	}


/*+-------------------------------------------------------------------------*/
BOOL NEAR PASCAL 			// ret T if collection & pendata was started ok
FBeginPenInput(			// establishes the collection session, creates pendata
	HWND hwnd,				// Handle to a window
	DWORD dwExtraInfo)	// Extra info associated with input device message
/*------------------------
:	This function demonstrates usage of INKPUT calls to create and
:	fill a pen data.
------------------------*/
	{
   PCMINFO pcminfo;
   int errCode;
   UINT wEventRef = LOWORD(dwExtraInfo);

   pcminfo.dwPcm = 0;	// messages by default
	if (vandlg.fTermTimeout)
		{
		UINT uTimeout;

	   pcminfo.dwPcm |= PCM_TIMEOUT;
   	GetPenMiscInfo(PMI_TIMEOUT, (LPARAM)(UINT FAR*)&uTimeout);
		pcminfo.dwTimeout = uTimeout;
		}
	else
		{
	   pcminfo.dwPcm |= PCM_RECTBOUND;
	   GetClientRect(hwnd, &pcminfo.rectBound);
   	ClientToScreen(hwnd, (LPPOINT)&pcminfo.rectBound.left);
	   ClientToScreen(hwnd, (LPPOINT)&pcminfo.rectBound.right);
		}

   // start input:
   if (!(vhpcmInp = StartPenInput(hwnd, wEventRef, &pcminfo, &errCode)))
      return FALSE;

   if (vhpndt)
		{
		DestroyPenData(vhpndt);
		vhpndt = NULL;
		}

	if (!(vhpndt = CreatePenDataEx((LPPENINFO)NULL, PDTS_STANDARDSCALE, 0, 0)))
		return FALSE;

	StartInking(vhpcmInp, wEventRef, NULL);
	return TRUE;
	}


/*+-------------------------------------------------------------------------*/
BOOL NEAR PASCAL 			// ret T if user selected a file
FGetFileName(				// get save/load filename 
	HWND hwnd, 				// owner
	BOOL fRead,				// T read F write
	LPSTR lpszFile)		// addr of filename 
/*------------------------
:	see <commdlg.h> and MS C7 Prog Ref/2 p.414
------------------------*/
	{
	OPENFILENAME ofn;
	char szPath[cbSzTMax];
	char szFile[cbSzTMax];
	char szDlgTitle[cbSzTMax];
	char szFilter[cbSzTMax];
	char chReplace;
	UINT i, cb;
	BOOL fRet = FALSE;

	if (!LoadString(vhInstanceCur, fRead? RS_OFNREADTITLE: RS_OFNWRITETITLE,
		(LPSTR)szDlgTitle, cbSzTMax))
			return FALSE;
	if (!(cb = LoadString(vhInstanceCur, RS_FILTER, (LPSTR)szFilter, cbSzTMax)))
		return FALSE;
	chReplace = szFilter[cb-1];	// retrieve wild character
	for (i = 0; szFilter[i]; i++)
		if (szFilter[i] == chReplace)
			szFilter[i] = '\0';

	GetSystemDirectory(szPath, cbSzTMax);
	lstrcpy((LPSTR)szFile, lpszFile);

	// setup info for comm dialog:
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = hwnd;
	ofn.hInstance = vhInstanceCur;
	ofn.lpstrFilter = szFilter;		// file types
	ofn.lpstrCustomFilter = NULL;
	ofn.nFilterIndex = 0;				// index of first one
	ofn.nMaxCustFilter = 0;
	ofn.lpstrFile = (LPSTR)szFile;	// initial file suggested
	ofn.nMaxFile = cbSzTMax;
	ofn.lpstrTitle = (LPSTR)szDlgTitle;		// dlg title
	ofn.lpstrInitialDir = (LPSTR)szPath;	// initial path
	ofn.Flags = OFN_SHOWHELP | OFN_HIDEREADONLY
		| OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_SHAREAWARE;
	ofn.lpstrDefExt = "*";				// default extension
	ofn.lpfnHook = NULL;

	fRet = fRead? GetOpenFileName((LPOPENFILENAME)&ofn):
		GetSaveFileName((LPOPENFILENAME)&ofn);

	if (fRet)
		lstrcpy(lpszFile, (LPSTR)ofn.lpstrFile);

	return fRet;
	}


/*+-------------------------------------------------------------------------*/
VOID NEAR PASCAL 	// no ret
ShowCancel(			// hides the redraw button and shows the cancel button in the options dialog
	BOOL fShow)		// T show cancel button, F show redraw
/*------------------------
:	both buttons occupy the same place. Escape is a keyboard accelerator
:		for Cancel.
------------------------*/
	{
	if (IsWindow(vhdlg))
		{
		HWND hwndShow = GetDlgItem(vhdlg, fShow? IDCANCEL: IDD_PBREDRAW);
		HWND hwndHide = GetDlgItem(vhdlg, fShow? IDD_PBREDRAW: IDCANCEL);

		ShowWindow(hwndShow, SW_SHOW);
		ShowWindow(hwndHide, SW_HIDE);
		InvalidateRect(hwndShow, NULL, FALSE);
		UpdateWindow(hwndShow);
		}
	}


/*+-------------------------------------------------------------------------*/
VOID NEAR PASCAL 				// no ret
TPtoCP(						// tablet to client
	HWND hwnd,	  			// Handle to client window
	LPPOINT lppt, 			// Pointer to an array of points
	int cpt,	  				// Count of points
	LPRECT lprect)			// Bounding rectangle calculated, or NULL
/*------------------------
:	converts an array of points in tablet coords to client
:	coordinates and optionally calculates a bounding rectangle
:	in client coordinates.
------------------------*/
	{
	POINT pt;
	BOOL fFirst = TRUE;

	// get upper left corner of client in tablet coords:
	pt.x = pt.y = 0;
	ClientToScreen(hwnd, &pt);
	DPtoTP(&pt, 1);

	for (lppt = lppt + cpt - 1; cpt > 0; --cpt, --lppt)
		{
		lppt->x -= pt.x;
		lppt->y -= pt.y;

		if (lprect)
			{
			if (fFirst)
				{
				lprect->left = lprect->right = lppt->x;
				lprect->top = lprect->bottom = lppt->y;
				fFirst = FALSE;
				}

			if (lppt->x > lprect->right)
				lprect->right = lppt->x;
			else if (lppt->x < lprect->left)
				lprect->left = lppt->x;

			if (lppt->y > lprect->bottom)
				lprect->bottom = lppt->y;
			else if (lppt->y < lprect->left)
				lprect->top = lppt->y;
			}
		}

	if (lprect)
		TPtoDP((LPPOINT)lprect, 2);
	}


/*+-------------------------------------------------------------------------*/
HPENDATA NEAR PASCAL ReadPenData(	// ret handle to pendata
	HFILE hfile)	// Handle of open file
/*------------------------
:	Reads pen data from a file. The file format at this point is
:	a UINT value representing the size of the pendata, followed
:	by that many bytes of pendata.
:
:	Before calling this function, the caller should have already
:	opened the file specified by hfile and ensured that the
:	file pointer is offset to the beginning of the pen data.
:	When the function returns, the file pointer will be offset
:	to the end of the pen data in the file.
------------------------*/
	{
	HPENDATA	hpndt = NULL;
	UINT		cb, cbRead, cbHpndt;
	BYTE		lpbBuf[cbBufMax];		// buffer
	DWORD		dwState = 0L;			// required init
	BOOL		fError = FALSE;

	if (!hfile
		|| (cb = _lread(hfile, &cbHpndt, sizeof(UINT))) == HFILE_ERROR
		|| cb != sizeof(UINT))
			return NULL;

	while (cbHpndt > 0)
		{
		if ((cbRead = _lread(hfile, lpbBuf, min(cbHpndt, cbBufMax)))
			== HFILE_ERROR
			|| PenDataFromBuffer(&hpndt, 0, lpbBuf, cbBufMax, &dwState) < 0)
			{
			if (hpndt)
				DestroyPenData(hpndt);
			return NULL;
			}
		cbHpndt -= cbRead;
		}

	return hpndt;
	}


BOOL NEAR PASCAL WritePenData(	// ret T if successful
	HFILE hfile,			// Handle to open file
	HPENDATA hpndt)		// pendata to write
/*------------------------
:	Writes pen data into a file, preceded by a UINT consisting of
:	the size of the pen data in bytes.
:
:	Before calling this function, the caller should have
:	already opened the file specified by hfile and ensured that
:	the file pointer is correctly placed.  When the function
:	returns, the file pointer will be offset to the end of the
:	pen data in the file. The function fails if the pen data is
:	larger than 64K.
------------------------*/
	{
	BYTE lpbBuf[cbBufMax];
	DWORD dwState = 0L;	// required init
	int cb;
	DWORD dwSize;
	UINT cbSize;

	if (!hfile || !hpndt)
		return FALSE;

	if (GetPenDataAttributes(hpndt, (LPVOID)&dwSize, GPA_SIZE) < 0)
		return FALSE;

	cbSize = LOWORD(dwSize);

	if (_lwrite(hfile, &cbSize, sizeof(UINT)) == HFILE_ERROR)
		return FALSE;

	while (cb = PenDataToBuffer(hpndt, lpbBuf, cbBufMax, &dwState))
		if (_lwrite(hfile, lpbBuf, (UINT)cb) == HFILE_ERROR)
			return FALSE;

	return cb >= 0;
	}


/******************* WINMAIN ************************************************/

/*+-------------------------------------------------------------------------*/
int PASCAL	 					// ret errorlevel
WinMain(							// Windows entry point; proto in <windows.h>
	HINSTANCE hInstance, 	// current instance
	HINSTANCE hPrevInstance,// prev instance if any, or NULL
	LPSTR lpszCommandLine,	// ptr to commandline args
	int cmdShow)				// WM_SHOW param
	{
	MSG msg;

	// look for Pen Windows:
	if (!(vhPenWin = (HINSTANCE)GetSystemMetrics(SM_PENWINDOWS)))
		{
		MessageBox(NULL, (LPSTR)"This program requires Pen Windows",
			(LPSTR)"Pen Data Drawing Sample", MB_TASKMODAL|MB_ICONSTOP|MB_OK);
		return wExitError;
		}

	RegisterPenApp(RPA_DEFAULT, PENVER);

	if (!FInitAN(hInstance, hPrevInstance, lpszCommandLine))
		exit(wExitError);

	if (FInitInstance(hInstance, hPrevInstance, cmdShow))	// show main window
		{
		while (GetMessage((LPMSG)&msg, NULL, 0, 0) )
			{
			if (!vhdlg || !IsDialogMessage(vhdlg, &msg))
				{
				TranslateMessage((LPMSG)&msg);
				DispatchMessage((LPMSG)&msg);
				}
			}
		}
	else
		msg.wParam = wExitError;	// error

	RegisterPenApp(RPA_DEFAULT, 0);

	TermAN();	// insurance
	return msg.wParam;
	}




