#include <windows.h>
#include <commdlg.h>
#include <imm.h>
#include "imeeudc.h"

//
//   J. J. Lee  9-29-1994
//
//         UI of this program
//  +--------------------------------------------------+
//  +--------------------------------------------------+
//  |                                                  |
//  |                          +------------+          |
//  |                          |    FA40    |          |
//  |                          +------------+          |
//  |                                                  |
//  |                          +------------+  +-----+ |
//  | (IMEName iStartIME + 0)  |   GOLD     |  |  ^  | |
//  |                          +------------+  |  |  | |
//  |                                          |     | |
//  |                          +------------+  |scrol| |
//  | (IMEName iStartIME + 1)  |   WOOD     |  | bar | |
//  |                          +------------+  |     | |
//  |                                          |     | |
//  |                          +------------+  |  |  | |
//  | (IMEName iStartIME + 2)  |   WATER    |  |  V  | |
//  |                          +------------+  +-----+ |
//  |                                                  |
//  |   +----------+                +-----------+      |
//  |   | Register |                |   Abort   |      |
//  |   +----------+                +-----------+      |
//  |                                                  |
//  +--------------------------------------------------+
//
// The scroll bar only appear when there are more than 3 IMEs
//

// This is a sample code for EUDC regsiter a new created word into IMEs



typedef struct _tagREGWORDSTRUCT {
    HKL   hKL;
    BOOL  bUpdate;
    TCHAR szIMEName[16];
    UINT  uIMENameLen;
    TCHAR szReading[14];
    DWORD dwReadingLen;
} REGWORDSTRUCT;

typedef REGWORDSTRUCT FAR *LPREGWORDSTRUCT;


typedef struct _tagIMELINKREGWORD {
    HIMC          hOldIMC;
    HIMC          hRegWordIMC;
    BOOL          fCompMsg;
    UINT          nEudcIMEs;
    UINT          nCurrIME;
    TCHAR         szEudcCodeString[4];
    REGWORDSTRUCT sRegWordStruct[1];
} IMELINKREGWORD;

typedef IMELINKREGWORD FAR *LPIMELINKREGWORD;


typedef struct _tagIMERADICALRECT {
    UINT nStartIME;
    UINT nPerPageIMEs;
    SIZE lTextSize;
    SIZE lCurrReadingExtent;
    HWND hRegWordButton;
    HWND hScrollWnd;
    RECT rcRadical[1];
} IMERADICALRECT;

typedef IMERADICALRECT FAR *LPIMERADICALRECT;


static const TCHAR     szAppName[] = TEXT("EUDC");
static const TCHAR     szMenuName[] = TEXT("ImeEudcMenu");
static const TCHAR     szRegWordCls[] = TEXT("Radical");
static const TCHAR     szImeLinkDlg[] = TEXT("ImeLinkDlg");


static HINSTANCE       hAppInst;


/************************************************************/
/*  SwitchToThisIME                                         */
/************************************************************/
void SwitchToThisIME(
    HWND hWnd,
    UINT uIndex)
{
    LPIMELINKREGWORD lpImeLinkRegWord;
    LPREGWORDSTRUCT  lpRegWordStructTmp;
    LPIMERADICALRECT lpImeLinkRadical;
    DWORD            fdwConversionMode, fdwSentenceMode;

    lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
        GWL_IMELINKREGWORD);

    if (lpImeLinkRegWord->nCurrIME == uIndex) {
        return;
    }

    if (uIndex >= lpImeLinkRegWord->nEudcIMEs) {
        MessageBeep((UINT)-1);
        return;
    }

    lpImeLinkRadical = (LPIMERADICALRECT)GetWindowLong(hWnd,
        GWL_RADICALRECT);

    if (uIndex < lpImeLinkRadical->nStartIME) {
        lpImeLinkRadical->nStartIME = uIndex;

        if (lpImeLinkRadical->hScrollWnd) {
            SCROLLINFO scInfo;

            scInfo.cbSize = sizeof(SCROLLINFO);
            scInfo.fMask = SIF_POS;
            scInfo.nPos = uIndex;

            SetScrollInfo(lpImeLinkRadical->hScrollWnd, SB_CTL, &scInfo, FALSE);
        }

        InvalidateRect(hWnd, NULL, TRUE);
    } else if ((uIndex - lpImeLinkRadical->nStartIME) >=
        lpImeLinkRadical->nPerPageIMEs) {
        lpImeLinkRadical->nStartIME = uIndex +
            lpImeLinkRadical->nPerPageIMEs - 1;

        if (lpImeLinkRadical->nStartIME > (lpImeLinkRegWord->nEudcIMEs -
            lpImeLinkRadical->nPerPageIMEs)) {
            lpImeLinkRadical->nStartIME = lpImeLinkRegWord->nEudcIMEs -
            lpImeLinkRadical->nPerPageIMEs;
        }

        if (lpImeLinkRadical->hScrollWnd) {
            SCROLLINFO scInfo;

            scInfo.cbSize = sizeof(SCROLLINFO);
            scInfo.fMask = SIF_POS;
            scInfo.nPos = lpImeLinkRadical->nStartIME;

            SetScrollInfo(lpImeLinkRadical->hScrollWnd, SB_CTL, &scInfo, FALSE);
        }

        InvalidateRect(hWnd, NULL, TRUE);
    } else {
        UINT i;

        i = lpImeLinkRegWord->nCurrIME - lpImeLinkRadical->nStartIME;

        i = i * RECT_NUMBER + RECT_RADICAL;

        InvalidateRect(hWnd, &lpImeLinkRadical->rcRadical[i], FALSE);

        i = uIndex - lpImeLinkRadical->nStartIME;

        i = i * RECT_NUMBER + RECT_RADICAL;

        InvalidateRect(hWnd, &lpImeLinkRadical->rcRadical[i], FALSE);
    }

    // avoid clear composition string
    SendMessage(hWnd, WM_EUDC_COMPMSG, 0, FALSE);

    lpRegWordStructTmp = &lpImeLinkRegWord->sRegWordStruct[uIndex];

    // switch to this IME
    ActivateKeyboardLayout(lpRegWordStructTmp->hKL, 0);

    ImmGetConversionStatus(lpImeLinkRegWord->hRegWordIMC,
        &fdwConversionMode, &fdwSentenceMode);

    fdwConversionMode = (fdwConversionMode | IME_CMODE_EUDC |
        IME_CMODE_NATIVE) | (fdwConversionMode & IME_CMODE_SOFTKBD);
 
    ImmSetConversionStatus(lpImeLinkRegWord->hRegWordIMC,
        fdwConversionMode, fdwSentenceMode);

    SendMessage(hWnd, WM_EUDC_COMPMSG, 0, TRUE);

    lpImeLinkRegWord->nCurrIME = uIndex;

    *(LPTSTR)&lpRegWordStructTmp->szReading[
        lpRegWordStructTmp->dwReadingLen] = '\0';

    ImmSetCompositionString(lpImeLinkRegWord->hRegWordIMC, SCS_SETSTR,
        NULL, 0, lpRegWordStructTmp->szReading,
        lpRegWordStructTmp->dwReadingLen * sizeof(TCHAR));

    SetFocus(hWnd);

    return;
}

