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

	PROGRAM:	HFORM.C version 2.0

	PURPOSE:
	
		A program demonstrating the handwriting and ink edit controls
		(Hedit, Bedit, Iedit).

	COMMENTS:

		HFORM has several edit fields, typical of a generic
		form application (name, address, city, etc.).  The
		application registers itself as a pen aware application.

		The lowermost field illustrates the ink edit control.
		In an earlier version of this sample, an HEDIT variant
		occupied that space.

		HFORM has an accelerator gesture (circle-'D') which
		automatically brings up the sample dialog box.

		Tabs are set at 1, 4, 7...

		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) 1992-1994  Microsoft Corporation.  All Rights Reserved.

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

#define WIN31
#include <windows.h>
#include <penwin.h>
#include "hfres.h"
#include "hform.h"


#define  cFieldsMax 7
#define  iFieldIEDIT 6		/* IEDIT field  */
#define  cFormWidth 180	   /* In dialog units */ 
#define  cFormHeight 220   /* In dialog units */ 
#define  cbBufMax	1024		/* for file transfer  */

#define Unused(x)		x		/* To prevent warning message */

/* globals */

BOOL fAppwideHookSet;
BOOL fPenKernel = FALSE;
char *szHformClass			= "HformSampleClass";
char *szHformWnd				= "Hform Sample";
char *szHookErr				= "Could not set hook";
char *szAppName				= "hform";
char *szTarget					= "Target";
char *szFile					= "HFORM.DAT";
HANDLE hAccel;				   /* Menu Accelerator Table */ 
HANDLE hwndMain;			   /* Parent window to all fields */
HANDLE hInstanceApp;

FIELD rgfield[cFieldsMax] =	
	{
	{"Name:",     6,   8,  44,   8, 128, 10, 0, ALC_DEFAULT, 1, FIELDHEDIT, NULL},
	{"Address:",  6,  24,  44,  24, 128, 10, 0, ALC_DEFAULT, 1, FIELDHEDIT, NULL},
	{"City:",     6,  40,  44,  40,  84, 10, 0, ALC_DEFAULT, 1, FIELDHEDIT, NULL},
	{"Zip:",    132,  40, 147,  40,  25, 10, 0, ALC_NUMERIC, 1, FIELDHEDIT, NULL},
	{"Notes:",    6,  56,  44,  54, 130, 40, WS_BORDER | ES_MULTILINE , ALC_DEFAULT, 1, FIELDHEDIT, NULL},
	{"Tel #:",    6, 102,  44,  98, BXD_CELLWIDTH*10+1, BXD_CELLHEIGHT, 0, ALC_NUMERIC, 1, FIELDBEDIT, NULL},
	{"Map:",      6, 120,  44, 118, 130, 70, WS_BORDER, 0, 1, FIELDIEDIT, NULL},
	};





int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
						LPSTR lpszCommandLine, int cmdShow)
/***************************************************************

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

PURPOSE:
	
	Main Windows application function.

***************************************************************/
	{
	MSG	msg;

	Unused(lpszCommandLine);

	/* check for Pen Windows: */
	if (!GetSystemMetrics(SM_PENWINDOWS))
		{
		ErrBox(hInstance, rsNoPW);
		return 1;
		}

	if (!hPrevInstance)
		{
		if (!FInitApp(hInstance))
			{
			return 1;
			}
		}

   /* Register this app */ 
	RegisterPenApp(RPA_DEFAULT, PENVER);

	if (FInitInstance(hInstance, hPrevInstance, cmdShow))
		{
		while (GetMessage((LPMSG)&msg,NULL,0,0) )
			{
			/* Check for menu accelerator message */ 
			if (!TranslateAccelerator(hwndMain, hAccel, &msg))
				{
				TranslateMessage((LPMSG)&msg);
				DispatchMessage((LPMSG)&msg);
				}
			}
		}
	else
		msg.wParam = 0;

   /* Unregister this app */ 
	RegisterPenApp(0, 0);

	return msg.wParam;
	}



