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

    PROGRAM:    HFORM.C

    PURPOSE:
    
        A program demonstrating the handwriting edit control
        (Hedit).

    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
        so that the edit controls will be replaced by hedit
        controls.

        This application will run under Win 3.0.  When running
        under Win 3.0, the edit control functionality will be
        present.  The boxed edit field and the picture field
        will not be present.

        The Delay Recognition menu, when checked, will cause
        ink to be captured in the inking field.  When the
        menu item is unchecked, the first tap in the field
        will cause recognition.  After that, the field will
        behave like a normal HEDIT control.

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

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

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

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


/* Initialization of globals */

char *  szHformClass        = "HformSampleClass";
char *  szHformWnd          = "Hform Sample";
BOOL fDelayRecog = FALSE;  /* Set to true if just capture ink */ 
BOOL fAppwideHookSet;
BOOL fHookIsSet = FALSE;
char *  szHookErr           = "Could not set hook";
HANDLE hPenWin = NULL;
BOOL (FAR PASCAL *SetAppRecogHook)(UINT, UINT, HWND);
HANDLE hAccel;                 /* Menu Accelerator Table */ 
HANDLE hwndMain;               /* Parent window to all fields */ 
#define  cFieldsMax 7
#define  cFormWidth 180    /* In dialog units */ 
#define  cFormHeight 220   /* In dialog units */ 

static FIELD rgfield[cFieldsMax] =  
    {
    {"Name:",       6,   8,  44,   8, 128, 10, 0, ALC_DEFAULT, FIELDEDIT, NULL},
    {"Address:",    6,  24,  44,  24, 128, 10, 0, ALC_DEFAULT, FIELDEDIT, NULL},
    {"City:",       6,  40,  44,  40,  84, 10, 0, ALC_DEFAULT, FIELDEDIT, NULL},
    {"Zip:",      132,  40, 147,  40,  25, 10, 0, ALC_NUMERIC | ALC_GESTURE, FIELDEDIT, NULL},
    {"Notes:",      6,  56,  44,  54, 130, 40, WS_BORDER | ES_MULTILINE , ALC_DEFAULT, FIELDEDIT, NULL},
    {"Tel #:",      6, 102,  44,  98, BXD_CELLWIDTH*10+1, BXD_CELLHEIGHT, 0, ALC_NUMERIC | ALC_GESTURE, FIELDBEDIT, NULL},
    {"Directions:", 6, 120,  44, 118, 130, 70, WS_BORDER | ES_MULTILINE, ALC_DEFAULT, FIELDPIC, 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;
    VOID (FAR PASCAL *RegisterPenApp)(UINT, BOOL) = NULL;

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

   /* If running on a Pen Windows system, register this app so all
        EDIT controls in dialogs are replaced by HEDIT controls.
        (Notice the CONTROL statement in the RC file is "EDIT",
        RegisterPenApp will automatically change that control to
        an HEDIT. */ 

    if ((hPenWin = GetSystemMetrics(SM_PENWINDOWS)) != NULL)
        {
       /* We do this fancy GetProcAddress simply because we don't
            know if we're running Pen Windows. */ 
        if ( ((FARPROC)RegisterPenApp = GetProcAddress(hPenWin, "RegisterPenApp"))!= NULL)
            (*RegisterPenApp)(RPA_DEFAULT, TRUE);
        (FARPROC)SetAppRecogHook = GetProcAddress(hPenWin, "SetRecogHook");
        }

    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 */ 
    if (RegisterPenApp != NULL)
            (*RegisterPenApp)(RPA_DEFAULT, FALSE);

    return msg.wParam;
    }



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

    Unused(hPrevInstance);

    /* 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-(hPenWin ? 0 : 2)) * cyDlgBase)/8;
        rgfield[i].cxEdit = (rgfield[i].cxEdit * cxDlgBase)/4;
        rgfield[i].cyEdit = ((rgfield[i].cyEdit+(hPenWin ? 0 : 2)) * 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, MAKEINTRESOURCE(IDHFORM));

        if (hPenWin && SetAppRecogHook)
            {
           /* Set a hook so our app window can get recognition results
                before the HEDIT control does.  We do this for an 
                accelerator gesture. */ 
            if(!(fHookIsSet = (*SetAppRecogHook)(HWR_APPWIDE, HKP_SETHOOK, hwndMain)))
                {
                MessageBox(NULL, szHookErr, szHformWnd, MB_ICONSTOP|MB_OK);
                DestroyWindow(hwndMain);
                hwndMain = NULL;
                }
            }

        ShowWindow(hwndMain, cmdShow);
        UpdateWindow(hwndMain);
        }

   /* If not running under Pen Win, disable delayed recognition option */ 
    if (hwndMain != NULL && !hPenWin)
        EnableMenuItem(GetMenu(hwndMain), miDelayRecog, MF_GRAYED|MF_BYCOMMAND);


    return hwndMain != NULL;
    }