/************************************************************/
/*  RegWordCreate                                           */
/************************************************************/
LPIMELINKREGWORD RegWordCreate(
    HWND hWnd)
{
    HWND             hEudcEditWnd;
    UINT             nLayouts;
    HKL FAR         *lphKL;
    UINT             i, nIMEs;
    DWORD            dwSize;
    LPIMELINKREGWORD lpImeLinkRegWord;
    LPREGWORDSTRUCT  lpRegWordStructTmp;
    TCHAR            szStrBuf[16];
    HDC              hDC;
    SIZE             lTextSize;
    RECT             rcRect;
    LPIMERADICALRECT lpImeLinkRadical;
    TCHAR            szTitle[32];
    TCHAR            szMessage[256];

    hEudcEditWnd = GetWindow(GetParent(hWnd), GW_OWNER);

    nLayouts = GetKeyboardLayoutList(0, NULL);

    lphKL = GlobalAlloc(GPTR, sizeof(HKL) * nLayouts);

    if (!lphKL) {
        return (NULL);
    }

    lpImeLinkRegWord = NULL;

    // get all keyboard layouts, it will include all IMEs
    GetKeyboardLayoutList(nLayouts, lphKL);

    for (i = 0, nIMEs = 0; i < nLayouts; i++) {
        BOOL  fRet;
        HKL   hKL;
        TCHAR szImeEudcDic[80];

        hKL = *(lphKL + i);

        fRet = ImmIsIME(hKL);

        if (!fRet) {            // this is not an IME
            continue;
        }

        szImeEudcDic[0] = '\0';

        fRet = ImmEscape(hKL, (HIMC)NULL, IME_ESC_GET_EUDC_DICTIONARY,
            szImeEudcDic);

        if (!fRet) {
            continue;
        } else if (szImeEudcDic[0]) {
        } else {
            continue;
        }

        *(lphKL + nIMEs) = hKL;     // write back to the same buffer

        nIMEs++;
    }

    if (!nIMEs) {
        LoadString(hAppInst, IDS_NOIME_TITLE, szTitle, sizeof(szTitle));
        LoadString(hAppInst, IDS_NOIME_MSG, szMessage, sizeof(szMessage));

        MessageBox(hEudcEditWnd, szMessage, szTitle, MB_OK);
        goto RegWordCreateFreeHKL;
    }

    // now there are nIMEs can support IME EUDC dictionary
    dwSize = sizeof(IMELINKREGWORD) - sizeof(REGWORDSTRUCT) +
        sizeof(REGWORDSTRUCT) * nIMEs;

    lpImeLinkRegWord = (LPIMELINKREGWORD)GlobalAlloc(GPTR, dwSize);

    if (!lpImeLinkRegWord) {
        LoadString(hAppInst, IDS_NOMEM_TITLE, szTitle, sizeof(szTitle));
        LoadString(hAppInst, IDS_NOMEM_MSG, szMessage, sizeof(szMessage));

        MessageBox(hEudcEditWnd, szMessage, szTitle, MB_OK);
        goto RegWordCreateFreeHKL;
    }

    lpImeLinkRegWord->nEudcIMEs = nIMEs;

    lpRegWordStructTmp = &lpImeLinkRegWord->sRegWordStruct[0];

    for (i = 0; i < nIMEs; i++) {
        LRESULT lRet;
        UINT    j, uInternal, uReadingSize;

        lpRegWordStructTmp->hKL = *(lphKL + i);

        lRet = ImmEscape(lpRegWordStructTmp->hKL, (HIMC)NULL,
            IME_ESC_MAX_KEY, NULL);

        if (!lRet) {
            // error message - can not support this IME!
            lpImeLinkRegWord->nEudcIMEs--;
            continue;
        }

        uReadingSize = sizeof(TCHAR);

        for (j = 0; j < 256; j++) {
            uInternal = ImmEscape(lpRegWordStructTmp->hKL, (HIMC)NULL,
                IME_ESC_SEQUENCE_TO_INTERNAL, &j);
            if (uInternal > 255) {
                uReadingSize = sizeof(WCHAR);
                break;
            }
        }

        if (lRet * uReadingSize > sizeof(lpRegWordStructTmp->szReading) - sizeof(TCHAR)) {
            // error case, we can not support this IME
            // we should count this into data structure
            // error message - the reading of this IME is too long!
            lpImeLinkRegWord->nEudcIMEs--;
            continue;
        }

        lRet = ImmEscape(lpRegWordStructTmp->hKL, (HIMC)NULL,
            IME_ESC_IME_NAME, lpRegWordStructTmp->szIMEName);

        if (!lRet) {
            // error message - can not support this IME!
            lpImeLinkRegWord->nEudcIMEs--;
            continue;
        }

        // avoid length problem
        lpRegWordStructTmp->szIMEName[
            sizeof(lpRegWordStructTmp->szIMEName) / sizeof(TCHAR) - 1] = '\0';

        lpRegWordStructTmp->uIMENameLen =
            lstrlen(lpRegWordStructTmp->szIMEName);

        lpRegWordStructTmp++;
    }

    if (!lpImeLinkRegWord->nEudcIMEs) {
        LoadString(hAppInst, IDS_NOIME_TITLE, szTitle, sizeof(szTitle));
        LoadString(hAppInst, IDS_NOIME_MSG, szMessage, sizeof(szMessage));

        MessageBox(hEudcEditWnd, szMessage, szTitle, MB_OK);
        goto RegWordCreateFreeRegWord;
    }

    LoadString(hAppInst, IDS_CHINESE_CHAR, szStrBuf, sizeof(szStrBuf));

    hDC = GetDC(NULL);
    GetTextExtentPoint(hDC, szStrBuf, lstrlen(szStrBuf), &lTextSize);
    ReleaseDC(NULL, hDC);

    // decide the rectangle of IME radical
    GetWindowRect(hWnd, &rcRect);

    // we can show how many IME per page
    nIMEs = (rcRect.bottom - rcRect.top) / (2 * lTextSize.cy);

    if (lpImeLinkRegWord->nEudcIMEs <= nIMEs) {
        // all IMEs can fit in one page
        nIMEs = lpImeLinkRegWord->nEudcIMEs;
    }

    dwSize = sizeof(IMERADICALRECT) - sizeof(RECT) + sizeof(RECT) *
        RECT_NUMBER * nIMEs;

    lpImeLinkRadical = (LPIMERADICALRECT)GlobalAlloc(GPTR, dwSize);

    if (!lpImeLinkRadical) {
        // we can not handel any IME
        lpImeLinkRegWord->nEudcIMEs = 0;

        LoadString(hAppInst, IDS_NOMEM_TITLE, szTitle, sizeof(szTitle));
        LoadString(hAppInst, IDS_NOMEM_MSG, szMessage, sizeof(szMessage));

        MessageBox(hEudcEditWnd, szMessage, szTitle, MB_OK);
        goto RegWordCreateFreeRegWord;
    }

    lpImeLinkRadical->nStartIME = 0;
    lpImeLinkRadical->nPerPageIMEs = nIMEs;
    lpImeLinkRadical->lTextSize = lTextSize;

    if (lpImeLinkRegWord->nEudcIMEs > nIMEs) {
        // IMEs more than one page, add scroll bar
        SCROLLINFO scInfo;

        // IMEs more than one page, add scroll bar
        lpImeLinkRadical->hScrollWnd = CreateWindowEx(0,
            TEXT("scrollbar"), NULL,
            WS_CHILD|WS_VISIBLE|SBS_VERT,
            rcRect.right - rcRect.left - lTextSize.cx, 0,
            lTextSize.cx, rcRect.bottom - rcRect.top,
            hWnd, 0, hAppInst, NULL);

        scInfo.cbSize = sizeof(SCROLLINFO);
        scInfo.fMask = SIF_ALL;
        scInfo.nMin = 0;
        scInfo.nMax = lpImeLinkRegWord->nEudcIMEs - 1;
        scInfo.nPage = nIMEs;
        scInfo.nPos = 0;
        scInfo.nTrackPos = 0;

        SetScrollInfo(lpImeLinkRadical->hScrollWnd, SB_CTL, &scInfo, FALSE);
    }

    // decide the UI dimension
    for (i = 0; i < nIMEs; i++) {
        UINT j, k;

        // rectangle for IME name
        j = i * RECT_NUMBER + RECT_IMENAME;

        lpImeLinkRadical->rcRadical[j].left = lTextSize.cx;

        // add UI margin - UI_MARGIN
        lpImeLinkRadical->rcRadical[j].top = lTextSize.cy * (i * 4 + 1) / 2 -
            UI_MARGIN;

        lpImeLinkRadical->rcRadical[j].right =
            lpImeLinkRadical->rcRadical[j].left + lTextSize.cx * 4;

        // add UI margin - UI_MARGIN * 2
        lpImeLinkRadical->rcRadical[j].bottom =
            lpImeLinkRadical->rcRadical[j].top + lTextSize.cy +
            UI_MARGIN * 2;

        // rectangle for radical
        k = i * RECT_NUMBER + RECT_RADICAL;

        lpImeLinkRadical->rcRadical[k].left =
            lpImeLinkRadical->rcRadical[j].right + lTextSize.cx;

        // add UI margin - UI_MARGIN
        lpImeLinkRadical->rcRadical[k].top =
            lpImeLinkRadical->rcRadical[j].top;

        lpImeLinkRadical->rcRadical[k].right =
            lpImeLinkRadical->rcRadical[k].left + lTextSize.cx *
            (sizeof(lpRegWordStructTmp->szReading) / sizeof(TCHAR) / 2 - 1);

        // add UI margin - UI_MARGIN * 2
        lpImeLinkRadical->rcRadical[k].bottom =
            lpImeLinkRadical->rcRadical[k].top + lTextSize.cy +
            UI_MARGIN * 2;
    }

    SetWindowLong(hWnd, GWL_RADICALRECT, (LONG)lpImeLinkRadical);

RegWordCreateFreeRegWord:
    if (!lpImeLinkRegWord->nEudcIMEs) {
        GlobalFree((HGLOBAL)lpImeLinkRegWord);
        lpImeLinkRegWord = NULL;
    }

RegWordCreateFreeHKL:
    GlobalFree((HGLOBAL)lphKL);

    return (lpImeLinkRegWord);
}