void NEAR PASCAL ErrBox(HINSTANCE hinst, int rs)
/***************************************************************

FUNCTION:	ErrBox(hinst, rs)

PURPOSE:
	
	Put user error message based on resource id.

***************************************************************/
	{
	char	sz[128];

	if (LoadString(hinst, rs, (LPSTR)sz, sizeof(sz)))
		MessageBox(NULL, (LPSTR)sz, NULL, MB_OK);
	}


BOOL NEAR PASCAL FInitApp(HANDLE hInstance)
/***************************************************************

FUNCTION:	FInitApp(hInstance)

PURPOSE:
	
	Initialize application data and register window class.
	To be called only once.

	Returns FALSE if function could not register window class.
	TRUE if successful.

***************************************************************/
	{
	WNDCLASS	wc;
	HCURSOR	hcursor;
	
	hcursor = LoadCursor(NULL, IDC_ARROW);

	/* Register Main window class */

	wc.hCursor = hcursor;
	wc.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(iconHform));
	wc.lpszMenuName = MAKEINTRESOURCE(menuMain);
	wc.lpszClassName = (LPSTR)szHformClass;
	wc.hbrBackground = (HBRUSH)COLOR_WINDOW+1;
	wc.hInstance = hInstance;
	wc.style = CS_VREDRAW | CS_HREDRAW;
	wc.lpfnWndProc = HformWndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;

	return RegisterClass((LPWNDCLASS) &wc);
	}


BOOL NEAR PASCAL FInitInstance(HANDLE hInstance, HANDLE hPrevInstance,
										int cmdShow)
/***************************************************************

FUNCTION:	FInitInstance(hInstance, hPrevInstance, cmdShow)

PURPOSE:
	
	Initialize all data structures for program instance and create
	the necessary windows.

	Returns TRUE if successsful, FALSE if failed.

***************************************************************/
	{
	int	i;
	LONG	lT				= GetDialogBaseUnits();
	int	cxDlgBase	= LOWORD(lT);
	int	cyDlgBase	= HIWORD(lT);
	WORD	wSysFlags;

	Unused(hPrevInstance);

   hInstanceApp = hInstance;

	// what kind of penwin are we running on?
	GetPenMiscInfo(PMI_SYSFLAGS, (LPARAM)(LPWORD)&wSysFlags);
	fPenKernel = !(wSysFlags & PWF_INPUT);

	/* Convert field coordinates to window coordinates */
	for (i = 0; i < cFieldsMax; i++)
		{
		rgfield[i].xStatic = (rgfield[i].xStatic * cxDlgBase)/4;
		rgfield[i].yStatic = (rgfield[i].yStatic * cyDlgBase)/8;
		rgfield[i].xEdit = (rgfield[i].xEdit * cxDlgBase)/4;
		rgfield[i].yEdit = (rgfield[i].yEdit * cyDlgBase)/8;
		rgfield[i].cxEdit = (rgfield[i].cxEdit * cxDlgBase)/4;
		rgfield[i].cyEdit = (rgfield[i].cyEdit * cyDlgBase)/8;
		}

	/* Create Main window */

	if (hwndMain = CreateWindow((LPSTR)szHformClass, 
		(LPSTR)szHformWnd,	  
		WS_CLIPCHILDREN | WS_OVERLAPPED | WS_SYSMENU,
		CW_USEDEFAULT, 0, (cFormWidth*cxDlgBase)/4, (cFormHeight*cyDlgBase)/8,
		(HWND)NULL,
		(HWND)NULL,
		(HANDLE)hInstance,
		(LPSTR)NULL
		))
		{

		hAccel = LoadAccelerators (hInstance, IDHFORM);
		ShowWindow(hwndMain, cmdShow);
		UpdateWindow(hwndMain);
		}

	return hwndMain != NULL;
	}





LONG FAR PASCAL HformWndProc(HWND hwnd, UINT message,
			   WPARAM wParam, LPARAM lParam)