LONG FAR PASCAL __export 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_COMMAND:
            {
            /* Edit control commands */

            if (HIWORD(lParam) == EN_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==FIELDPIC && fDelayRecog)
                                SendMessage(rgfield[i].hwnd, WM_HEDITCTL, HE_SETINKMODE , (LONG)0L);
                            else 
                                SendMessage(rgfield[i].hwnd, WM_SETTEXT, 0, (LONG)(LPSTR)"");
                            }
                        }
                    SetFocus(rgfield[0].hwnd);
                    break;

                case miSampleDlg:
                    SampleDialog(hwnd);
                    break;

                case miDelayRecog:
                    fDelayRecog = !fDelayRecog;
                    ModifyMenu (GetMenu(hwndMain), miDelayRecog, MF_BYCOMMAND|MF_STRING, miDelayRecog, (LPSTR)(fDelayRecog ? szRecog : szDelay));
                    {
                    SetFocus(rgfield[0].hwnd);
                    for (i = 0; i < cFieldsMax; i++)
                        {
                        if (rgfield[i].hwnd != NULL && rgfield[i].wFieldType==FIELDPIC)
                            {
                            if (fDelayRecog)
                                {
                               /* Place control in delayed recognition mode */ 
                                SendMessage(rgfield[i].hwnd, WM_HEDITCTL, HE_SETINKMODE , (LONG)0L);
                                }
                            else
                                {
                               /* Send message to hedit to recognize data */ 
                                SendMessage(rgfield[i].hwnd, WM_HEDITCTL, HE_STOPINKMODE , (LONG)HEP_RECOG);
                                }
                            }
                        }
                    }
                    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;
            }
        case WM_HOOKRCRESULT:      /* getting recognition results */ 
            {
            LPRCRESULT lpr = (LPRCRESULT)lParam;
            SYV syv;
            WORD wResultsType = lpr->wResultsType;

            /* Does this result contain:
             a gesture which has been translated by the gesture mapper
             or an untranslated gesture */

            if(wResultsType & (RCRT_GESTURE|RCRT_GESTURETRANSLATED|RCRT_GESTURETOKEYS))
                {
                /* Get it from the results returned by the recognizer
                   lpr->lpsyv contains results which could have been
                   translated by the gesture mapper
                   Hence we get the original recognition results */

                syv = lpr->syg.lpsye->syv;

               /* This is an example of an accelerator gesture.  The
                    user writes a circle with a 'D' (or 'd') inside,
                    and we look for this gesture.  

                    However, if the user through the gesture mapper has mapped
                    circle d to something else, then we don't need to do
                    anything here. */

                if(syv == SyvAppGestureFromLoAnsi('d')
                    || syv == SyvAppGestureFromUpAnsi('D'))
                    {
                    PostMessage(hwnd, WM_COMMAND, miSampleDlg, 0L);

                   /* Let target window know result has already been acted upon */ 

                    SetAlreadyProcessed(lpr);

                    lRet = 1L;
                    }
                }
            }
            break;

        case WM_SETFOCUS:
            SetFocus(hwndFocusField);
            break;

        case WM_DESTROY:
            {
            if (hPenWin)
                {
                if(SetAppRecogHook && fHookIsSet)
                    (*SetAppRecogHook)(HWR_APPWIDE, HKP_UNHOOK, hwnd);
                }

            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;
    RC  rcin;
    LONG        lT      = GetDialogBaseUnits();
    int     cx      = LOWORD(lT)/2;
    int     cy      = HIWORD(lT)/2;
    RECTOFS rectofs;

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

        switch (pfield->wFieldType)
            {
        case FIELDPIC:
        case FIELDEDIT:
            /* Create Hedit window. */
            /* If running on a Pen Windows system, this app will have
                been registered, so all EDIT controls will be changed to
                HEDIT controls */

            pfield->hwnd = CreateWindow(
                    (LPSTR)"edit",
                    (LPSTR)NULL,
                    dwStyle,
                    pfield->xEdit,
                    pfield->yEdit,
                    pfield->cxEdit,
                    pfield->cyEdit,
                    hwndParent, 
                    (HMENU)NULL,
                    GetWindowWord(hwndParent, GWW_HINSTANCE),
                    (LPSTR)NULL);

            break;

        case FIELDBEDIT:     /* Comb field */ 
            pfield->hwnd = CreateWindow(
                    (LPSTR)(hPenWin ? "bedit" : "edit"),
                    (LPSTR)NULL,
                    dwStyle,
                    pfield->xEdit,
                    pfield->yEdit,
                    pfield->cxEdit,
                    pfield->cyEdit,
                    hwndParent, 
                    (HMENU)NULL,
                    GetWindowWord(hwndParent, GWW_HINSTANCE),
                    (LPSTR)NULL);
            break;
        }
            
        if (!pfield->hwnd)
            {
            continue;
            }

        /* Set RC preferences for this edit control */

        if (hPenWin)
            {
            if (SendMessage(pfield->hwnd, WM_HEDITCTL, HE_GETRC, (LONG)((LPRC)&rcin)))
                {
                rcin.alc = pfield->alc;
                SendMessage(pfield->hwnd, WM_HEDITCTL, HE_SETRC, (LONG)((LPRC)&rcin));
                }

            /* Change default inflation rectangle offset so it is 
                half the base dialog unit for each respective axis. */

            rectofs.dLeft = -cx;
            rectofs.dTop = -cy;
            rectofs.dRight = cx;
            rectofs.dBottom = cy;
            SendMessage(pfield->hwnd, WM_HEDITCTL, HE_SETINFLATE, (LONG)((LPRECTOFS)&rectofs));

           /* If no border, put under line in. */ 
            if ((pfield->dwEditStyle & ES_MULTILINE) == 0 && pfield->wFieldType==FIELDEDIT)
                SendMessage(pfield->hwnd, WM_HEDITCTL, HE_SETUNDERLINE, (LONG)(1));
            }

        }

    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 __export 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, inc, iCount;
    WORD wRet = FALSE;
    LONG lInkData;
    
    if ((wParam != chNextField) && (wParam != chPrecField))
        return FALSE;

    inc = wParam == chPrecField ? cFieldsMax-1 : 1;
    i = (IFromHwnd(hwndFocusField)+inc) %(cFieldsMax);


    /*
        To determine if an hedit is in cold recognition mode, send a GETINKHANDLE
        message to the control. If the loword of the return code is NULL, then the
        control is not in   cold recognition mode, and the focus can be set. If it
        is in cold mode, skip over it and check the next field.
        iCount is used to break the loop if all fields are in cold mode.
    */

    iCount=0;
    while (iCount<cFieldsMax && !wRet)
        {
        if (!LOWORD(SendMessage(rgfield[i].hwnd, WM_HEDITCTL, HE_GETINKHANDLE,(LONG)(LPSTR)&lInkData)))
            {
            hwndFocusField = rgfield[i].hwnd;
            SetFocus(hwndFocusField);
            wRet = TRUE;
            }
        else
            i = (i+inc) %(cFieldsMax);       /* Calculate the next field */ 
        iCount++;
        }

    return wRet;
    }



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 */ 
    }