/************************************************************/
/*  WmImeComposition                                        */
/************************************************************/
void WmImeComposition(
    HWND   hWnd,
    LPARAM lParam)
{
    LPIMELINKREGWORD lpImeLinkRegWord;
    LPREGWORDSTRUCT  lpRegWordStructTmp;
    LONG             lRet;
    BOOL             bUpdate;
    TCHAR            szReading[sizeof(lpRegWordStructTmp->szReading)];

    lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
        GWL_IMELINKREGWORD);

    lpRegWordStructTmp = &lpImeLinkRegWord->sRegWordStruct[
        lpImeLinkRegWord->nCurrIME];

    lRet = ImmGetCompositionString(lpImeLinkRegWord->hRegWordIMC,
        GCS_COMPREADSTR, szReading, sizeof(szReading));

    if (lRet < 0) {
        lpRegWordStructTmp->bUpdate = UPDATE_ERROR;
        return;
    }

    if (lRet > (sizeof(szReading) - sizeof(TCHAR))) {
        lRet = sizeof(szReading) - sizeof(TCHAR);
    }

    szReading[lRet / sizeof(TCHAR)] = '\0';

    if (lpRegWordStructTmp->dwReadingLen != (DWORD)lRet / sizeof(TCHAR)) {
        bUpdate = TRUE;
    } else if (lstrcmp(lpRegWordStructTmp->szReading, szReading)) {
        bUpdate = TRUE;
    } else {
        bUpdate = FALSE;
    }

    if (bUpdate) {
        LPIMERADICALRECT lpImeLinkRadical;
        UINT             i;
        UINT             j, k;

        lpImeLinkRadical = (LPIMERADICALRECT)GetWindowLong(hWnd,
            GWL_RADICALRECT);

        lstrcpy(lpRegWordStructTmp->szReading, szReading);

        if (lParam & GCS_RESULTSTR) {
            lpRegWordStructTmp->bUpdate = UPDATE_FINISH;
        } else {
            lpRegWordStructTmp->bUpdate = UPDATE_START;
        }

        lpRegWordStructTmp->dwReadingLen = (DWORD)lRet / sizeof(TCHAR);

        if (!IsWindowEnabled(lpImeLinkRadical->hRegWordButton)) {
            EnableWindow(lpImeLinkRadical->hRegWordButton, TRUE);
        }

        i = lpImeLinkRegWord->nCurrIME - lpImeLinkRadical->nStartIME;

        j = i * RECT_NUMBER + RECT_IMENAME;

        InvalidateRect(hWnd, &lpImeLinkRadical->rcRadical[j], FALSE);

        k = i * RECT_NUMBER + RECT_RADICAL;

        InvalidateRect(hWnd, &lpImeLinkRadical->rcRadical[k], FALSE);
    } else if (lParam & GCS_RESULTSTR) {
        LPIMERADICALRECT lpImeLinkRadical;
        UINT             i;
        UINT             j, k;

        lpImeLinkRadical = (LPIMERADICALRECT)GetWindowLong(hWnd,
            GWL_RADICALRECT);

        if (lpRegWordStructTmp->bUpdate) {
            lpRegWordStructTmp->bUpdate = UPDATE_FINISH;
        }

        i = lpImeLinkRegWord->nCurrIME - lpImeLinkRadical->nStartIME;

        j = i * RECT_NUMBER + RECT_IMENAME;

        InvalidateRect(hWnd, &lpImeLinkRadical->rcRadical[j], FALSE);

        k = i * RECT_NUMBER + RECT_RADICAL;

        InvalidateRect(hWnd, &lpImeLinkRadical->rcRadical[k], FALSE);
    } else {
    }

    return;
}

/************************************************************/
/*  lstrcmpn                                                */
/************************************************************/
int lstrcmpn(
    LPCTSTR lpctszStr1,
    LPCTSTR lpctszStr2,
    int     cCount)
{
    int i;

    for (i = 0; i < cCount; i++) {
        int iCmp = *lpctszStr1++ - *lpctszStr2++;
        if (iCmp) { return iCmp; }
    }

    return 0;
}

/************************************************************/
/*  EnumReading                                             */
/************************************************************/
int CALLBACK EnumReading(
    LPCTSTR         lpszReading,
    DWORD           dwStyle,
    LPCTSTR         lpszString,
    LPREGWORDSTRUCT lpRegWordStructTmp)
{
    int     iLen;
    DWORD   dwZeroSeq;
    LRESULT lRet;
    TCHAR   tszZeroSeq[8];

    iLen = lstrlen(lpszReading);

    if (iLen * sizeof(TCHAR) > sizeof(lpRegWordStructTmp->szReading) -
        sizeof(WORD)) {
        return (0);
    }

    lpRegWordStructTmp->dwReadingLen = (DWORD)iLen;

    lstrcpy(lpRegWordStructTmp->szReading, lpszReading);

    dwZeroSeq = 0;
    lRet = ImmEscape(lpRegWordStructTmp->hKL, (HIMC)NULL,
        IME_ESC_SEQUENCE_TO_INTERNAL, &dwZeroSeq);

    if (!lRet) { return (1); }

    iLen = 0;

    if (LOWORD(lRet)) {
#ifdef UNICODE
        tszZeroSeq[iLen++] = LOWORD(lRet);
#else
        if (LOWORD(lRet) > 0xFF) {
            tszZeroSeq[iLen++] = HIBYTE(LOWORD(lRet));
            tszZeroSeq[iLen++] = LOBYTE(LOWORD(lRet));
        } else {
            tszZeroSeq[iLen++] = LOBYTE(LOWORD(lRet));
        }
#endif
    }

    if (HIWORD(lRet) == 0xFFFF) {
        // This is caused by sign extent in Win9x in the return value of
        // ImmEscape, it causes an invalid internal code.
    } else if (HIWORD(lRet)) {
#ifdef UNICODE
        tszZeroSeq[iLen++] = HIWORD(lRet);
#else
        if (HIWORD(lRet) > 0xFF) {
            tszZeroSeq[iLen++] = HIBYTE(HIWORD(lRet));
            tszZeroSeq[iLen++] = LOBYTE(HIWORD(lRet));
        } else {
            tszZeroSeq[iLen++] = LOBYTE(HIWORD(lRet));
        }
#endif
    } else {
    }

    for (; lpRegWordStructTmp->dwReadingLen > 0;
        lpRegWordStructTmp->dwReadingLen -= iLen) {
        if (lstrcmpn(&lpRegWordStructTmp->szReading[
            lpRegWordStructTmp->dwReadingLen - iLen], tszZeroSeq, iLen) != 0) {
            break;
        }
    }

    lpRegWordStructTmp->szReading[lpRegWordStructTmp->dwReadingLen] = '\0';

    return (1);
}