/***************************************************************

FUNCTION:	HformWndProc(hwnd, message, wParam, lParam)

PURPOSE:
	
	Windows procedure for application's parent window.

***************************************************************/
	{
	int		i;
	LONG		lRet	= 0L;

	static HWND	hwndFocusField = NULL;

	switch (message)
		{
		case WM_CREATE:
			/* Create fields */

			if (!FCreateForm(hwnd))
				{
				lRet = 1L;	/* Failed */
				break;	
				}

			/* Initialize focus to first edit control */

			hwndFocusField = rgfield[0].hwnd;
			SetFocus(hwndFocusField);
			break;

      case WM_LBUTTONDOWN:
         {
         LONG lExtraInfo = GetMessageExtraInfo();

         if (IsPenEvent(message, lExtraInfo) && 
            (DoDefaultPenInput(hwnd, EventRefFromLparam(lExtraInfo))
             == PCMR_OK))
               return 1L;
         }
      break;

		case WM_COMMAND:
			{
			/* Edit control commands */
         WORD wNotify = HIWORD(lParam);

			if (wNotify == EN_SETFOCUS || wNotify == IN_SETFOCUS)
				{
				/* Field focus is being set */
				hwndFocusField = LOWORD(lParam);
				break;
				}

			/* Menu commands */

			switch (wParam)
				{
				case miExit:
					DestroyWindow(hwnd);
					break;

				case miClearAll:
					for (i = 0; i < cFieldsMax; i++)
						{
						if (rgfield[i].hwnd != NULL)
							{
						   /* Clear picture.  Forces edit back into picture mode */ 
							if (rgfield[i].wFieldType==FIELDIEDIT)
								SendMessage(rgfield[i].hwnd, WM_PENCTL,
									IE_SETINK, NULL);
							else 
								SendMessage(rgfield[i].hwnd, WM_SETTEXT,
									0, (LONG)(LPSTR)"");
							}
						}
					SetFocus(rgfield[0].hwnd);
					break;

				case miSampleDlg:
					SampleDialog(hwnd);
					break;

				case miLoad:
				case miSave:
					FLoadSave(wParam == miLoad);
					break;

				case miNextField:
				   /* Focus on the next field */ 
					ProcessFieldChange(hwndFocusField, (WORD) chNextField);
					break;

				case miPrecField:
				   /* Set Focus on the preceeding field */ 
					ProcessFieldChange(hwndFocusField, (WORD) chPrecField);
					break;


				default:
					break;
				}
			break;
			}

		case WM_PAINT:
			{
			PAINTSTRUCT ps;
			HDC			hdc;

			hdc = BeginPaint(hwnd, &ps);
			SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
			SetBkMode(hdc, TRANSPARENT);

			for (i = 0; i < cFieldsMax; i++)
				{
				PFIELD pfield = &rgfield[i];

				TextOut(hdc, pfield->xStatic, pfield->yStatic, pfield->szTitle,
					lstrlen(pfield->szTitle));
				}

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

		case WM_SETFOCUS:
			SetFocus(hwndFocusField);
			break;

		case WM_DESTROY:
         PostQuitMessage(0);
			break;

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

	return lRet;
	}


BOOL NEAR PASCAL FCreateForm(HWND hwndParent)
/***************************************************************

FUNCTION:	FCreateForm(hwndParent)

PURPOSE:
	
	This function creates the fields.

	Returns TRUE is successful, FALSE if field hedit window could
	not be created.

***************************************************************/
	{
	int		i;
	HRC		hrc;
	LONG		lT		= GetDialogBaseUnits();
	int		cx		= LOWORD(lT)/2;
	int		cy		= HIWORD(lT)/2;
	char		*szClass[3];

	szClass[FIELDHEDIT] = "hedit";
	szClass[FIELDBEDIT] = "bedit";
	szClass[FIELDIEDIT] = "iedit";

	for (i = 0; i < cFieldsMax; i++)
		{
		PFIELD pfield = &rgfield[i];
		DWORD dwStyle = WS_VISIBLE | WS_CHILD | pfield->dwEditStyle;

		if (fPenKernel)
			dwStyle |= WS_BORDER;	// no underline available; subst border

		pfield->hwnd = CreateWindow(
			(LPSTR)szClass[pfield->wFieldType],
			(LPSTR)NULL,
			dwStyle,
			pfield->xEdit,
			pfield->yEdit,
			pfield->cxEdit,
			pfield->cyEdit,
			hwndParent,	
			(HMENU)NULL,
			GetWindowWord(hwndParent, GWW_HINSTANCE),
			(LPSTR)NULL);

		if (!pfield->hwnd)
			{
			continue;
			}

		/* Set alphabet preferences for this edit control */

		if (hrc = (HRC)SendMessage(pfield->hwnd, WM_PENMISC, PMSC_GETHRC, 0L))
			{
			SetAlphabetHRC(hrc, pfield->alc, NULL);
			SetWordlistCoercionHRC(hrc, SCH_ADVISE);
			if (!pfield->fEnableGestures)		// default: all enabled 
				EnableGestureSetHRC(hrc, GST_ALL, FALSE);
			SendMessage(pfield->hwnd, WM_PENMISC, PMSC_SETHRC, (LPARAM)hrc);
			DestroyHRC(hrc);
			}

		/* If no border, put underline in unless just kernel */ 
		if ((pfield->dwEditStyle & ES_MULTILINE) == 0
			&& pfield->wFieldType==FIELDHEDIT
			&& !fPenKernel)
				SendMessage(pfield->hwnd, WM_HEDITCTL, HE_SETUNDERLINE, 1L);
		}

	return TRUE;
	}


VOID NEAR PASCAL SampleDialog(HWND hwnd)
/***************************************************************

FUNCTION:	SampleDialog()

PURPOSE:
	
	Brings up a sample dialog containing an EDIT (not HEDIT) control.
	If we're running under Pen Windows system, then RegisterPenApp
	has been called, in which case the EDIT will act like an HEDIT.

***************************************************************/
	{
	FARPROC lpSampleDlg;
	HANDLE hinstance=GetWindowWord(hwnd, GWW_HINSTANCE);

	lpSampleDlg = MakeProcInstance((FARPROC) SampleDlgProc, hinstance);
	DialogBox(hinstance, "SampleH",	hwnd, lpSampleDlg);
	FreeProcInstance(lpSampleDlg);
	}


HANDLE FAR PASCAL SampleDlgProc(HWND hdlg, WORD message, WORD wParam, LONG lParam)
/***************************************************************

FUNCTION:	SampleDlgProc(hdlg, message, wParam, lParam)

PURPOSE:
	
	Dialog window procedure.

***************************************************************/
	{
	char	szDlgString[cbSzDlgMax];

	Unused(lParam);

	switch (message)
		{
		case WM_COMMAND:
			{
			switch (wParam)
				{
            case IDOK:
					*szDlgString = 0;
					GetDlgItemText(hdlg, IDC_EDIT, szDlgString, cbSzDlgMax);
					if (*szDlgString != 0)
						{
	           		EndDialog(hdlg, 1);
               	return TRUE;
						}
					break;	/* Don't end dialog with empty box */

            case IDCANCEL:
               EndDialog(hdlg, 0);
					return TRUE;
				}
			}
			break;

      case WM_INITDIALOG:		
      	SetDlgItemText(hdlg, IDC_EDIT, "Sample Name");
			/* Set focus to edit field and select all text */
      	SetFocus(GetDlgItem(hdlg, IDC_EDIT));
			SendDlgItemMessage(hdlg, IDC_EDIT, EM_SETSEL, 0, MAKELONG(0,-1)); 
      	break;
		}
	return FALSE;
	}

	
BOOL NEAR PASCAL ProcessFieldChange (HWND hwndFocusField,  WORD wParam)	
/***************************************************************

FUNCTION:	ProcessFieldChange (HWND hwndFocusField,  WORD wParam)

PURPOSE:
	
	Set the focus on either the next or previous field based on
	the value of wParam. wParam is can be set to chNextField or
	chPrecField.  The hwndFocusField parameter is assigned the value
	of the newly focused field.

	Returns TRUE if successful, FALSE otherwise. 

***************************************************************/
	{
  	int	i;
  	int	inc;
	
	if ((wParam != chNextField) && (wParam != chPrecField))
		return FALSE;

	inc = wParam == chPrecField ? cFieldsMax-1 : 1;
	i = (IFromHwnd(hwndFocusField)+inc) %(cFieldsMax);
	hwndFocusField = rgfield[i].hwnd;
	SetFocus(hwndFocusField);

	return TRUE;
	}



int NEAR PASCAL IFromHwnd(HWND hwnd)
/***************************************************************

FUNCTION:	IFromHwnd(HWND hwnd)

PURPOSE:
	
	Returns the index into the rgfield which corresponds to
	the entry containing parameter hwnd.

	Returns 0 if a match is not found.

***************************************************************/
	{
	register int	i;
	LPFIELD			lpfield;

	for (lpfield = rgfield, i = 0; i < cFieldsMax; i++, lpfield++)
		if (lpfield->hwnd == hwnd)
			return i;

	return 0;	 /* default on err */ 
	}


BOOL NEAR PASCAL FLoadSaveText(HFILE hfile, BOOL fLoad, HWND hwnd)
/***************************************************************

FUNCTION:	FLoadSaveText(HFILE hfile, BOOL fLoad, HWND hwnd)

PURPOSE:
	
	Loads (fLoad TRUE) or saves (fLoad FALSE) the contents of
	a text field. The file format is 2 bytes for length followed
	by that many bytes of text (no null term)

	Returns TRUE if successful.

***************************************************************/
	{
	char	sz[cbSzTMax];
	int	cb;
	BOOL	fRet;

	if (fLoad)
		{
		if (fRet = _lread(hfile, &cb, sizeof(int)) != HFILE_ERROR
			&& _lread(hfile, sz, cb) != HFILE_ERROR)
			{
			sz[cb] = '\0';	// terminate
			SetWindowText(hwnd, (LPSTR)sz);
			}
		}
	else	// save text to file
		{
		cb = GetWindowText(hwnd, (LPSTR)sz, sizeof(sz));
		fRet = _lwrite(hfile, &cb, sizeof(int)) != HFILE_ERROR
			&& _lwrite(hfile, sz, cb) != HFILE_ERROR;
		}
	return fRet;
	}


BOOL NEAR PASCAL FLoadSave(BOOL fLoad)
/***************************************************************

FUNCTION:	FLoadSave(BOOL fLoad)

PURPOSE:
	
	Loads (fLoad TRUE) or saves (fLoad FALSE) the contents of
	the form, in file HFORM.DAT.

	Returns TRUE if successful.

***************************************************************/
	{
	HCURSOR	hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
	HFILE		hfile;
	OFSTRUCT	of;
	int		i;
	BOOL		fRet = TRUE;

	if ((hfile = OpenFile((LPSTR)szFile, &of,
		fLoad? OF_READ: OF_CREATE)) != HFILE_ERROR)
		{
		// first do the text fields:
		for (i = 0; fRet && i < cFieldsMax-1; i++)
			fRet = FLoadSaveText(hfile, fLoad, rgfield[i].hwnd);

		// now do the IEDIT (assumed to be last):
		if (fRet)
			{
			HPENDATA hpndt;

			if (fLoad)
				{
				if (!(hpndt = ReadPenData(hfile))
					|| (SendMessage(rgfield[iFieldIEDIT].hwnd, WM_PENCTL,
						IE_SETINK, MAKELONG(IESI_REPLACE, (WORD)hpndt)) != IER_OK))
							fRet = FALSE;
				}
			else	// save
				{
				DWORD dw = (DWORD)SendMessage(rgfield[iFieldIEDIT].hwnd,
					WM_PENCTL, IE_GETINK, MAKELONG(IEGI_ALL, 0));

				if (LOWORD(dw) != IER_OK)
					fRet = FALSE;
				else if (hpndt = (HPENDATA)HIWORD(dw))		// assignment
					{
					fRet = WritePenData(hfile, hpndt);
					DestroyPenData(hpndt);	// we took it so we drop it too
					}
				}
			}
		_lclose(hfile);
		}
	else
		fRet = FALSE;

	SetCursor(hCursor);
	return fRet;
	}


HPENDATA NEAR PASCAL ReadPenData(HFILE hfile)
/***************************************************************

FUNCTION:	ReadPenData(HFILE hfile)

PURPOSE:
	
	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.

	Returns a handle to a pendata if successful.

	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(HFILE hfile, HPENDATA hpndt)
/***************************************************************

FUNCTION:	WritePenData(HFILE hfile, HPENDATA hpndt)

PURPOSE:

	Writes pen data into a file, preceded by a UINT consisting of
	the size of the pen data in bytes.

	Returns TRUE if successful.

	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;
	}