/************************************************************/
/*  EudcCode                                                */
/************************************************************/
void EudcCode(
    HWND hWnd,
    UINT uCode)
{
    LPIMELINKREGWORD lpImeLinkRegWord;
    LPREGWORDSTRUCT  lpRegWordStructTmp;
    UINT             i;

    lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
        GWL_IMELINKREGWORD);

#ifdef UNICODE
    lpImeLinkRegWord->szEudcCodeString[0] = (WCHAR)uCode;
#else
    lpImeLinkRegWord->szEudcCodeString[0] = HIBYTE(uCode);
    lpImeLinkRegWord->szEudcCodeString[1] = LOBYTE(uCode);
#endif
    lpImeLinkRegWord->szEudcCodeString[2] =
        lpImeLinkRegWord->szEudcCodeString[3] = '\0';

    lpRegWordStructTmp = &lpImeLinkRegWord->sRegWordStruct[0];

    for (i = 0; i < lpImeLinkRegWord->nEudcIMEs; i++) {
        lpRegWordStructTmp->bUpdate = UPDATE_NONE;
        lpRegWordStructTmp->szReading[0] = '\0';
        lpRegWordStructTmp->dwReadingLen = 0;

        ImmEnumRegisterWord(lpRegWordStructTmp->hKL, EnumReading,
            NULL, IME_REGWORD_STYLE_EUDC,
            lpImeLinkRegWord->szEudcCodeString,
            lpRegWordStructTmp);

        lpRegWordStructTmp->dwReadingLen = lstrlen(lpRegWordStructTmp->szReading);

        lpRegWordStructTmp++;
    }

    lpRegWordStructTmp = &lpImeLinkRegWord->sRegWordStruct[
        lpImeLinkRegWord->nCurrIME];

    ImmSetCompositionString(lpImeLinkRegWord->hRegWordIMC, SCS_SETSTR,
        NULL, 0, lpRegWordStructTmp->szReading,
        lpRegWordStructTmp->dwReadingLen * sizeof(TCHAR));

    InvalidateRect(hWnd, NULL, FALSE);

    return;
}

/************************************************************/
/*  ChangeToOtherIME                                        */
/************************************************************/
void ChangeToOtherIME(
    HWND   hWnd,
    LPARAM lMousePos)
{
    POINT            ptMouse;
    LPIMERADICALRECT lpImeLinkRadical;
    UINT             i;
    BOOL             bFound;

    ptMouse.x = LOWORD(lMousePos);
    ptMouse.y = HIWORD(lMousePos);

    lpImeLinkRadical = (LPIMERADICALRECT)GetWindowLong(hWnd,
        GWL_RADICALRECT);

    bFound = FALSE;

    for (i = 0; i < lpImeLinkRadical->nPerPageIMEs; i++) {
        UINT j;

        j = i * RECT_NUMBER + RECT_RADICAL;

        if (PtInRect(&lpImeLinkRadical->rcRadical[j], ptMouse)) {
            bFound = TRUE;
            break;
        }
    }

    if (!bFound) {
        return;
    }

    SwitchToThisIME(hWnd, lpImeLinkRadical->nStartIME + i);

    return;
}

/************************************************************/
/*  ScrollIME                                               */
/************************************************************/
void ScrollIME(
    HWND   hWnd,
    WPARAM wParam)
{
    LPIMELINKREGWORD lpImeLinkRegWord;
    LPIMERADICALRECT lpImeLinkRadical;
    int              iLines;
    UINT             uIndex;

    lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
        GWL_IMELINKREGWORD);

    lpImeLinkRadical = (LPIMERADICALRECT)GetWindowLong(hWnd,
        GWL_RADICALRECT);

    switch (LOWORD(wParam)) {
    case SB_PAGEDOWN:
        // scroll (page size - 1)
        iLines = lpImeLinkRadical->nPerPageIMEs - 1;
        break;
    case SB_LINEDOWN:
        iLines = 1;
        break;
    case SB_PAGEUP:
        // scroll (page size - 1)
        iLines = 1 - lpImeLinkRadical->nPerPageIMEs;
        break;
    case SB_LINEUP:
        iLines = -1;
        break;
    case SB_TOP:
        // swicth to the first one
        SwitchToThisIME(hWnd, 0);
        return;
    case SB_BOTTOM:
        // swicth to the last one
        SwitchToThisIME(hWnd, lpImeLinkRegWord->nEudcIMEs - 1);
        return;
    case SB_THUMBPOSITION:
        if (lpImeLinkRadical->nStartIME == HIWORD(wParam)) {
            // we got the same value
            return;
        }

        // set to an invalid value
        lpImeLinkRadical->nStartIME = 0xFFFFFFFF;

        SwitchToThisIME(hWnd, HIWORD(wParam));
        return;
    default:
        return;
    }

    uIndex = lpImeLinkRegWord->nCurrIME;

    if (iLines > 0) {
        uIndex += (UINT)iLines;

        if (uIndex >= lpImeLinkRegWord->nEudcIMEs) {
            // should not exceed the total IMEs
            uIndex = lpImeLinkRegWord->nEudcIMEs - 1;
        }
    } else {
        UINT uLines;

        uLines = -iLines;

        if (uLines > uIndex) {
            uIndex = 0;
        } else {
            uIndex -= uLines;
        }
    }

    SwitchToThisIME(hWnd, uIndex);

    return;
}

/************************************************************/
/*  ScrollIMEByKey                                          */
/************************************************************/
void ScrollIMEByKey(
    HWND   hWnd,
    WPARAM wParam)
{
    switch (wParam) {
    case VK_NEXT:
        ScrollIME(hWnd, SB_PAGEDOWN);
        break;
    case VK_DOWN:   // can not work because dialog do not pass this key to us
        ScrollIME(hWnd, SB_LINEDOWN);
        break;
    case VK_PRIOR:
        ScrollIME(hWnd, SB_PAGEUP);
        break;
    case VK_UP:     // can not work because dialog do not pass this key to us
        ScrollIME(hWnd, SB_LINEUP);
        break;
    default:
        return;
    }

    return;
}

/************************************************************/
/*  RegWordGetFocus                                         */
/************************************************************/
void RegWordGetFocus(
    HWND hWnd)
{
    LPIMELINKREGWORD lpImeLinkRegWord;
    LPIMERADICALRECT lpImeLinkRadical;
    UINT             i;

    lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
        GWL_IMELINKREGWORD);

    lpImeLinkRadical = (LPIMERADICALRECT)GetWindowLong(hWnd,
        GWL_RADICALRECT);

    CreateCaret(hWnd, NULL, 2, lpImeLinkRadical->lTextSize.cy +
        CARET_MARGIN * 2);

    if (lpImeLinkRegWord->nCurrIME < lpImeLinkRadical->nStartIME) {
        lpImeLinkRegWord->nCurrIME = lpImeLinkRadical->nStartIME;
    } else if ((lpImeLinkRegWord->nCurrIME - lpImeLinkRadical->nStartIME) >=
        lpImeLinkRadical->nPerPageIMEs) {
        lpImeLinkRegWord->nCurrIME = lpImeLinkRadical->nStartIME +
            lpImeLinkRadical->nPerPageIMEs - 1;
    } else {
    }

    i = lpImeLinkRegWord->nCurrIME - lpImeLinkRadical->nStartIME;

    i = (i * RECT_NUMBER) + RECT_RADICAL;

    SetCaretPos(lpImeLinkRadical->rcRadical[i].left +
        lpImeLinkRadical->lCurrReadingExtent.cx + 2,
        lpImeLinkRadical->rcRadical[i].top + UI_MARGIN - CARET_MARGIN);

    ShowCaret(hWnd);

    return;
}

/************************************************************/
/*  RegWordPaint                                            */
/************************************************************/
void RegWordPaint(
    HWND hWnd)
{
    LPIMERADICALRECT lpImeLinkRadical;
    LPIMELINKREGWORD lpImeLinkRegWord;
    LPREGWORDSTRUCT  lpRegWordStructTmp;
    HDC              hDC;
    PAINTSTRUCT      ps;
    UINT             i;
    UINT             nShowIMEs;

    lpImeLinkRadical = (LPIMERADICALRECT)GetWindowLong(hWnd,
        GWL_RADICALRECT);

    lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
        GWL_IMELINKREGWORD);

    lpRegWordStructTmp = &lpImeLinkRegWord->sRegWordStruct[
        lpImeLinkRadical->nStartIME];

    HideCaret(hWnd);

    hDC = BeginPaint(hWnd, &ps);

    // we only can show up to the final one
    nShowIMEs = lpImeLinkRegWord->nEudcIMEs - lpImeLinkRadical->nStartIME;

    if (nShowIMEs > lpImeLinkRadical->nPerPageIMEs) {
        // we only can show one page a time
        nShowIMEs = lpImeLinkRadical->nPerPageIMEs;
    }

    for (i = 0; i < nShowIMEs; i++) {
        RECT rcSunken;
        UINT j, k;

        k = i * RECT_NUMBER + RECT_RADICAL;

        rcSunken = lpImeLinkRadical->rcRadical[k];

        rcSunken.left -= 2;
        rcSunken.top -= 2;
        rcSunken.right += 2;
        rcSunken.bottom += 2;

        DrawEdge(hDC, &rcSunken, BDR_SUNKENOUTER, BF_RECT);

        SetBkColor(hDC, GetSysColor(COLOR_BTNFACE));

        if (lpRegWordStructTmp->bUpdate == UPDATE_ERROR) {
            // red text for error
            SetTextColor(hDC, RGB(0xFF, 0x00, 0x00));
        } else if (lpRegWordStructTmp->bUpdate == UPDATE_START) {
            // yellow text for not finished
            SetTextColor(hDC, RGB(0xFF, 0xFF, 0x00));
        } else if (lpRegWordStructTmp->bUpdate == UPDATE_REGISTERED) {
            // green text for registered
            SetTextColor(hDC, RGB(0x00, 0x80, 0x00));
        } else {
            SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        }

        j = i * RECT_NUMBER + RECT_IMENAME;

        ExtTextOut(hDC, lpImeLinkRadical->rcRadical[j].left,
            lpImeLinkRadical->rcRadical[j].top,
            ETO_OPAQUE|ETO_CLIPPED, &lpImeLinkRadical->rcRadical[j],
            lpRegWordStructTmp->szIMEName,
            lpRegWordStructTmp->uIMENameLen, NULL);

        if ((lpImeLinkRegWord->nCurrIME - lpImeLinkRadical->nStartIME) == i) {
            SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
            SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

            GetTextExtentPoint(hDC, lpRegWordStructTmp->szReading,
                lpRegWordStructTmp->dwReadingLen,
                &lpImeLinkRadical->lCurrReadingExtent);

            SetCaretPos(lpImeLinkRadical->rcRadical[k].left +
                lpImeLinkRadical->lCurrReadingExtent.cx + 2,
                lpImeLinkRadical->rcRadical[k].top + UI_MARGIN - CARET_MARGIN);
        } else {
            SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        }

        ExtTextOut(hDC, lpImeLinkRadical->rcRadical[k].left,
            lpImeLinkRadical->rcRadical[k].top + UI_MARGIN,
            ETO_OPAQUE, &lpImeLinkRadical->rcRadical[k],
            lpRegWordStructTmp->szReading,
            lpRegWordStructTmp->dwReadingLen, NULL);

        lpRegWordStructTmp++;
    }

    EndPaint(hWnd, &ps);

    ShowCaret(hWnd);

    return;
}

/************************************************************/
/*  RegWordWndProc                                          */
/************************************************************/
LRESULT CALLBACK RegWordWndProc(
    HWND   hWnd,
    UINT   uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (uMsg) {
    case WM_CREATE:
        {
            LPIMELINKREGWORD lpImeLinkRegWord;
            UINT             uIndex;

            // initialize to 0
            SetWindowLong(hWnd, GWL_IMELINKREGWORD, 0L);
            SetWindowLong(hWnd, GWL_RADICALRECT, 0L);

            lpImeLinkRegWord = RegWordCreate(hWnd);

            if (!lpImeLinkRegWord) {
                return (-1);
            }

            lpImeLinkRegWord->fCompMsg = TRUE;
            lpImeLinkRegWord->nCurrIME = 0xFFFFFFFF;

            lpImeLinkRegWord->hRegWordIMC = ImmCreateContext();

            if (!lpImeLinkRegWord->hRegWordIMC) {
                return (-1);
            }

            lpImeLinkRegWord->hOldIMC = ImmAssociateContext(hWnd,
                lpImeLinkRegWord->hRegWordIMC);

            SetWindowLong(hWnd, GWL_IMELINKREGWORD, (LONG)lpImeLinkRegWord);

            uIndex = 0;
            SwitchToThisIME(hWnd, 0);

            // the switch will fail, if the window is disable, try again
            PostMessage(hWnd, WM_EUDC_SWITCHIME, 0, uIndex);
        }
        break;
    case WM_EUDC_COMPMSG:
        {
            LPIMELINKREGWORD lpImeLinkRegWord;

            lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
                GWL_IMELINKREGWORD);

            lpImeLinkRegWord->fCompMsg = (BOOL)lParam;
        }
        break;
    case WM_EUDC_SWITCHIME:
        {
            LPIMELINKREGWORD lpImeLinkRegWord;

            lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
                GWL_IMELINKREGWORD);

            lpImeLinkRegWord->nCurrIME = 0xFFFFFFFF;

            SwitchToThisIME(hWnd, (UINT)lParam);
        }
        break;
    case WM_IME_STARTCOMPOSITION:
    case WM_IME_ENDCOMPOSITION:
        break;
    case WM_IME_COMPOSITION:
        {
            LPIMELINKREGWORD lpImeLinkRegWord;

            lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
                GWL_IMELINKREGWORD);

            if (lpImeLinkRegWord->fCompMsg) {
                WmImeComposition(hWnd, lParam);
            }
        }
        break;
    case WM_IME_NOTIFY:
        switch (wParam) {
        case IMN_OPENSTATUSWINDOW:
        case IMN_CLOSESTATUSWINDOW:
        case IMN_OPENCANDIDATE:
        case IMN_CHANGECANDIDATE:
        case IMN_CLOSECANDIDATE:
            break;
        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
        }
        break;
    case WM_IME_SETCONTEXT:
        return DefWindowProc(hWnd, uMsg, wParam, lParam & ~(ISC_SHOWUIALL));
    case WM_EUDC_REGISTER_BUTTON:
        {
            LPIMERADICALRECT lpImeRadicalRect;

            lpImeRadicalRect = (LPIMERADICALRECT)GetWindowLong(hWnd,
                GWL_RADICALRECT);

            lpImeRadicalRect->hRegWordButton = (HWND)lParam;
        }
        break;
    case WM_EUDC_CODE:
        EudcCode(hWnd, (UINT)lParam);
        break;
    case WM_LBUTTONDOWN:
        ChangeToOtherIME(hWnd, lParam);
        break;
    case WM_VSCROLL:
        ScrollIME(hWnd, wParam);
        break;
    case WM_KEYDOWN:
        ScrollIMEByKey(hWnd, wParam);
        break;
    case WM_SETFOCUS:
        RegWordGetFocus(hWnd);
        break;
    case WM_KILLFOCUS:
        DestroyCaret();
        break;
    case WM_PAINT:
        RegWordPaint(hWnd);
        break;
    case WM_DESTROY:
        {
            LPIMERADICALRECT lpImeRadicalRect;
            LPIMELINKREGWORD lpImeLinkRegWord;

            lpImeRadicalRect = (LPIMERADICALRECT)GetWindowLong(hWnd,
                GWL_RADICALRECT);

            if (lpImeRadicalRect) {
                GlobalFree((HGLOBAL)lpImeRadicalRect);
            }

            lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
                GWL_IMELINKREGWORD);

            if (!lpImeLinkRegWord) {
                break;
            }

            ImmAssociateContext(hWnd, lpImeLinkRegWord->hOldIMC);

            ImmDestroyContext(lpImeLinkRegWord->hRegWordIMC);

            GlobalFree((HGLOBAL)lpImeLinkRegWord);
        }
        break;
    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    return (0L);
}

/************************************************************/
/*  RegisterThisEudc                                        */
/************************************************************/
int RegisterThisEudc(
    HWND hWnd)
{
    LPIMELINKREGWORD lpImeLinkRegWord;
    LPREGWORDSTRUCT  lpRegWordStructTmp;
    UINT             i;
    int              iRet;

    lpImeLinkRegWord = (LPIMELINKREGWORD)GetWindowLong(hWnd,
        GWL_IMELINKREGWORD);

    lpRegWordStructTmp = &lpImeLinkRegWord->sRegWordStruct[0];

    iRet = -1;

    for (i = 0; i < lpImeLinkRegWord->nEudcIMEs; i++, lpRegWordStructTmp++) {
        if (lpRegWordStructTmp->bUpdate == UPDATE_NONE) {
        } else if (lpRegWordStructTmp->bUpdate != UPDATE_FINISH) {
            TCHAR szStrBuf[128];
            int   iYesNo;

            if (iRet != -1) {
                continue;
            }

            LoadString(hAppInst, IDS_QUERY_NOTFINISH, szStrBuf,
                sizeof(szStrBuf) / sizeof(TCHAR));

            iYesNo = MessageBox(hWnd, szStrBuf,
                lpRegWordStructTmp->szIMEName,
                MB_APPLMODAL|MB_YESNO|MB_DEFBUTTON1);

            if (iYesNo == IDYES) {
                iRet = i;
            }
        } else {
            BOOL  fRet;
            TCHAR szStrBuf[128];
            int   iYesNo;

            fRet = ImmRegisterWord(lpRegWordStructTmp->hKL,
                lpRegWordStructTmp->szReading, IME_REGWORD_STYLE_EUDC,
                lpImeLinkRegWord->szEudcCodeString);

            if (fRet) {
                lpRegWordStructTmp->bUpdate = UPDATE_REGISTERED;
                continue;
            } else {
                lpRegWordStructTmp->bUpdate = UPDATE_ERROR;
            }

            if (iRet != -1) {
                continue;
            }

            LoadString(hAppInst, IDS_QUERY_REGISTER, szStrBuf,
                sizeof(szStrBuf) / sizeof(TCHAR));

            iYesNo = MessageBox(hWnd, szStrBuf,
                lpRegWordStructTmp->szIMEName,
                MB_APPLMODAL|MB_YESNO|MB_DEFBUTTON1);

            if (iYesNo == IDYES) {
                iRet = i;
            }
        }
    }

    InvalidateRect(hWnd, NULL, FALSE);

    return (iRet);
}

/************************************************************/
/*  ImeLinkDlgProc                                          */
/************************************************************/
BOOL CALLBACK ImeLinkDlgProc(
    HWND   hDlg,
    UINT   uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (uMsg) {
    case WM_INITDIALOG:
        {
            HWND  hRadicalWnd, hRegWordButton;
            int   cbString;
            TCHAR szTitle[128];

            cbString = GetWindowText(hDlg, szTitle, sizeof(szTitle) /
                sizeof(TCHAR));

            wsprintf(&szTitle[cbString], "%4X", (UINT)lParam);

            SetWindowText(hDlg, szTitle);

            hRadicalWnd = GetDlgItem(hDlg, IDD_RADICAL);

            SendMessage(hRadicalWnd, WM_EUDC_CODE, 0, lParam);

            hRegWordButton = GetDlgItem(hDlg, IDOK);

            EnableWindow(hRegWordButton, FALSE);

            SendMessage(hRadicalWnd, WM_EUDC_REGISTER_BUTTON, 0,
                (LPARAM)hRegWordButton);
        }
        return (TRUE);      // do not want to set focus to special control
    case WM_COMMAND:
        switch (wParam) {
        case IDOK:
            {
                HWND  hRadicalWnd;

                hRadicalWnd = GetDlgItem(hDlg, IDD_RADICAL);

                if (RegisterThisEudc(hRadicalWnd) == -1) {
                    EndDialog(hDlg, TRUE);
                } else {
                    SetFocus(hRadicalWnd);
                }
            }

            break;
        case IDCANCEL:
            EndDialog(hDlg, FALSE);
            break;
        default:
            return (FALSE);
        }
        return (TRUE);
    case WM_IME_NOTIFY:
        // we need to hook these messages from frame window also
        // otherwise sometime the OPENSTATUS will send to the frame
        // window and the child - hRadicalWnd will not get these messages
        switch (wParam) {
        case IMN_OPENSTATUSWINDOW:
        case IMN_CLOSESTATUSWINDOW:
            return (TRUE);
        default:
            return (FALSE);
        }
    default:
        return (FALSE);
    }

    return (TRUE);
}

/************************************************************/
/*  ImeLink                                                 */
/************************************************************/
void ImeLink(
    HWND hWnd,
    UINT uCode)
{
    static BOOL bFirstTime = TRUE;

    UINT       nLayouts;
    HKL FAR   *lphKL;
    TCHAR      szTitle[32];
    TCHAR      szMessage[256];
    UINT       i, nIMEs;
    WNDCLASSEX wcClass;
    HKL        hOldKL;

    nLayouts = GetKeyboardLayoutList(0, NULL);

    lphKL = GlobalAlloc(GPTR, sizeof(HKL) * nLayouts);

    if (!lphKL) {
        LoadString(hAppInst, IDS_NOMEM_TITLE, szTitle, sizeof(szTitle));
        LoadString(hAppInst, IDS_NOMEM_MSG, szMessage, sizeof(szMessage));

        MessageBox(hWnd, szMessage, szTitle, MB_OK);
        return;
    }

    // MSVC may have problem for recusive modal dialog box,
    // I mean create a modal dialog box within a modal dialog box.

    // so we need to move the code from RegWordCreate() to here to
    // prevent creating a modal dialog form a modal dialog.
    // The ImmConfigureIME API call is possible to create a modal dialog.

    // get all keyboard layouts, it will include all IMEs
    GetKeyboardLayoutList(nLayouts, lphKL);

    for (i = 0, nIMEs = 0; i < nLayouts; i++) {
        BOOL  fRet;
        HKL   hKL;
        TCHAR szImeEudcDic[80];

        hKL = *(lphKL + i);

        fRet = ImmIsIME(hKL);

        if (!fRet) {            // this is not an IME
            continue;
        }

        szImeEudcDic[0] = '\0';

        fRet = ImmEscape(hKL, (HIMC)NULL, IME_ESC_GET_EUDC_DICTIONARY,
            szImeEudcDic);

        if (!fRet) {
            continue;
        }

        if (szImeEudcDic[0]) {
            fRet = TRUE;
        } else if (!bFirstTime) {
        } else {
            fRet = ImmConfigureIME(hKL, hWnd, IME_CONFIG_SELECTDICTIONARY, NULL);
        }

        if (!fRet) {
            // this IME do not have an IME EUDC dictionary
            continue;
        }

        if (szImeEudcDic[0] == '\0') {
            // check whether we really get a dictionary
            fRet = ImmEscape(hKL, (HIMC)NULL, IME_ESC_GET_EUDC_DICTIONARY,
                szImeEudcDic);

            if (!fRet) {
                continue;
            } else if (szImeEudcDic[0] == '\0') {
                continue;
            } else {
            }
        } else {
        }

        nIMEs++;
    }

    GlobalFree((HGLOBAL)lphKL);

    if (bFirstTime) {
        bFirstTime = FALSE;
    }

    if (!nIMEs) {
        LoadString(hAppInst, IDS_NOIME_TITLE, szTitle, sizeof(szTitle));
        LoadString(hAppInst, IDS_NOIME_MSG, szMessage, sizeof(szMessage));

        MessageBox(hWnd, szMessage, szTitle, MB_OK);
        return;
    }

    if (!GetClassInfoEx(hAppInst, szRegWordCls, &wcClass)) {
        wcClass.cbSize = sizeof(WNDCLASSEX);
        wcClass.style = CS_HREDRAW|CS_VREDRAW;
        wcClass.lpfnWndProc = RegWordWndProc;
        wcClass.cbClsExtra = 0;
        wcClass.cbWndExtra = 8;
        wcClass.hInstance = hAppInst;
        wcClass.hIcon = NULL;
        wcClass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
        wcClass.lpszMenuName = NULL;
        wcClass.lpszClassName = szRegWordCls;
        wcClass.hIconSm = NULL;
        RegisterClassEx(&wcClass);
    }

    hOldKL = GetKeyboardLayout(0);

    DialogBoxParam(hAppInst, szImeLinkDlg, hWnd, ImeLinkDlgProc,
        (LPARAM)uCode);

    ActivateKeyboardLayout(hOldKL, 0);

    return;
}

/************************************************************/
/*  MatchImeName()                                          */
/************************************************************/
HKL MatchImeName(
    LPCTSTR szStr,
    UINT    uLen)
{
    TCHAR     szImeName[16];
    int       nLayout;
    HKL       hKL;
    HGLOBAL   hMem;
    HKL FAR * lpMem;
    int       i;

    nLayout = GetKeyboardLayoutList(0, NULL);

    // alloc temp buffer
    hMem = GlobalAlloc(GHND, sizeof(HKL) * nLayout);

    if (!hMem) {
        return (NULL);
    }

    lpMem = (HKL FAR *)GlobalLock(hMem);

    if (!lpMem) {
        GlobalFree(hMem);
        return (NULL);
    }

    // get all keyboard layouts, it includes all IMEs
    GetKeyboardLayoutList(nLayout, lpMem);

    for (i = 0; i < nLayout; i++) {
        UINT j;
        BOOL fRet;

        hKL = *(lpMem + i);

        for (j = 0; j < uLen; j++) {
            szImeName[j] = '\0';
        }

        fRet = ImmEscape(hKL, (HIMC)NULL, IME_ESC_IME_NAME, szImeName);

        if (!fRet) {                // this hKL can not ask name
            continue;
        }

        fRet = TRUE;

        for (j = 0; j < uLen; j++) {
            if (szStr[j] != szImeName[j]) {
                fRet = FALSE;
                break;
            }
        }

        if (fRet) {
            goto MatchOvr;
        }
    }

    hKL = NULL;

MatchOvr:
    GlobalUnlock(hMem);
    GlobalFree(hMem);

    return (hKL);
}

/************************************************************/
/*  RegisterBig5Table()                                     */
/************************************************************/
HKL RegisterBig5Table(
    HWND          hWnd,
    LPUSRDICIMHDR lpIsvUsrDic,
    DWORD         dwFileSize)
{
    HKL    hKL;
    HDC    hDC;
    SIZE   lTextSize;
    RECT   rcProcess;
    DWORD  i;
    LPBYTE lpCurr, lpEnd;
    BOOL   fRet;
    TCHAR  szStr[16];
    TCHAR  szProcessFmt[32];
    TCHAR  szResult[2][32];
    TCHAR  szProcessInfo[48];
    WORD   wInternalCode[256];
    WORD   wAltInternalCode[256];

    for (i = 0; i < sizeof(lpIsvUsrDic->achMethodName); i++) {
        szStr[i] = lpIsvUsrDic->achMethodName[i];
    }

    hKL = MatchImeName(szStr, sizeof(lpIsvUsrDic->achMethodName) /
        sizeof(TCHAR));

    if (!hKL) {
        return (hKL);
    }

    LoadString(hAppInst, IDS_PROCESS_FMT, szProcessFmt, sizeof(szProcessFmt));
    LoadString(hAppInst, IDS_RESULT_FAIL, szResult[0], sizeof(szResult[0]));
    LoadString(hAppInst, IDS_RESULT_SUCCESS, szResult[1], sizeof(szResult[1]));

    LoadString(hAppInst, IDS_CHINESE_CHAR, szStr, sizeof(szStr));

    hDC = GetDC(NULL);

    GetTextExtentPoint(hDC, szStr, sizeof(WORD)/sizeof(TCHAR),
        &lTextSize);

    ReleaseDC(NULL, hDC);

    // show the processing in somewhere, don't need to be same as this
    rcProcess.left = 1;
    rcProcess.top  = 1;
    rcProcess.right = rcProcess.left + lTextSize.cx *
        sizeof(szProcessInfo) / sizeof(WORD);
    rcProcess.bottom = rcProcess.top + lTextSize.cy;

    // convert sequence code to internal code
    for (i = 0; i < sizeof(wInternalCode) / sizeof(WORD); i++) {
        LRESULT lRet;

        lRet = ImmEscape(hKL, (HIMC)NULL,
            IME_ESC_SEQUENCE_TO_INTERNAL, &i);

        if (HIWORD(lRet) == 0xFFFF) {
            // This is caused by sign extent in Win9x in the return value of
            // ImmEscape, it causes an invalid internal code.
            wAltInternalCode[i] = 0;
        } else {
            wAltInternalCode[i] = HIWORD(lRet);
        }

        wInternalCode[i] = LOWORD(lRet);

#ifndef UNICODE
        if (wAltInternalCode[i] > 0xFF) {
            // convert to multi byte string
            wAltInternalCode[i] = LOBYTE(wAltInternalCode[i]) << 8 |
                HIBYTE(wAltInternalCode[i]);
        }

        if (wInternalCode[i] > 0xFF) {
            // convert to multi byte string
            wInternalCode[i] = LOBYTE(wInternalCode[i]) << 8 |
                HIBYTE(wInternalCode[i]);
        }
#endif
    }

    // check for each record and register it
    // get to the first record and skip the Bank ID
    lpCurr = (LPBYTE)(lpIsvUsrDic + 1) + sizeof(WORD);
    lpEnd = (LPBYTE)lpIsvUsrDic + dwFileSize;

    for (; lpCurr < lpEnd;
        // internal code + sequence code + Bank ID of next record
        lpCurr += sizeof(WORD) + lpIsvUsrDic->cMethodKeySize + sizeof(WORD)) {
        int j;

        // quick way to init \0 for the register string
        *(LPDWORD)szStr = 0;

#ifdef UNICODE
        szStr[0] = *(LPTSTR)lpCurr;
#else
        szStr[1] = *lpCurr;
        szStr[0] = *(lpCurr + 1);
#endif

        for (i = 0, j = 0; i < lpIsvUsrDic->cMethodKeySize; i++) {
            if (!wAltInternalCode[*(LPBYTE)(lpCurr + sizeof(WORD) + i)]) {
            } else if (wAltInternalCode[*(LPBYTE)(lpCurr + sizeof(WORD) + i)] < 0xFF) {
                *(LPTSTR)&szStr[4 + j] = (TCHAR)
                    wAltInternalCode[*(LPBYTE)(lpCurr + sizeof(WORD) + i)];
                j += sizeof(TCHAR) / sizeof(TCHAR);
            } else {
                *(LPWSTR)&szStr[4 + j] = (WCHAR)
                    wAltInternalCode[*(LPBYTE)(lpCurr + sizeof(WORD) + i)];
                j += sizeof(WCHAR) / sizeof(TCHAR);
            }

            if (wInternalCode[*(LPBYTE)(lpCurr + sizeof(WORD) + i)] < 0xFF) {
                *(LPTSTR)&szStr[4 + j] = (TCHAR)
                    wInternalCode[*(LPBYTE)(lpCurr + sizeof(WORD) + i)];
                j += sizeof(TCHAR) / sizeof(TCHAR);
            } else {
                *(LPWSTR)&szStr[4 + j] = (WCHAR)
                    wInternalCode[*(LPBYTE)(lpCurr + sizeof(WORD) + i)];
                j += sizeof(WCHAR) / sizeof(TCHAR);
            }
        }

        szStr[4 + j] = szStr[4 + j + 1] = szStr[4 + j + 2] = '\0';

        fRet = ImmRegisterWord(hKL, &szStr[4], IME_REGWORD_STYLE_EUDC,
            szStr);

        wsprintf(szProcessInfo, szProcessFmt, (LPTSTR)szStr,
            (LPTSTR)&szStr[4], szResult[fRet]);

        hDC = GetDC(hWnd);

        // show the process information
        ExtTextOut(hDC, rcProcess.left, rcProcess.top, ETO_OPAQUE,
            &rcProcess, szProcessInfo, lstrlen(szProcessInfo),
            NULL);

        ReleaseDC(NULL, hDC);

        if (!fRet) {
            // wait 3 seconds for fail case
            Sleep(3000);
        }
    }

    return (hKL);
}

/************************************************************/
/*  BatchImeLink()                                          */
/************************************************************/
void BatchImeLink(
    HWND hWnd)
{
    HANDLE        hIsvUsrDicFile, hIsvUsrDic;
    LPUSRDICIMHDR lpIsvUsrDic;
    TCHAR         chReplace;
    int           i, cbString;
    DWORD         dwSize, dwFileSize;
    LPTSTR        szTitle, szMessage;
    int           iTitle, iMessage;

    OPENFILENAME  ofn;
    TCHAR         szFilter[64];
    TCHAR         szFileName[MAX_PATH];
    TCHAR         szDirName[MAX_PATH];

    // try to share the buffer
    szTitle = szFilter;
    iTitle = sizeof(szFilter);
    szMessage = szDirName;
    iMessage = sizeof(szDirName);

    // internal error, the data structure need byte alignment
    // it should not use WORD or DWORD alignment

    if (sizeof(USRDICIMHDR) != 256) {
        LoadString(hAppInst, IDS_INTERNAL_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_INTERNAL_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle, MB_OK);
        return;
    }

    // do we need to set a new file name
    cbString = LoadString(hAppInst, IDS_ISV_FILE_FILTER, szFilter,
        sizeof(szFilter) / sizeof(TCHAR));
    chReplace = szFilter[cbString - 1];

    for (i = 0; szFilter[i]; i++) {
        if (szFilter[i] == chReplace) {
            szFilter[i] = '\0';
        }
    }

    GetWindowsDirectory(szDirName, sizeof(szDirName) / sizeof(TCHAR));
    lstrcpy(szFileName, TEXT("*.TBL"));

    // prompt a open file dialog
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hWnd;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = sizeof(szFileName);
    ofn.lpstrInitialDir = szDirName;
    ofn.lpstrTitle = NULL;;
    ofn.Flags = OFN_NOCHANGEDIR|OFN_HIDEREADONLY|OFN_CREATEPROMPT|
        OFN_PATHMUSTEXIST;
    ofn.lpstrDefExt = NULL;

    if (!GetOpenFileName(&ofn)) {
        return;
    }

    hIsvUsrDicFile = CreateFile(ofn.lpstrFile, GENERIC_READ, 0, NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hIsvUsrDicFile == INVALID_HANDLE_VALUE) {
        LoadString(hAppInst, IDS_NOTOPEN_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_NOTOPEN_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle,
            MB_OK|MB_ICONHAND|MB_TASKMODAL|MB_TOPMOST);
        return;
    }

    for (i = 0; i < sizeof(szFileName); i++) {
        if (szFileName[i] == '\\') {
            szFileName[i] = ' ';
        }
    }

    hIsvUsrDic = CreateFileMapping((HANDLE)hIsvUsrDicFile, NULL,
        PAGE_READONLY, 0, 0, szFileName);

    if (!hIsvUsrDic) {
        LoadString(hAppInst, IDS_NOTOPEN_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_NOTOPEN_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle,
            MB_OK|MB_ICONHAND|MB_TASKMODAL|MB_TOPMOST);
        goto BatchCloseUsrDicFile;
    }

    lpIsvUsrDic = MapViewOfFile(hIsvUsrDic, FILE_MAP_READ, 0, 0, 0);

    if (!lpIsvUsrDic) {
        LoadString(hAppInst, IDS_NOTOPEN_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_NOTOPEN_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle,
            MB_OK|MB_ICONHAND|MB_TASKMODAL|MB_TOPMOST);
        goto BatchCloseUsrDic;
    }

    dwSize = lpIsvUsrDic->ulTableCount * (sizeof(WORD) + sizeof(WORD) +
        lpIsvUsrDic->cMethodKeySize) + 256;

    dwFileSize = GetFileSize(hIsvUsrDicFile, (LPDWORD)NULL);
#if 0       // temp code
    dwSize = dwFileSize;
#endif

    if (dwSize != dwFileSize) {
        LoadString(hAppInst, IDS_FILESIZE_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_FILESIZE_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle, MB_OK);
    } else if (lpIsvUsrDic->uHeaderSize != 256) {
        LoadString(hAppInst, IDS_HEADERSIZE_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_HEADERSIZE_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle, MB_OK);
    } else if (lpIsvUsrDic->uInfoSize != 13) {
        LoadString(hAppInst, IDS_INFOSIZE_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_INFOSIZE_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle, MB_OK);
    } else if (lpIsvUsrDic->idCP != BIG5_CP &&
        lpIsvUsrDic->idCP != ALT_BIG5_CP) {
        LoadString(hAppInst, IDS_CODEPAGE_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_CODEPAGE_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle, MB_OK);
    } else if (*(LPUNADWORD)lpIsvUsrDic->idUserCharInfoSign != SIGN_CWIN) {
        // != CWIN
        LoadString(hAppInst, IDS_CWINSIGN_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_CWINSIGN_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle, MB_OK);
    } else if (*(LPUNADWORD)((LPBYTE)lpIsvUsrDic->idUserCharInfoSign +
        sizeof(DWORD)) != SIGN__TBL) {
        // != _TBL
        LoadString(hAppInst, IDS_CWINSIGN_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_CWINSIGN_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle, MB_OK);
    } else if (!RegisterBig5Table(hWnd, lpIsvUsrDic, dwFileSize)) {
        LoadString(hAppInst, IDS_UNMATCHED_TITLE, szTitle, iTitle);
        LoadString(hAppInst, IDS_UNMATCHED_MSG, szMessage, iMessage);

        MessageBox(hWnd, szMessage, szTitle, MB_OK);
    } else {
        // OK
    }

    UnmapViewOfFile(lpIsvUsrDic);

BatchCloseUsrDic:
    CloseHandle(hIsvUsrDic);

BatchCloseUsrDicFile:
    CloseHandle(hIsvUsrDicFile);

    return;
}

/************************************************************/
/*  WndProc()                                               */
/************************************************************/
LRESULT CALLBACK WndProc(       // this is the window procedure of
                                // EUDC editor
    HWND   hWnd,
    UINT   uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
#ifdef UNICODE
    static UINT uCode = 0xE000;
#else
    static UINT uCode = 0xFA40;
#endif

    switch (uMsg) {
    case WM_COMMAND:
        switch (wParam) {
        case IDM_NEW_EUDC:
            uCode++;
            break;
        case IDM_IME_LINK:
            ImeLink(hWnd, uCode);
            break;
        case IDM_BATCH_IME_LINK:
            BatchImeLink(hWnd);
            break;
        default:
            break;
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        return (0L);
    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    return (0L);
}

/************************************************************/
/*  WinMain()                                               */
/************************************************************/
int WINAPI WinMain(
    HINSTANCE hInst,
    HINSTANCE hPrevInst,
    LPSTR     lpszCmdLine,
    int       nCmdShow)
{
    WNDCLASS wcClass;
    HWND     hWnd;
    MSG      sMsg;

    hAppInst = hInst;

    wcClass.style = CS_HREDRAW|CS_VREDRAW;
    wcClass.lpfnWndProc = WndProc;
    wcClass.cbClsExtra = 0;
    wcClass.cbWndExtra = 0;
    wcClass.hInstance = hAppInst;
    wcClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wcClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcClass.hbrBackground = GetStockObject(LTGRAY_BRUSH);
    wcClass.lpszMenuName = szMenuName;
    wcClass.lpszClassName = szAppName;
    RegisterClass(&wcClass);

    hWnd = CreateWindowEx(WS_EX_WINDOWEDGE,
        szAppName,
        TEXT("Fake EUDC Editor"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hAppInst, NULL);

    ShowWindow(hWnd, SW_SHOWDEFAULT);
    UpdateWindow(hWnd);

    if (!hWnd) {
        return (0);
    }

    while (GetMessage(&sMsg, NULL, 0, 0)) {
        TranslateMessage(&sMsg);
        DispatchMessage(&sMsg);
    }

    return (sMsg.wParam);
}
