//--------------------------------------------------------------------------------------
// File: ImeUi.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#include "dxut.h"
#include "ImeUi.h"
#include <math.h>
#include <msctf.h>
#include <malloc.h>
#include <strsafe.h>

// Ignore typecast warnings
#pragma warning( disable : 4312 )
#pragma warning( disable : 4244 )
#pragma warning( disable : 4311 )


#define MAX_CANDIDATE_LENGTH 256
#define COUNTOF(a) ( sizeof( a ) / sizeof( ( a )[0] ) )
#define POSITION_UNINITIALIZED	((DWORD)-1)

#define LANG_CHT	MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL)
#define LANG_CHS	MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED)

#define MAKEIMEVERSION(major,minor) ( (DWORD)( ( (BYTE)( major ) << 24 ) | ( (BYTE)( minor ) << 16 ) ) )
#define IMEID_VER(dwId)		( ( dwId ) & 0xffff0000 )
#define IMEID_LANG(dwId)	( ( dwId ) & 0x0000ffff )

#define _CHT_HKL_DAYI				( (HKL)0xE0060404 )	// DaYi
#define _CHT_HKL_NEW_PHONETIC		( (HKL)0xE0080404 )	// New Phonetic
#define _CHT_HKL_NEW_CHANG_JIE		( (HKL)0xE0090404 )	// New Chang Jie
#define _CHT_HKL_NEW_QUICK			( (HKL)0xE00A0404 )	// New Quick
#define _CHT_HKL_HK_CANTONESE		( (HKL)0xE00B0404 )	// Hong Kong Cantonese
#define _CHT_IMEFILENAME	"TINTLGNT.IME"	// New Phonetic
#define _CHT_IMEFILENAME2	"CINTLGNT.IME"	// New Chang Jie
#define _CHT_IMEFILENAME3	"MSTCIPHA.IME"	// Phonetic 5.1
#define IMEID_CHT_VER42 ( LANG_CHT | MAKEIMEVERSION( 4, 2 ) )	// New(Phonetic/ChanJie)IME98  : 4.2.x.x // Win98
#define IMEID_CHT_VER43 ( LANG_CHT | MAKEIMEVERSION( 4, 3 ) )	// New(Phonetic/ChanJie)IME98a : 4.3.x.x // Win2k
#define IMEID_CHT_VER44 ( LANG_CHT | MAKEIMEVERSION( 4, 4 ) )	// New ChanJie IME98b          : 4.4.x.x // WinXP
#define IMEID_CHT_VER50 ( LANG_CHT | MAKEIMEVERSION( 5, 0 ) )	// New(Phonetic/ChanJie)IME5.0 : 5.0.x.x // WinME
#define IMEID_CHT_VER51 ( LANG_CHT | MAKEIMEVERSION( 5, 1 ) )	// New(Phonetic/ChanJie)IME5.1 : 5.1.x.x // IME2002(w/OfficeXP)
#define IMEID_CHT_VER52 ( LANG_CHT | MAKEIMEVERSION( 5, 2 ) )	// New(Phonetic/ChanJie)IME5.2 : 5.2.x.x // IME2002a(w/WinXP)
#define IMEID_CHT_VER60 ( LANG_CHT | MAKEIMEVERSION( 6, 0 ) )	// New(Phonetic/ChanJie)IME6.0 : 6.0.x.x // New IME 6.0(web download)
#define IMEID_CHT_VER_VISTA ( LANG_CHT | MAKEIMEVERSION( 7, 0 ) )	// All TSF TIP under Cicero UI-less mode: a hack to make GetImeId() return non-zero value

#define _CHS_HKL		( (HKL)0xE00E0804 )	// MSPY
#define _CHS_IMEFILENAME	"PINTLGNT.IME"	// MSPY1.5/2/3
#define _CHS_IMEFILENAME2	"MSSCIPYA.IME"	// MSPY3 for OfficeXP
#define IMEID_CHS_VER41	( LANG_CHS | MAKEIMEVERSION( 4, 1 ) )	// MSPY1.5	// SCIME97 or MSPY1.5 (w/Win98, Office97)
#define IMEID_CHS_VER42	( LANG_CHS | MAKEIMEVERSION( 4, 2 ) )	// MSPY2	// Win2k/WinME
#define IMEID_CHS_VER53	( LANG_CHS | MAKEIMEVERSION( 5, 3 ) )	// MSPY3	// WinXP

static CHAR signature[] = "%%%IMEUILIB:070111%%%";

static IMEUI_APPEARANCE         gSkinIME =
{
    0,			// symbolColor;
    0x404040,   // symbolColorOff;
    0xff000000,	// symbolColorText;
    24,			// symbolHeight;
    0xa0,		// symbolTranslucence;
    0,			// symbolPlacement;
    NULL,		// symbolFont;
    0xffffffff,	// candColorBase;
    0xff000000,	// candColorBorder;
    0,			// candColorText;
    0x00ffff00,	// compColorInput;
    0x000000ff,	// compColorTargetConv;
    0x0000ff00,	// compColorConverted;
    0x00ff0000,	// compColorTargetNotConv;
    0x00ff0000,	// compColorInputErr;
    0x80,		// compTranslucence;
    0,			// compColorText;
    2,			// caretWidth;
    1,			// caretYMargin;
};

struct _SkinCompStr
{
    DWORD colorInput;
    DWORD colorTargetConv;
    DWORD colorConverted;
    DWORD colorTargetNotConv;
    DWORD colorInputErr;
};

_SkinCompStr                    gSkinCompStr;

// Definition from Win98DDK version of IMM.H
typedef struct
tagINPUTCONTEXT2
{
    HWND hWnd;
    BOOL fOpen;
    POINT ptStatusWndPos;
    POINT ptSoftKbdPos;
    DWORD fdwConversion;
    DWORD fdwSentence;
    union
    {
        LOGFONTA A;
        LOGFONTW W;
    } lfFont;
    COMPOSITIONFORM cfCompForm;
    CANDIDATEFORM cfCandForm[4];
    HIMCC hCompStr;
    HIMCC hCandInfo;
    HIMCC hGuideLine;
    HIMCC hPrivate;
    DWORD dwNumMsgBuf;
    HIMCC hMsgBuf;
    DWORD fdwInit;
    DWORD dwReserve[3];
}
INPUTCONTEXT2, *PINPUTCONTEXT2, NEAR *NPINPUTCONTEXT2,
FAR*                            LPINPUTCONTEXT2;


// Class to disable Cicero in case ImmDisableTextFrameService() doesn't disable it completely
class CDisableCicero
{
public:
            CDisableCicero() : m_ptim( NULL ),
                               m_bComInit( false )
            {
            }
            ~CDisableCicero()
            {
                Uninitialize();
            }
    void    Initialize()
    {
        if( m_bComInit )
        {
            return;
        }
        HRESULT hr;
        hr = CoInitializeEx( NULL, COINIT_APARTMENTTHREADED );
        if( SUCCEEDED( hr ) )
        {
            m_bComInit = true;
            hr = CoCreateInstance( CLSID_TF_ThreadMgr,
                                   NULL,
                                   CLSCTX_INPROC_SERVER,
                                   __uuidof( ITfThreadMgr ),
                                   ( void** )&m_ptim );
        }
    }
    void    Uninitialize()
    {
        if( m_ptim )
        {
            m_ptim->Release();
            m_ptim = NULL;
        }
        if( m_bComInit )
            CoUninitialize();
        m_bComInit = false;
    }

    void    DisableCiceroOnThisWnd( HWND hwnd )
    {
        if( m_ptim == NULL )
            return;
        ITfDocumentMgr* pdimPrev; // the dim that is associated previously.
        // Associate NULL dim to the window.
        // When this window gets the focus, Cicero does not work and IMM32 IME
        // will be activated.
        if( SUCCEEDED( m_ptim->AssociateFocus( hwnd, NULL, &pdimPrev ) ) )
        {
            if( pdimPrev )
                pdimPrev->Release();
        }
    }
private:
    ITfThreadMgr* m_ptim;
    bool m_bComInit;
};
static CDisableCicero           g_disableCicero;

#define _IsLeadByte(x) ( LeadByteTable[(BYTE)( x )] )
static void _PumpMessage();
static BYTE LeadByteTable[256];
#define _ImmGetContext	ImmGetContext
#define _ImmReleaseContext	ImmReleaseContext
#define _ImmAssociateContext	ImmAssociateContext
static LONG ( WINAPI* _ImmGetCompositionString )( HIMC himc, DWORD dwIndex, LPVOID lpBuf, DWORD dwBufLen );
#define _ImmGetOpenStatus	ImmGetOpenStatus
#define _ImmSetOpenStatus	ImmSetOpenStatus
#define _ImmGetConversionStatus	ImmGetConversionStatus
static DWORD ( WINAPI* _ImmGetCandidateList )( HIMC himc, DWORD deIndex, LPCANDIDATELIST lpCandList, DWORD dwBufLen );
static LPINPUTCONTEXT2 ( WINAPI* _ImmLockIMC )( HIMC hIMC );
static BOOL ( WINAPI* _ImmUnlockIMC )( HIMC hIMC );
static LPVOID ( WINAPI* _ImmLockIMCC )( HIMCC hIMCC );
static BOOL ( WINAPI* _ImmUnlockIMCC )( HIMCC hIMCC );
#define _ImmGetDefaultIMEWnd	ImmGetDefaultIMEWnd
#define _ImmGetIMEFileNameA	ImmGetIMEFileNameA
#define _ImmGetVirtualKey	ImmGetVirtualKey
#define _ImmNotifyIME	ImmNotifyIME
#define _ImmSetConversionStatus	ImmSetConversionStatus
#define _ImmSimulateHotKey	ImmSimulateHotKey
#define _ImmIsIME	ImmIsIME

// private API provided by CHT IME. Available on version 6.0 or later.
UINT ( WINAPI*_GetReadingString )( HIMC himc, UINT uReadingBufLen, LPWSTR lpwReadingBuf, PINT pnErrorIndex,
                                   BOOL* pfIsVertical, PUINT puMaxReadingLen );
BOOL ( WINAPI*_ShowReadingWindow )( HIMC himc, BOOL bShow );

// Callbacks
void ( CALLBACK*ImeUiCallback_DrawRect )( int x1, int y1, int x2, int y2, DWORD color );
void ( CALLBACK*ImeUiCallback_DrawFans )( const IMEUI_VERTEX* paVertex, UINT uNum );
void*                           ( __cdecl*ImeUiCallback_Malloc )( size_t bytes );
void ( __cdecl*ImeUiCallback_Free )( void* ptr );
void ( CALLBACK*ImeUiCallback_OnChar )( WCHAR wc );

static void (*_SendCompString )();
static LRESULT ( WINAPI* _SendMessage )( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) = SendMessageA;
static DWORD (* _GetCandidateList )( HIMC himc, DWORD dwIndex, LPCANDIDATELIST* ppCandList );

static HWND                     g_hwndMain;
static HWND                     g_hwndCurr;
static HIMC                     g_himcOrg;
static bool                     g_bImeEnabled = false;
static TCHAR g_szCompositionString[256];
static BYTE g_szCompAttrString[256];
static DWORD                    g_IMECursorBytes = 0;
static DWORD                    g_IMECursorChars = 0;
static TCHAR g_szCandidate[MAX_CANDLIST][MAX_CANDIDATE_LENGTH];
static DWORD                    g_dwSelection, g_dwCount;
static UINT                     g_uCandPageSize;
static DWORD                    g_bDisableImeCompletely = false;
static DWORD                    g_dwIMELevel;
static DWORD                    g_dwIMELevelSaved;
static TCHAR g_szMultiLineCompString[ 256 *( 3 - sizeof( TCHAR ) ) ];
static bool                     g_bReadingWindow = false;
static bool                     g_bHorizontalReading = false;
static bool                     g_bVerticalCand = true;
static UINT                     g_uCaretBlinkTime = 0;
static UINT                     g_uCaretBlinkLast = 0;
static bool                     g_bCaretDraw = false;
static bool                     g_bChineseIME;
static bool                     g_bInsertMode = true;
static TCHAR g_szReadingString[32];	// Used only in case of horizontal reading window
static int                      g_iReadingError;	// Used only in case of horizontal reading window
static UINT                     g_screenWidth, g_screenHeight;
static DWORD                    g_dwPrevFloat;
static bool                     bIsSendingKeyMessage = false;
static OSVERSIONINFOA           g_osi;
static bool                     g_bInitialized = false;
static bool                     g_bCandList = false;
static DWORD                    g_dwCandX, g_dwCandY;
static DWORD                    g_dwCaretX, g_dwCaretY;
static DWORD                    g_hCompChar;
static int                      g_iCandListIndexBase;
static DWORD                    g_dwImeUiFlags = IMEUI_FLAG_SUPPORT_CARET;
static bool                     g_bUILessMode = false;
static HMODULE                  g_hImmDll = NULL;

#define IsNT() (g_osi.dwPlatformId == VER_PLATFORM_WIN32_NT)

struct CompStringAttribute
{
    UINT caretX;
    UINT caretY;
    CImeUiFont_Base* pFont;
    DWORD colorComp;
    DWORD colorCand;
    RECT margins;
};

static CompStringAttribute      g_CaretInfo;
static DWORD                    g_dwState = IMEUI_STATE_OFF;
static DWORD                    swirl = 0;
static double                   lastSwirl;

#define INDICATOR_NON_IME	0
#define INDICATOR_CHS		1
#define INDICATOR_CHT		2
#define INDICATOR_KOREAN	3
#define INDICATOR_JAPANESE	4

#define GETLANG()		LOWORD(g_hklCurrent)
#define GETPRIMLANG()	((WORD)PRIMARYLANGID(GETLANG()))
#define GETSUBLANG()	SUBLANGID(GETLANG())

#define LANG_CHS MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED)
#define LANG_CHT MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL)

static HKL                      g_hklCurrent = 0;
static UINT                     g_uCodePage = 0;
static LPTSTR g_aszIndicator[] =
{
    TEXT( "A" ),
#ifdef UNICODE
		L"\x7B80",
		L"\x7E41",
		L"\xac00",
		L"\x3042",
#else
    "\xd6\xd0",
    "\xa4\xa4",
    "\xb0\xa1",
    "\x82\xa0",
#endif
};
static LPTSTR                   g_pszIndicatior = g_aszIndicator[0];

static void GetReadingString( HWND hWnd );
static DWORD GetImeId( UINT uIndex = 0 );
static void CheckToggleState();
static void DrawImeIndicator();
static void DrawCandidateList();
static void DrawCompositionString( bool bDrawCompAttr );
static void GetReadingWindowOrientation( DWORD dwId );
static void OnInputLangChangeWorker();
static void OnInputLangChange();
static void SetImeApi();
static void CheckInputLocale();
static void SetSupportLevel( DWORD dwImeLevel );
void ImeUi_SetSupportLevel( DWORD dwImeLevel );


//
//	local helper functions
//
inline LRESULT SendKeyMsg( HWND hwnd, UINT msg, WPARAM wp )
{
    bIsSendingKeyMessage = true;
    LRESULT lRc = _SendMessage( hwnd, msg, wp, 1 );
    bIsSendingKeyMessage = false;
    return lRc;
}
#define SendKeyMsg_DOWN(hwnd,vk)	SendKeyMsg(hwnd, WM_KEYDOWN, vk)
#define SendKeyMsg_UP(hwnd,vk)		SendKeyMsg(hwnd, WM_KEYUP, vk)

///////////////////////////////////////////////////////////////////////////////
//
//  CTsfUiLessMode
//      Handles IME events using Text Service Framework (TSF). Before Vista,
//      IMM (Input Method Manager) API has been used to handle IME events and
//      inqueries. Some IMM functions lose backward compatibility due to design
//      of TSF, so we have to use new TSF interfaces.
//
///////////////////////////////////////////////////////////////////////////////
class CTsfUiLessMode
{
protected:
    // Sink receives event notifications
    class CUIElementSink : public ITfUIElementSink,
                           public ITfInputProcessorProfileActivationSink,
                           public ITfCompartmentEventSink
    {
    public:
                        CUIElementSink();
                        ~CUIElementSink();

        // IUnknown
        STDMETHODIMP    QueryInterface( REFIID riid, void** ppvObj );
		STDMETHODIMP_( ULONG )
                        AddRef( void );
		STDMETHODIMP_( ULONG )
                        Release( void );

        // ITfUIElementSink
        //   Notifications for Reading Window events. We could process candidate as well, but we'll use IMM for simplicity sake.
        STDMETHODIMP    BeginUIElement( DWORD dwUIElementId, BOOL* pbShow );
        STDMETHODIMP    UpdateUIElement( DWORD dwUIElementId );
        STDMETHODIMP    EndUIElement( DWORD dwUIElementId );

        // ITfInputProcessorProfileActivationSink
        //   Notification for keyboard input locale change
        STDMETHODIMP    OnActivated( DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid,
                                     REFGUID guidProfile, HKL hkl, DWORD dwFlags );

        // ITfCompartmentEventSink
        //    Notification for open mode (toggle state) change
        STDMETHODIMP    OnChange( REFGUID rguid );

    private:
        LONG _cRef;
    };

    static void MakeReadingInformationString( ITfReadingInformationUIElement* preading );
    static void MakeCandidateStrings( ITfCandidateListUIElement* pcandidate );
    static ITfUIElement* GetUIElement( DWORD dwUIElementId );
    static BOOL GetCompartments( ITfCompartmentMgr** ppcm, ITfCompartment** ppTfOpenMode,
                                 ITfCompartment** ppTfConvMode );
    static BOOL SetupCompartmentSinks( BOOL bResetOnly = FALSE, ITfCompartment* pTfOpenMode = NULL,
                                       ITfCompartment* ppTfConvMode = NULL );

    static ITfThreadMgrEx* m_tm;
    static DWORD m_dwUIElementSinkCookie;
    static DWORD m_dwAlpnSinkCookie;
    static DWORD m_dwOpenModeSinkCookie;
    static DWORD m_dwConvModeSinkCookie;
    static CUIElementSink* m_TsfSink;
    static int m_nCandidateRefCount;	// Some IME shows multiple candidate lists but the Library doesn't support multiple candidate list. 
    // So track open / close events to make sure the candidate list opened last is shown.
                CTsfUiLessMode()
                {
                }	// this class can't be instanciated

public:
    static BOOL SetupSinks();
    static void ReleaseSinks();
    static BOOL CurrentInputLocaleIsIme();
    static void UpdateImeState( BOOL bResetCompartmentEventSink = FALSE );
    static void EnableUiUpdates( bool bEnable );
};

ITfThreadMgrEx*                 CTsfUiLessMode::m_tm;
DWORD                           CTsfUiLessMode::m_dwUIElementSinkCookie = TF_INVALID_COOKIE;
DWORD                           CTsfUiLessMode::m_dwAlpnSinkCookie = TF_INVALID_COOKIE;
DWORD                           CTsfUiLessMode::m_dwOpenModeSinkCookie = TF_INVALID_COOKIE;
DWORD                           CTsfUiLessMode::m_dwConvModeSinkCookie = TF_INVALID_COOKIE;
CTsfUiLessMode::CUIElementSink* CTsfUiLessMode::m_TsfSink = NULL;
int                             CTsfUiLessMode::m_nCandidateRefCount = NULL;

static unsigned long _strtoul( LPCSTR psz, LPTSTR*, int )
{
    if( !psz )
        return 0;

    ULONG ulRet = 0;
    if( psz[0] == '0' && ( psz[1] == 'x' || psz[1] == 'X' ) )
    {
        psz += 2;
        ULONG ul = 0;
        while( *psz )
        {
            if( '0' <= *psz && *psz <= '9' )
                ul = *psz - '0';
            else if( 'A' <= *psz && *psz <= 'F' )
                ul = *psz - 'A' + 10;
            else if( 'a' <= *psz && *psz <= 'f' )
                ul = *psz - 'a' + 10;
            else
                break;
            ulRet = ulRet * 16 + ul;
            psz++;
        }
    }
    else
    {
        while( *psz && ( '0' <= *psz && *psz <= '9' ) )
        {
            ulRet = ulRet * 10 + ( *psz - '0' );
            psz++;
        }
    }
    return ulRet;
}

#ifdef UNICODE
#define GetCharCount(psz) lstrlen(psz)
#define GetCharCountFromBytes(psz,iBytes) (iBytes)
static void AW_SendCompString()
{
	int i, iLen;
	if ( ImeUiCallback_OnChar )
	{
		for ( i = 0; g_szCompositionString[i]; i++ )
		{
			ImeUiCallback_OnChar( g_szCompositionString[i] );
		}
		return;
	}

	BYTE szCompStr[COUNTOF(g_szCompositionString) * 2];
	iLen = WideCharToMultiByte(g_uCodePage, 0, g_szCompositionString, -1,
				(LPSTR)szCompStr, COUNTOF(szCompStr), NULL, NULL) - 1; // don't need to send NUL terminator;
	for (i = 0; i < iLen; i++)
	{
		SendKeyMsg(g_hwndCurr, WM_CHAR, szCompStr[i]);
	}
}

// The following AW_Imm* functions are there to support Win95/98 first version.
// They can be deleted if the game doesn't supports them (i.e. games requires Win98 SE or later).
static DWORD AW_GetCandidateList(HIMC himc, DWORD dwIndex, LPCANDIDATELIST* ppCandList)
{
	DWORD dwBufLen = ImmGetCandidateListA( himc, dwIndex, NULL, 0 );
	if (dwBufLen)
	{
		LPCANDIDATELIST pCandList = (LPCANDIDATELIST)ImeUiCallback_Malloc(dwBufLen);
		if (pCandList) {
			dwBufLen = ImmGetCandidateListA( himc, dwIndex, pCandList, dwBufLen );
			if (dwBufLen) {
				int i;
				int wideBufLen = 0;
				for (i = 0; i < (int)pCandList->dwCount; i++) {
					wideBufLen += MultiByteToWideChar(g_uCodePage, 0, (LPSTR)pCandList + pCandList->dwOffset[i], -1, NULL, 0) * sizeof(WCHAR);
				}
				wideBufLen += pCandList->dwOffset[0];
				*ppCandList = (LPCANDIDATELIST)ImeUiCallback_Malloc(wideBufLen);
				LPCANDIDATELIST pCandListW = *ppCandList;
				memcpy(pCandListW, pCandList, pCandList->dwOffset[0]);
				LPWSTR pwz = (LPWSTR)((LPSTR)pCandListW + pCandList->dwOffset[0]);
				for (i = 0; i < (int)pCandList->dwCount; i++) {
					pCandListW->dwOffset[i] = (LPSTR)pwz - (LPSTR)pCandListW;
					pwz += MultiByteToWideChar(g_uCodePage, 0, (LPSTR)pCandList + pCandList->dwOffset[i], -1, pwz, 256);
				}
				dwBufLen = wideBufLen;
			}
			ImeUiCallback_Free(pCandList);
		}
	}
	return dwBufLen;
}

static LONG WINAPI AW_ImmGetCompositionString(HIMC himc, DWORD dwIndex, LPVOID lpBuf, DWORD dwBufLen)
{
	char pszMb[COUNTOF(g_szCompositionString) * 2];
	DWORD dwRet = ImmGetCompositionStringA(himc, dwIndex, pszMb, sizeof(pszMb));
	switch (dwIndex) {
	case GCS_RESULTSTR:
	case GCS_COMPSTR:
		if (dwRet) {
			pszMb[dwRet] = 0;
			dwRet = (DWORD)MultiByteToWideChar(g_uCodePage, 0, pszMb, -1, (LPWSTR)lpBuf, dwBufLen);
			if (dwRet) {
				// Note that ImmGetCompositionString() returns number of bytes copied, regardless of the width of character.
				dwRet = (dwRet - 1) * sizeof(WCHAR);
			}
		}
		break;
	case GCS_CURSORPOS:
		dwRet /= 2;
		break;
	case GCS_COMPATTR: {
		char pszMb2[COUNTOF(g_szCompositionString) * 2];
		DWORD dwRet2 = ImmGetCompositionStringA(himc, GCS_COMPSTR, pszMb2, sizeof(pszMb2));
		if (!dwRet2) {
			dwRet2 = ImmGetCompositionStringA(himc, GCS_RESULTSTR, pszMb2, sizeof(pszMb2));
			if (!dwRet2) {
				return 0;
			}
		}
		char* pOut = (char*)lpBuf;
		for (DWORD i = 0; i < dwRet; i++) {
			*pOut++ = pszMb[i];	// copy attribute
			if (_IsLeadByte(pszMb2[i]))
				i++;
		}
		dwRet = pOut - (char*)lpBuf;
		}
		break;
	}
	return dwRet;
}

#else	// !UNICODE
// returns number of characters from number of bytes
static int GetCharCountFromBytes( LPCSTR pszString, int iBytes )
{
    int iCount = 0;
    int i;
    for( i = 0; pszString[i] && i < iBytes; i++ )
    {
        iCount++;
        if( _IsLeadByte(pszString[i]) )
            i++;
    }
    if( i != iBytes )
        iCount = -iCount;	// indicate error - iBytes specifies wrong boundary (i.e. the last byte is leadbyte)
    return iCount;
}

static int GetCharCount( LPTSTR psz )
{
    int i = 0;
    while( *psz )
    {
        if( _IsLeadByte(*psz) )
        {
            psz++;
        }
        psz++;
        i++;
    }
    return i;
}

static DWORD WA_GetCandidateList( HIMC himc, DWORD dwIndex, LPCANDIDATELIST* ppCandList )
{
    DWORD dwBufLen = ImmGetCandidateListW( himc, dwIndex, NULL, 0 );
    if( dwBufLen )
    {
        LPCANDIDATELIST pCandList = ( LPCANDIDATELIST )ImeUiCallback_Malloc( dwBufLen );
        if( pCandList )
        {
            dwBufLen = ImmGetCandidateListW( himc, dwIndex, pCandList, dwBufLen );
            if( dwBufLen )
            {
                int i;
                int mbBufLen = 0;
                for( i = 0; i < ( int )pCandList->dwCount; i++ )
                {
                    mbBufLen += WideCharToMultiByte( g_uCodePage, 0, ( LPWSTR )( ( LPSTR )pCandList +
                                                                                 pCandList->dwOffset[i] ), -1, NULL, 0,
                                                     NULL, NULL );
                }
                mbBufLen += pCandList->dwOffset[0];
                *ppCandList = ( LPCANDIDATELIST )ImeUiCallback_Malloc( mbBufLen );
                LPCANDIDATELIST pCandListA = *ppCandList;
                memcpy( pCandListA, pCandList, pCandList->dwOffset[0] );
                LPSTR psz = ( LPSTR )pCandListA + pCandList->dwOffset[0];
                for( i = 0; i < ( int )pCandList->dwCount; i++ )
                {
                    pCandListA->dwOffset[i] = ( LPSTR )psz - ( LPSTR )pCandListA;
                    psz += WideCharToMultiByte( g_uCodePage, 0, ( LPWSTR )( ( LPSTR )pCandList +
                                                                            pCandList->dwOffset[i] ), -1, psz, 256,
                                                NULL, NULL );
                }
                dwBufLen = mbBufLen;
            }
            ImeUiCallback_Free( pCandList );
        }
    }
    return dwBufLen;
}

static LONG WINAPI WA_ImmGetCompositionString( HIMC himc, DWORD dwIndex, LPVOID lpBuf, DWORD dwBufLen )
{
    WCHAR pwzUc[COUNTOF(g_szCompositionString)];
    DWORD dwRet = ImmGetCompositionStringW( himc, dwIndex, pwzUc, sizeof( pwzUc ) );
    switch( dwIndex )
    {
        case GCS_RESULTSTR:
        case GCS_COMPSTR:
            if( dwRet )
            {
                pwzUc[dwRet / sizeof( WCHAR )] = 0;
                dwRet = ( DWORD )WideCharToMultiByte( g_uCodePage, 0, pwzUc, -1, ( LPSTR )lpBuf, dwBufLen, NULL,
                                                      NULL );
                if( dwRet )
                {
                    dwRet = dwRet - 1;
                }
            }
            break;

        case GCS_CURSORPOS:
        {
            WCHAR pwzUc2[COUNTOF(g_szCompositionString)];
            DWORD dwRet2 = ImmGetCompositionStringW( himc, GCS_COMPSTR, pwzUc2, sizeof( pwzUc2 ) );
            if( !dwRet2 )
            {
                dwRet2 = ImmGetCompositionStringW( himc, GCS_RESULTSTR, pwzUc2, sizeof( pwzUc2 ) );
                if( !dwRet2 )
                {
                    return 0;
                }
            }
            dwRet2 /= 2;
            //The return value of WideCharToMultiByte() should probably be checked/asserted for success.
            //bounds violation (overflow) 'pszMb[iRc]'
            const int bufSize = COUNTOF(g_szCompositionString) * 2;
            char pszMb[bufSize];
            int iRc = WideCharToMultiByte( g_uCodePage, 0, pwzUc2, dwRet2, pszMb, sizeof( pszMb ), NULL, NULL );
            assert( iRc > 0 ); //WideCharToMultiByte returns 0 if it failed, and it should *never* be negative in the first place
            if( iRc >= bufSize ) //if we wrote more bytes than the length of the buffer, we need to terminate it
            {
                pszMb[ bufSize - 1] = 0; //0 terminate the end of the buffer
            }
            else
            {
                pszMb[ iRc ] = 0;
            }
            char* psz = pszMb;
            for( dwRet2 = 0; dwRet2 != dwRet; dwRet2++ )
            {
                if( _IsLeadByte( *psz ) )
                    psz++;
                psz++;
            }
            dwRet = psz - pszMb;
        }
            break;

        case GCS_COMPATTR:
        {
            WCHAR pwzUc2[COUNTOF(g_szCompositionString)];
            DWORD dwRet2 = ImmGetCompositionStringW( himc, GCS_COMPSTR, pwzUc2, sizeof( pwzUc2 ) );
            if( !dwRet2 )
            {
                dwRet2 = ImmGetCompositionStringW( himc, GCS_RESULTSTR, pwzUc2, sizeof( pwzUc2 ) );
                if( !dwRet2 )
                {
                    return 0;
                }
            }
            dwRet2 /= 2;
            const int bufSize = COUNTOF(g_szCompositionString) * 2;
            char pszMb[bufSize];
            int iRc = WideCharToMultiByte( g_uCodePage, 0, pwzUc2, dwRet2, pszMb, sizeof( pszMb ), NULL, NULL );
            assert( iRc > 0 ); //WideCharToMultiByte returns 0 if it failed, and it should *never* be negative in the first place
            if( iRc >= bufSize ) //if we wrote more bytes than the length of the buffer, we need to terminate it
            {
                pszMb[ bufSize - 1] = 0; //0 terminate the end of the buffer
            }
            else
            {
                pszMb[ iRc ] = 0;
            }
            char* pSrc = ( char* )pwzUc;
            char* pOut = ( char* )lpBuf;
            for( char* psz = pszMb; *psz; psz++, pSrc++ )
            {
                *pOut++ = *pSrc;	// copy attribute
                if( _IsLeadByte( *psz ) )
                {
                    *pOut++ = *pSrc;
                    psz++;
                }
                // buffer overrun protection, pOut is incremented in the loop, but not part	of the 
                // loop	invariant test.	To make	the	code more readable we have a test rather than
                // rolling this	into the for stmt.
                if( ( DWORD )( pOut - ( char* )lpBuf ) >= dwBufLen )
                    break;
            }
            dwRet = pOut - ( char* )lpBuf;
        }
            break;
    }
    return dwRet;
}

#endif	// UNICODE

static void ComposeCandidateLine( int index, LPCTSTR pszCandidate )
{
    LPTSTR psz = g_szCandidate[index];
    *psz++ = ( TCHAR )( TEXT( '0' ) + ( ( index + g_iCandListIndexBase ) % 10 ) );
    if( g_bVerticalCand )
    {
        *psz++ = TEXT( ' ' );
    }
    while( *pszCandidate && ( COUNTOF(g_szCandidate[index]) > ( psz - g_szCandidate[index] ) ) )
    {
        *psz++ = *pszCandidate++;
    }
    *psz = 0;
}

static void SendCompString()
{
    int i, iLen = lstrlen( g_szCompositionString );
    if( ImeUiCallback_OnChar )
    {
        LPCWSTR pwz;
#ifdef UNICODE
		pwz = g_szCompositionString;
#else
        WCHAR szUnicode[COUNTOF( g_szCompositionString ) ];
        pwz = szUnicode;
        iLen = MultiByteToWideChar( g_uCodePage, 0, g_szCompositionString, -1, szUnicode, COUNTOF(szUnicode) ) - 1;
#endif
        for( i = 0; i < iLen; i++ )
        {
            ImeUiCallback_OnChar( pwz[i] );
        }
        return;
    }
    for( i = 0; i < iLen; i++ )
    {
        SendKeyMsg( g_hwndCurr, WM_CHAR,
#ifdef UNICODE
			(WPARAM)g_szCompositionString[i]
#else
                    ( WPARAM )( BYTE )g_szCompositionString[i]
#endif
                    );
    }
}

static DWORD GetCandidateList( HIMC himc, DWORD dwIndex, LPCANDIDATELIST* ppCandList )
{
    DWORD dwBufLen = _ImmGetCandidateList( himc, dwIndex, NULL, 0 );
    if( dwBufLen )
    {
        *ppCandList = ( LPCANDIDATELIST )ImeUiCallback_Malloc( dwBufLen );
        dwBufLen = _ImmGetCandidateList( himc, dwIndex, *ppCandList, dwBufLen );
    }
    return dwBufLen;
}

static void SendControlKeys( UINT vk, UINT num )
{
    if( num == 0 )
        return;
    for( UINT i = 0; i < num; i++ )
    {
        SendKeyMsg_DOWN(g_hwndCurr, vk);
    }
    SendKeyMsg_UP(g_hwndCurr, vk);
}

// send key messages to erase composition string.
static void CancelCompString( HWND hwnd, bool bUseBackSpace = true, int iNewStrLen = 0 )
{
    if( g_dwIMELevel != 3 )
        return;
    int cc = GetCharCount( g_szCompositionString );
    int i;
    // move caret to the end of composition string
    SendControlKeys( VK_RIGHT, cc - g_IMECursorChars );

    if( bUseBackSpace || g_bInsertMode )
        iNewStrLen = 0;

    // The caller sets bUseBackSpace to false if there's possibility of sending 
    // new composition string to the app right after this function call.
    // 
    // If the app is in overwriting mode and new comp string is 
    // shorter than current one, delete previous comp string 
    // till it's same long as the new one. Then move caret to the beginning of comp string.
    // New comp string will overwrite old one.
    if( iNewStrLen < cc )
    {
        for( i = 0; i < cc - iNewStrLen; i++ )
        {
            SendKeyMsg_DOWN(hwnd, VK_BACK);
            SendKeyMsg( hwnd, WM_CHAR, 8 );	//Backspace character
        }
        SendKeyMsg_UP(hwnd, VK_BACK);
    }
    else
        iNewStrLen = cc;

    SendControlKeys( VK_LEFT, iNewStrLen );
}

// initialize composition string data.
static void InitCompStringData( void )
{
    g_IMECursorBytes = 0;
    g_IMECursorChars = 0;
    memset( &g_szCompositionString, 0, sizeof( g_szCompositionString ) );
    memset( &g_szCompAttrString, 0, sizeof( g_szCompAttrString ) );
}

static void DrawCaret( DWORD x, DWORD y, DWORD height )
{
    if( g_bCaretDraw && ImeUiCallback_DrawRect )
        ImeUiCallback_DrawRect( x, y + gSkinIME.caretYMargin, x + gSkinIME.caretWidth,
                                y + height - gSkinIME.caretYMargin, g_CaretInfo.colorComp );
}

//
// Apps that draw the composition string on top of composition string attribute
// in level 3 support should call this function twice in rendering a frame.
//     // Draw edit box UI;
//     ImeUi_RenderUI(true, false);	// paint composition string attribute;
//     // Draw text in the edit box;
//     ImeUi_RenderUi(false, true); // paint the rest of IME UI;
//
void ImeUi_RenderUI( bool bDrawCompAttr, bool bDrawOtherUi )
{
    if( !g_bInitialized || !g_bImeEnabled || !g_CaretInfo.pFont )
        return;
    if( !bDrawCompAttr && !bDrawOtherUi )
        return;	// error case
    if( g_dwIMELevel == 2 )
    {
        if( !bDrawOtherUi )
            return;	// 1st call for level 3 support
    }

    if( bDrawOtherUi )
        DrawImeIndicator();

    DrawCompositionString( bDrawCompAttr );

    if( bDrawOtherUi )
        DrawCandidateList();
}

static void DrawImeIndicator()
{
    bool bOn = g_dwState != IMEUI_STATE_OFF;

    IMEUI_VERTEX PieData[17];
    float SizeOfPie = ( float )gSkinIME.symbolHeight;

    memset( PieData, 0, sizeof( PieData ) );

    switch( gSkinIME.symbolPlacement )
    {
        case 0: // vertical centering IME indicator
        {
            if( SizeOfPie + g_CaretInfo.margins.right + 4 > g_screenWidth )
            {
                PieData[0].sx = ( -SizeOfPie / 2 ) + g_CaretInfo.margins.left - 4;
                PieData[0].sy = ( float )g_CaretInfo.margins.top + ( g_CaretInfo.margins.bottom -
                                                                     g_CaretInfo.margins.top ) / 2;
            }
            else
            {
                PieData[0].sx = -( SizeOfPie / 2 ) + g_CaretInfo.margins.right + gSkinIME.symbolHeight + 4;
                PieData[0].sy = ( float )g_CaretInfo.margins.top + ( g_CaretInfo.margins.bottom -
                                                                     g_CaretInfo.margins.top ) / 2;
            }
            break;
        }
        case 1: // upperleft
            PieData[0].sx = 4 + ( SizeOfPie / 2 );
            PieData[0].sy = 4 + ( SizeOfPie / 2 );
            break;
        case 2: // upperright
            PieData[0].sx = g_screenWidth - ( 4 + ( SizeOfPie / 2 ) );
            PieData[0].sy = 4 + ( SizeOfPie / 2 );
            break;
        case 3: // lowerright
            PieData[0].sx = g_screenWidth - ( 4 + ( SizeOfPie / 2 ) );
            PieData[0].sy = g_screenHeight - ( 4 + ( SizeOfPie / 2 ) );
            break;
        case 4: // lowerleft
            PieData[0].sx = 4 + ( SizeOfPie / 2 );
            PieData[0].sy = g_screenHeight - ( 4 + ( SizeOfPie / 2 ) );
            break;
    }
    PieData[0].rhw = 1.0f;
    if( bOn )
    {
        if( GetTickCount() - lastSwirl > 250 )
        {
            swirl++;
            lastSwirl = GetTickCount();
            if( swirl > 13 )
                swirl = 0;
        }
    }
    else
        swirl = 0;
    for( int t1 = 1; t1 < 16; t1++ )
    {
        float radian = 2.0f * 3.1415926f * ( t1 - 1 + ( bOn * swirl ) ) / 14.0f;
        PieData[t1].sx = ( float )( PieData[0].sx + SizeOfPie / 2 * cos( radian ) );
        PieData[t1].sy = ( float )( PieData[0].sy + SizeOfPie / 2 * sin( radian ) );
        PieData[t1].rhw = 1.0f;
    }

    PieData[0].color = 0xffffff + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
    if( !gSkinIME.symbolColor && bOn )
    {
        {
            PieData[1].color = 0xff0000 + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[2].color = 0xff3000 + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[3].color = 0xff6000 + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[4].color = 0xff9000 + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[5].color = 0xffC000 + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[6].color = 0xffff00 + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[7].color = 0xC0ff00 + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[8].color = 0x90ff00 + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[9].color = 0x60ff00 + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[10].color = 0x30c0ff + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[11].color = 0x00a0ff + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[12].color = 0x3090ff + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[13].color = 0x6060ff + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[14].color = 0x9030ff + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
            PieData[15].color = 0xc000ff + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
        }
    }
    else
    {
        DWORD dwColor = bOn ? gSkinIME.symbolColor : gSkinIME.symbolColorOff;
        for( int t1 = 1; t1 < 16; t1++ )
        {
            PieData[t1].color = dwColor + ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 );
        }
    }
    PieData[16] = PieData[1];

    if( ImeUiCallback_DrawFans )
        ImeUiCallback_DrawFans( PieData, 17 );

    float fHeight = gSkinIME.symbolHeight * 0.625f;

    // fix for Ent Gen #120 - reduce the height of character when Korean IME is on
    if( GETPRIMLANG() == LANG_KOREAN && bOn )
    {
        fHeight *= 0.8f;
    }

    if( gSkinIME.symbolFont )
    {
#ifdef DS2
		// save the font height here since DS2 shares editbox font and indicator font
		DWORD _w, _h;
		g_CaretInfo.pFont->GetTextExtent( TEXT(" "), &_w, &_h );
#endif //DS2

        // GOS deals height in points that is 1/72nd inch and assumes display device is 96dpi.
        fHeight = fHeight * 96 / 72;
        gSkinIME.symbolFont->SetHeight( ( UINT )fHeight );
        gSkinIME.symbolFont->SetColor( ( ( ( DWORD )gSkinIME.symbolTranslucence ) << 24 ) | gSkinIME.symbolColorText );

        //
        // draw the proper symbol over the fan
        //
        DWORD w, h;
        LPCTSTR cszSymbol = ( g_dwState == IMEUI_STATE_ON ) ? g_pszIndicatior : g_aszIndicator[0];

        gSkinIME.symbolFont->GetTextExtent( cszSymbol, &w, &h );
        gSkinIME.symbolFont->SetPosition( ( int )( PieData[0].sx ) - w / 2, ( int )( PieData[0].sy ) - h / 2 );
        gSkinIME.symbolFont->DrawText( cszSymbol );

#ifdef DS2
		// revert the height.
		g_CaretInfo.pFont->SetHeight( _h );
		
		// Double-check: Confirm match by testing a range of font heights to find best fit
		DWORD _h2;
		g_CaretInfo.pFont->GetTextExtent( TEXT(" "), &_w, &_h2 );
		if ( _h2 < _h )
		{
			for ( int i=1; _h2<_h && i<10; i++ )
			{
				g_CaretInfo.pFont->SetHeight( _h+i );
				g_CaretInfo.pFont->GetTextExtent( TEXT(" "), &_w, &_h2 );
			}
		}
		else if ( _h2 > _h )
		{
			for ( int i=1; _h2>_h && i<10; i++ )
			{
				g_CaretInfo.pFont->SetHeight( _h-i );
				g_CaretInfo.pFont->GetTextExtent( TEXT(" "), &_w, &_h2 );
			}
		}
#endif //DS2
    }
}

static void DrawCompositionString( bool bDrawCompAttr )
{
    // Process timer for caret blink
    UINT uCurrentTime = GetTickCount();
    if( uCurrentTime - g_uCaretBlinkLast > g_uCaretBlinkTime )
    {
        g_uCaretBlinkLast = uCurrentTime;
        g_bCaretDraw = !g_bCaretDraw;
    }

    int i = 0;

    g_CaretInfo.pFont->SetColor( g_CaretInfo.colorComp );

    DWORD uDummy;

    int len = lstrlen( g_szCompositionString );

    DWORD bgX = g_CaretInfo.caretX;
    DWORD bgY = g_CaretInfo.caretY;
    g_dwCaretX = POSITION_UNINITIALIZED;
    g_dwCaretY = POSITION_UNINITIALIZED;
    DWORD candX = POSITION_UNINITIALIZED;
    DWORD candY = 0;
    LPTSTR pszMlcs = g_szMultiLineCompString;

    DWORD wCompChar = 0;
    DWORD hCompChar = 0;
    g_CaretInfo.pFont->GetTextExtent( TEXT( " " ), &uDummy, &hCompChar );
    if( g_dwIMELevel == 3 && g_IMECursorBytes && g_szCompositionString[0] )
    {
        // shift starting point of drawing composition string according to the current caret position.
        TCHAR temp = g_szCompositionString[g_IMECursorBytes];
        g_szCompositionString[g_IMECursorBytes] = 0;
        g_CaretInfo.pFont->GetTextExtent( g_szCompositionString, &wCompChar, &hCompChar );
        g_szCompositionString[g_IMECursorBytes] = temp;
        bgX -= wCompChar;
    }

    //
    // Draw the background colors for IME text nuggets
    //
    bool saveCandPos = false;
    DWORD cType = 1;
    LPTSTR pszCurrentCompLine = g_szCompositionString;
    DWORD dwCompLineStart = bgX;
    DWORD bgXnext = bgX;

    if( GETPRIMLANG() != LANG_KOREAN || g_bCaretDraw )	// Korean uses composition attribute as blinking block caret
        for( i = 0; i < len; i += cType )
        {
            DWORD bgColor = 0x00000000;
            TCHAR szChar[3];
            szChar[0] = g_szCompositionString[i];
            szChar[1] = szChar[2] = 0;
#ifndef UNICODE
            cType = 1 + ( ( _IsLeadByte(g_szCompositionString[i]) ) ? 1 : 0 );
            if( cType == 2 && g_szCompositionString[i + 1] )	// in case we have 0 in trailbyte, we don't count it.
                szChar[1] = g_szCompositionString[i + 1];
#endif
            bgX = bgXnext;
            TCHAR cSave = g_szCompositionString[i + cType];
            g_szCompositionString[i + cType] = 0;
            g_CaretInfo.pFont->GetTextExtent( pszCurrentCompLine, &bgXnext, &hCompChar );
            g_szCompositionString[i + cType] = cSave;
            bgXnext += dwCompLineStart;
            wCompChar = bgXnext - bgX;

            switch( g_szCompAttrString[i] )
            {
                case ATTR_INPUT:
                    bgColor = gSkinCompStr.colorInput;
                    break;
                case ATTR_TARGET_CONVERTED:
                    bgColor = gSkinCompStr.colorTargetConv;
                    if( IMEID_LANG( GetImeId() ) != LANG_CHS )
                        saveCandPos = true;
                    break;						
                case ATTR_CONVERTED:
                    bgColor = gSkinCompStr.colorConverted;
                    break;
                case ATTR_TARGET_NOTCONVERTED:
                    //
                    // This is the one the user is working with currently
                    //
                    bgColor = gSkinCompStr.colorTargetNotConv;
                    break;
                case ATTR_INPUT_ERROR:
                    bgColor = gSkinCompStr.colorInputErr;
                    break;
                default:
                    // STOP( TEXT( "Attributes on IME characters are wrong" ) );
                    break;
            }

            if( g_dwIMELevel == 3 && bDrawCompAttr )
            {
                if( ( LONG )bgX >= g_CaretInfo.margins.left && ( LONG )bgX <= g_CaretInfo.margins.right )
                {
                    if( g_dwImeUiFlags & IMEUI_FLAG_SUPPORT_CARET )
                    {
                        if( ImeUiCallback_DrawRect )
                            ImeUiCallback_DrawRect( bgX, bgY, bgX + wCompChar, bgY + hCompChar, bgColor );
                    }
                    else
                    {
                        if( ImeUiCallback_DrawRect )
                            ImeUiCallback_DrawRect( bgX - wCompChar, bgY, bgX, bgY + hCompChar, bgColor );
                    }
                }
            }
            else if( g_dwIMELevel == 2 )
            {
                // make sure enough buffer space (possible space, NUL for current line, possible DBCS, 2 more NUL) 
                // are available in multiline composition string buffer
                bool bWrite = ( pszMlcs - g_szMultiLineCompString <
                                COUNTOF( g_szMultiLineCompString ) - 5 * ( 3 - sizeof( TCHAR ) ) );

                if( ( LONG )( bgX + wCompChar ) >= g_CaretInfo.margins.right )
                {
                    bgX = dwCompLineStart = bgXnext = g_CaretInfo.margins.left;
                    bgY = bgY + hCompChar;
                    pszCurrentCompLine = g_szCompositionString + i;
                    if( bWrite )
                    {
                        if( pszMlcs == g_szMultiLineCompString || pszMlcs[-1] == 0 )
                            *pszMlcs++ = ' ';	// to avoid zero length line
                        *pszMlcs++ = 0;
                    }
                }
                if( ImeUiCallback_DrawRect )
                    ImeUiCallback_DrawRect( bgX, bgY, bgX + wCompChar, bgY + hCompChar, bgColor );
                if( bWrite )
                {
                    *pszMlcs++ = g_szCompositionString[i];
#ifndef UNICODE
                    if( cType == 2 )
                        *pszMlcs++ = g_szCompositionString[i + 1];
#endif
                }
                if( ( DWORD )i == g_IMECursorBytes )
                {
                    g_dwCaretX = bgX;
                    g_dwCaretY = bgY;
                }
            }
            if( ( saveCandPos && candX == POSITION_UNINITIALIZED ) ||
                ( IMEID_LANG( GetImeId() ) == LANG_CHS && i / ( 3 - sizeof( TCHAR ) ) == ( int )g_IMECursorChars ) )
            {
                candX = bgX;
                candY = bgY;
            }
            saveCandPos = false;
        }

    bgX = bgXnext;
    if( g_dwIMELevel == 2 )
    {
        // in case the caret in composition string is at the end of it, draw it here
        if( len != 0 && ( DWORD )i == g_IMECursorBytes )
        {
            g_dwCaretX = bgX;
            g_dwCaretY = bgY;
        }

        // Draw composition string.
        //assert(pszMlcs - g_szMultiLineCompString <=
        //			sizeof(g_szMultiLineCompString) / sizeof(g_szMultiLineCompString[0]) - 2);
        *pszMlcs++ = 0;
        *pszMlcs++ = 0;
        DWORD x, y;
        x = g_CaretInfo.caretX;
        y = g_CaretInfo.caretY;
        pszMlcs = g_szMultiLineCompString;
        while( *pszMlcs &&
               pszMlcs - g_szMultiLineCompString < sizeof( g_szMultiLineCompString ) / sizeof
               ( g_szMultiLineCompString[0] ) )
        {
            g_CaretInfo.pFont->SetPosition( x, y );
            g_CaretInfo.pFont->DrawText( pszMlcs );
            pszMlcs += lstrlen( pszMlcs ) + 1;
            x = g_CaretInfo.margins.left;
            y += hCompChar;
        }
    }
    // for changing z-order of caret
    if( g_dwCaretX != POSITION_UNINITIALIZED && g_dwCaretY != POSITION_UNINITIALIZED )
    {
        DrawCaret( g_dwCaretX, g_dwCaretY, hCompChar );
    }
    g_dwCandX = candX;
    g_dwCandY = candY;
    g_hCompChar = hCompChar;
}

static void DrawCandidateList()
{
    DWORD candX = g_dwCandX;
    DWORD candY = g_dwCandY;
    DWORD hCompChar = g_hCompChar;
    int i;

    // draw candidate list / reading window
    if( !g_dwCount || g_szCandidate[0][0] == 0 )
    {
        return;
    }

    // If position of candidate list is not initialized yet, set it here.
    if( candX == POSITION_UNINITIALIZED )
    {
        // CHT IME in Vista doesn't have ATTR_TARGET_CONVERTED attribute while typing, 
        // so display the candidate list near the caret in the composition string
        if( GETLANG() == LANG_CHT && GetImeId() != 0 && g_dwCaretX != POSITION_UNINITIALIZED )
        {
            candX = g_dwCaretX;
            candY = g_dwCaretY;
        }
        else
        {
            candX = g_CaretInfo.caretX;
            candY = g_CaretInfo.caretY;
        }
    }

    SIZE largest =
    {
        0,0
    };

    static DWORD uDigitWidth = 0;
    DWORD uSpaceWidth = 0;
    static DWORD uDigitWidthList[10];
    static CImeUiFont_Base* pPrevFont = NULL;
    // find out the widest width of the digits
    if( pPrevFont != g_CaretInfo.pFont )
    {
        pPrevFont = g_CaretInfo.pFont;
        for( int cnt = 0; cnt <= 9; cnt++ )
        {
            DWORD uDW = 0;
            DWORD uDH = 0;
            TCHAR ss[8];
            swprintf_s( ss, COUNTOF(ss), TEXT( "%d" ), cnt );
            g_CaretInfo.pFont->GetTextExtent( ss, &uDW, &uDH );
            uDigitWidthList[cnt] = uDW;
            if( uDW > uDigitWidth )
                uDigitWidth = uDW;
            if( ( signed )uDH > largest.cy )
                largest.cy = uDH;
        }
    }
    uSpaceWidth = uDigitWidth;
    DWORD dwMarginX = ( uSpaceWidth + 1 ) / 2;
    DWORD adwCandWidth[ MAX_CANDLIST ];

    // Find out the widest width of the candidate strings
    DWORD dwCandWidth = 0;
    if( g_bReadingWindow && g_bHorizontalReading )
        g_CaretInfo.pFont->GetTextExtent( g_szReadingString, ( DWORD* )&largest.cx, ( DWORD* )&largest.cy );
    else
    {
        for( i = 0; g_szCandidate[i][0] && i < ( int )g_uCandPageSize; i++ )
        {
            DWORD tx = 0;
            DWORD ty = 0;

            if( g_bReadingWindow )
                g_CaretInfo.pFont->GetTextExtent( g_szCandidate[i], &tx, &ty );
            else
            {
                if( g_bVerticalCand )
                    g_CaretInfo.pFont->GetTextExtent( g_szCandidate[i] + 2, &tx, &ty );
                else
                    g_CaretInfo.pFont->GetTextExtent( g_szCandidate[i] + 1, &tx, &ty );
                tx = tx + uDigitWidth + uSpaceWidth;
            }

            if( ( signed )tx > largest.cx )
                largest.cx = tx;
            if( ( signed )ty > largest.cy )
                largest.cy = ty;
            adwCandWidth[ i ] = tx;
            dwCandWidth += tx;
        }
    }

    DWORD slotsUsed;
    if( g_bReadingWindow && g_dwCount < g_uCandPageSize )
        slotsUsed = g_dwCount;
    else
        slotsUsed = g_uCandPageSize;

    // Show candidate list above composition string if there isn't enough room below.
    DWORD dwCandHeight;
    if( g_bVerticalCand && !( g_bReadingWindow && g_bHorizontalReading ) )
        dwCandHeight = slotsUsed * largest.cy + 2;
    else
        dwCandHeight = largest.cy + 2;
    if( candY + hCompChar + dwCandHeight > g_screenHeight )
        candY -= dwCandHeight;
    else
        candY += hCompChar;
    if( ( int )candY < 0 )
        candY = 0;

    // Move candidate list horizontally to keep it inside of screen
    if( !g_bReadingWindow && IMEID_LANG( GetImeId() ) == LANG_CHS )
        dwCandWidth += dwMarginX * ( slotsUsed - 1 );
    else if( g_bReadingWindow && g_bHorizontalReading )
        dwCandWidth = largest.cx + 2 + dwMarginX * 2;
    else if( g_bVerticalCand || g_bReadingWindow )
        dwCandWidth = largest.cx + 2 + dwMarginX * 2;
    else
        dwCandWidth = slotsUsed * ( largest.cx + 1 ) + 1;
    if( candX + dwCandWidth > g_screenWidth )
        candX = g_screenWidth - dwCandWidth;
    if( ( int )candX < 0 )
        candX = 0;

    // Draw frame and background of candidate list / reading window
    int seperateLineX = 0;
    int left = candX;
    int top = candY;
    int right = candX + dwCandWidth;
    int bottom = candY + dwCandHeight;
    if( ImeUiCallback_DrawRect )
        ImeUiCallback_DrawRect( left, top, right, bottom, gSkinIME.candColorBorder );
    left++;
    top++;
    right--;
    bottom--;
    if( g_bReadingWindow || IMEID_LANG( GetImeId() ) == LANG_CHS )
    {
        if( ImeUiCallback_DrawRect )
            ImeUiCallback_DrawRect( left, top, right, bottom, gSkinIME.candColorBase );
    }
    else if( g_bVerticalCand )
    {
        // uDigitWidth is the max width of all digits. 
        if( !g_bReadingWindow )
        {
            seperateLineX = left + dwMarginX + uDigitWidth + uSpaceWidth / 2;
            if( ImeUiCallback_DrawRect )
            {
                ImeUiCallback_DrawRect( left, top, seperateLineX - 1, bottom, gSkinIME.candColorBase );
                ImeUiCallback_DrawRect( seperateLineX, top, right, bottom, gSkinIME.candColorBase );
            }
        }
    }
    else
    {
        for( i = 0; ( DWORD )i < slotsUsed; i++ )
        {
            if( ImeUiCallback_DrawRect )
                ImeUiCallback_DrawRect( left, top, left + largest.cx, bottom, gSkinIME.candColorBase );
            left += largest.cx + 1;
        }
    }

    // Draw candidates / reading strings
    candX++;
    candY++;
    if( g_bReadingWindow && g_bHorizontalReading )
    {
        int iStart = -1, iEnd = -1, iDummy;
        candX += dwMarginX;

        // draw background of error character if it exists
        TCHAR szTemp[COUNTOF( g_szReadingString ) ];
        if( g_iReadingError >= 0 )
        {
            StringCchCopy( szTemp, COUNTOF(szTemp), g_szReadingString );
            LPTSTR psz = szTemp + g_iReadingError;
#ifdef UNICODE
			psz++;
#else
            psz += ( _IsLeadByte( szTemp[g_iReadingError] ) ) ? 2 : 1;
#endif
            *psz = 0;
            g_CaretInfo.pFont->GetTextExtent( szTemp, ( DWORD* )&iEnd, ( DWORD* )&iDummy );
            TCHAR cSave = szTemp[ g_iReadingError ];
            szTemp[g_iReadingError] = 0;
            g_CaretInfo.pFont->GetTextExtent( szTemp, ( DWORD* )&iStart, ( DWORD* )&iDummy );
            szTemp[g_iReadingError] = cSave;
            if( ImeUiCallback_DrawRect )
                ImeUiCallback_DrawRect( candX + iStart, candY, candX + iEnd, candY + largest.cy,
                                        gSkinIME.candColorBorder );
        }

        g_CaretInfo.pFont->SetPosition( candX, candY );
        g_CaretInfo.pFont->SetColor( g_CaretInfo.colorCand );
        g_CaretInfo.pFont->DrawText( g_szReadingString );

        // draw error character if it exists
        if( iStart >= 0 )
        {
            g_CaretInfo.pFont->SetPosition( candX + iStart, candY );
            if( gSkinIME.candColorBase != 0xffffffff || gSkinIME.candColorBorder != 0xff000000 )
                g_CaretInfo.pFont->SetColor( g_CaretInfo.colorCand );
            else
                g_CaretInfo.pFont->SetColor( 0xff000000 + ( ~( ( 0x00ffffff ) & g_CaretInfo.colorCand ) ) );
            g_CaretInfo.pFont->DrawText( szTemp + g_iReadingError );
        }
    }
    else
    {
        for( i = 0; i < ( int )g_uCandPageSize && ( DWORD )i < g_dwCount; i++ )
        {
            if( g_dwSelection == ( DWORD )i )
            {
                if( gSkinIME.candColorBase != 0xffffffff || gSkinIME.candColorBorder != 0xff000000 )
                    g_CaretInfo.pFont->SetColor( g_CaretInfo.colorCand );
                else
                    g_CaretInfo.pFont->SetColor( 0xff000000 + ( ~( ( 0x00ffffff ) & g_CaretInfo.colorCand ) ) );

                if( ImeUiCallback_DrawRect )
                {
                    if( g_bReadingWindow || g_bVerticalCand )
                        ImeUiCallback_DrawRect( candX, candY + i * largest.cy,
                                                candX - 1 + dwCandWidth, candY + ( i + 1 ) * largest.cy,
                                                gSkinIME.candColorBorder );
                    else
                        ImeUiCallback_DrawRect( candX, candY,
                                                candX + adwCandWidth[i], candY + largest.cy,
                                                gSkinIME.candColorBorder );
                }
            }
            else
                g_CaretInfo.pFont->SetColor( g_CaretInfo.colorCand );
            if( g_szCandidate[i][0] != 0 )
            {
                if( !g_bReadingWindow && g_bVerticalCand )
                {
                    TCHAR szOneDigit[2] =
                    {
                        g_szCandidate[i][0], 0
                    };
                    int nOneDigit = g_szCandidate[i][0] - TEXT( '0' );
                    TCHAR* szCandidateBody = g_szCandidate[i] + 2;

                    int dx = candX + ( seperateLineX - candX - uDigitWidthList[nOneDigit] ) / 2;
                    int dy = candY + largest.cy * i;
					
                    g_CaretInfo.pFont->SetPosition( dx, dy );
                    g_CaretInfo.pFont->DrawText( szOneDigit );
                    g_CaretInfo.pFont->SetPosition( seperateLineX + dwMarginX, dy );
                    g_CaretInfo.pFont->DrawText( szCandidateBody );
                }
                else if( g_bReadingWindow )
                {
                    g_CaretInfo.pFont->SetPosition( dwMarginX + candX, candY + i * largest.cy );
                    g_CaretInfo.pFont->DrawText( g_szCandidate[i] );
                }
                else
                {
                    g_CaretInfo.pFont->SetPosition( uSpaceWidth / 2 + candX, candY );
                    g_CaretInfo.pFont->DrawText( g_szCandidate[i] );
                }
            }
            if( !g_bReadingWindow && !g_bVerticalCand )
            {
                if( IMEID_LANG( GetImeId() ) == LANG_CHS )
                    candX += adwCandWidth[i] + dwMarginX;
                else
                    candX += largest.cx + 1;
            }
        }
    }
}

static void CloseCandidateList()
{
    g_bCandList = false;
    if( !g_bReadingWindow )	// fix for Ent Gen #120.
    {
        g_dwCount = 0;
        memset( &g_szCandidate, 0, sizeof( g_szCandidate ) );
    }
}

//
//	ProcessIMEMessages()
//	Processes IME related messages and acquire information
//
LPARAM ImeUi_ProcessMessage( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM& lParam, bool* trapped )
{
    HIMC himc;
    int len;
    static LPARAM lAlt = 0x80000000, lCtrl = 0x80000000, lShift = 0x80000000;

    *trapped = false;
    if( !g_bInitialized || g_bDisableImeCompletely )
    {
        return 0;
    }

    switch( uMsg )
    {
            //
            //	IME Handling
            //
        case WM_INPUTLANGCHANGE:
            OnInputLangChange();
            break;

        case WM_IME_SETCONTEXT:
            //
            // We don't want anything to display, so we have to clear lParam and pass it to DefWindowProc().
            // Expecially important in Vista to receive IMN_CHANGECANDIDATE correctly.
            //
            lParam = 0;
            break;

        case WM_IME_STARTCOMPOSITION:
            InitCompStringData();
            *trapped = true;
            break;

        case WM_IME_COMPOSITION:
        {
            LONG lRet;
            TCHAR szCompStr[COUNTOF(g_szCompositionString)];

            *trapped = true;
            if( NULL == ( himc = _ImmGetContext( hWnd ) ) )
            {
                break;
            }

            // ResultStr must be processed before composition string.
            if( lParam & GCS_RESULTSTR )
            {
                lRet = ( LONG )_ImmGetCompositionString( himc, GCS_RESULTSTR, szCompStr,
                                                         COUNTOF( szCompStr ) ) / sizeof( TCHAR );
                szCompStr[lRet] = 0;
                CancelCompString( g_hwndCurr, false, GetCharCount( szCompStr ) );
                StringCchCopy( g_szCompositionString, COUNTOF(g_szCompositionString), szCompStr );
                _SendCompString();
                InitCompStringData();
            }
            //
            // Reads in the composition string.
            //
            if( lParam & GCS_COMPSTR )
            {
                //////////////////////////////////////////////////////
                // Retrieve the latest user-selected IME candidates
                lRet = ( LONG )_ImmGetCompositionString( himc, GCS_COMPSTR, szCompStr,
                                                         COUNTOF( szCompStr ) ) / sizeof( TCHAR );
                szCompStr[lRet] = 0;
                //
                // Remove the whole of the string
                //
                CancelCompString( g_hwndCurr, false, GetCharCount( szCompStr ) );

                StringCchCopy( g_szCompositionString, COUNTOF(g_szCompositionString), szCompStr );
                lRet = _ImmGetCompositionString( himc, GCS_COMPATTR, g_szCompAttrString,
                                                 COUNTOF( g_szCompAttrString ) );
                g_szCompAttrString[lRet] = 0;
                // Older CHT IME uses composition string for reading string
                if( GETLANG() == LANG_CHT && !GetImeId() )
                {
                    int i, chars = lstrlen( g_szCompositionString ) / ( 3 - sizeof( TCHAR ) );
                    if( chars )
                    {
                        g_dwCount = 4;
                        g_dwSelection = ( DWORD )-1;	// don't select any candidate

                        for( i = 3; i >= 0; i-- )
                        {
                            if( i > chars - 1 )
                                g_szCandidate[i][0] = 0;
                            else
                            {
#ifdef UNICODE
									g_szCandidate[i][0] = g_szCompositionString[i];
									g_szCandidate[i][1] = 0;
#else
                                g_szCandidate[i][0] = g_szCompositionString[i * 2];
                                g_szCandidate[i][1] = g_szCompositionString[i * 2 + 1];
                                g_szCandidate[i][2] = 0;
#endif
                            }
                        }
                        g_uCandPageSize = MAX_CANDLIST;
                        memset( g_szCompositionString, 0, 8 );
                        g_bReadingWindow = true;
                        GetReadingWindowOrientation( 0 );
                        if( g_bHorizontalReading )
                        {
                            g_iReadingError = -1;
                            g_szReadingString[0] = 0;
                            for( i = 0; i < ( int )g_dwCount; i++ )
                            {
                                if( g_dwSelection == ( DWORD )i )
                                    g_iReadingError = lstrlen( g_szReadingString );
                                LPCTSTR pszTmp = g_szCandidate[i];
                                wcscat_s( g_szReadingString, COUNTOF(g_szReadingString), pszTmp );
                            }
                        }
                    }
                    else
                        g_dwCount = 0;
                }

                // get caret position in composition string
                g_IMECursorBytes = _ImmGetCompositionString( himc, GCS_CURSORPOS, NULL, 0 );
                g_IMECursorChars = GetCharCountFromBytes( g_szCompositionString, g_IMECursorBytes );

                if( g_dwIMELevel == 3 )
                {
                    // send composition string via WM_CHAR
                    _SendCompString();
                    // move caret to appropreate location
                    len = GetCharCount( g_szCompositionString + g_IMECursorBytes );
                    SendControlKeys( VK_LEFT, len );
                }
            }
            _ImmReleaseContext( hWnd, himc );
        }
            break;

        case WM_IME_ENDCOMPOSITION:
            CancelCompString( g_hwndCurr );
            InitCompStringData();
            break;

        case WM_IME_NOTIFY:
            switch( wParam )
            {
                case IMN_SETCONVERSIONMODE:
                {
                    // Disable CHT IME software keyboard.
                    static bool bNoReentrance = false;
                    if( LANG_CHT == GETLANG() && !bNoReentrance )
                    {
                        bNoReentrance = true;
                        DWORD dwConvMode, dwSentMode;
                        _ImmGetConversionStatus( g_himcOrg, &dwConvMode, &dwSentMode );
                        const DWORD dwFlag = IME_CMODE_SOFTKBD | IME_CMODE_SYMBOL;
                        if( dwConvMode & dwFlag )
                            _ImmSetConversionStatus( g_himcOrg, dwConvMode & ~dwFlag, dwSentMode );
                    }
                    bNoReentrance = false;
                }
                    // fall through
                case IMN_SETOPENSTATUS:
                    if( g_bUILessMode )
                        break;
                    CheckToggleState();
                    break;
			
                case IMN_OPENCANDIDATE:
                case IMN_CHANGECANDIDATE:
                    if( g_bUILessMode )
                    {
                        break;
                    }
                    {
                        g_bCandList = true;
                        *trapped = true;
                        if( NULL == ( himc = _ImmGetContext( hWnd ) ) )
                            break;
					
                        LPCANDIDATELIST lpCandList;
                        DWORD dwIndex, dwBufLen;

                        g_bReadingWindow = false;
                        dwIndex = 0;
                        dwBufLen = _GetCandidateList( himc, dwIndex, &lpCandList );

                        if( dwBufLen )
                        {
                            g_dwSelection = lpCandList->dwSelection;
                            g_dwCount = lpCandList->dwCount;

                            int startOfPage = 0;
                            if( GETLANG() == LANG_CHS && GetImeId() )
                            {
                                // MSPY (CHS IME) has variable number of candidates in candidate window
                                // find where current page starts, and the size of current page
                                const int maxCandChar = 18 * ( 3 - sizeof( TCHAR ) );
                                UINT cChars = 0;
                                UINT i;
                                for( i = 0; i < g_dwCount; i++ )
                                {
                                    UINT uLen = lstrlen(
                                        ( LPTSTR )( ( DWORD )lpCandList + lpCandList->dwOffset[i] ) ) +
                                        ( 3 - sizeof( TCHAR ) );
                                    if( uLen + cChars > maxCandChar )
                                    {
                                        if( i > g_dwSelection )
                                        {
                                            break;
                                        }
                                        startOfPage = i;
                                        cChars = uLen;
                                    }
                                    else
                                    {
                                        cChars += uLen;
                                    }
                                }
                                g_uCandPageSize = i - startOfPage;
                            }
                            else
                            {
                                g_uCandPageSize = min( lpCandList->dwPageSize, MAX_CANDLIST );
                                startOfPage = g_bUILessMode ? lpCandList->dwPageStart :
                                    ( g_dwSelection / g_uCandPageSize ) * g_uCandPageSize;
                            }

                            g_dwSelection = ( GETLANG() == LANG_CHS && !GetImeId() ) ? ( DWORD )-1
                                : g_dwSelection - startOfPage;

                            memset( &g_szCandidate, 0, sizeof( g_szCandidate ) );
                            for( UINT i = startOfPage, j = 0;
                                 ( DWORD )i < lpCandList->dwCount && j < g_uCandPageSize;
                                 i++, j++ )
                            {
                                ComposeCandidateLine( j,
                                                      ( LPTSTR )( ( DWORD )lpCandList + lpCandList->dwOffset[i] ) );
                            }
                            ImeUiCallback_Free( ( HANDLE )lpCandList );
                            _ImmReleaseContext( hWnd, himc );

                            // don't display selection in candidate list in case of Korean and old Chinese IME.
                            if( GETPRIMLANG() == LANG_KOREAN ||
                                GETLANG() == LANG_CHT && !GetImeId() )
                                g_dwSelection = ( DWORD )-1;
                        }
                        break;
                    }
			
                case IMN_CLOSECANDIDATE:
                    if( g_bUILessMode )
                    {
                        break;
                    }
                    CloseCandidateList();
                    *trapped = true;
                    break;

                    // Jun.16,2000 05:21 by yutaka.
                case IMN_PRIVATE:
                {
                    if( !g_bCandList )
                    {
                        GetReadingString( hWnd );
                    }
                    // Trap some messages to hide reading window
                    DWORD dwId = GetImeId();
                    switch( dwId )
                    {
                        case IMEID_CHT_VER42:
                        case IMEID_CHT_VER43:
                        case IMEID_CHT_VER44:
                        case IMEID_CHS_VER41:
                        case IMEID_CHS_VER42:
                            if( ( lParam == 1 ) || ( lParam == 2 ) )
                            {
                                *trapped = true;
                            }
                            break;
                        case IMEID_CHT_VER50:
                        case IMEID_CHT_VER51:
                        case IMEID_CHT_VER52:
                        case IMEID_CHT_VER60:
                        case IMEID_CHS_VER53:
                            if( ( lParam == 16 ) || ( lParam == 17 ) || ( lParam == 26 ) || ( lParam == 27 ) ||
                                ( lParam == 28 ) )
                            {
                                *trapped = true;
                            }
                            break;
                    }
                }
                    break;

                default:
                    *trapped = true;
                    break;
            }
            break;

            // fix for #15386 - When Text Service Framework is installed in Win2K, Alt+Shift and Ctrl+Shift combination (to switch 
            // input locale / keyboard layout) doesn't send WM_KEYUP message for the key that is released first. We need to check
            // if these keys are actually up whenever we receive key up message for other keys.
        case WM_KEYUP:
        case WM_SYSKEYUP:
            if( !( lAlt & 0x80000000 ) && wParam != VK_MENU && ( GetAsyncKeyState( VK_MENU ) & 0x8000 ) == 0 )
            {
                PostMessageA( GetFocus(), WM_KEYUP, ( WPARAM )VK_MENU, ( lAlt & 0x01ff0000 ) | 0xC0000001 );
            }	
            else if( !( lCtrl & 0x80000000 ) && wParam != VK_CONTROL &&
                     ( GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) == 0 )
            {
                PostMessageA( GetFocus(), WM_KEYUP, ( WPARAM )VK_CONTROL, ( lCtrl & 0x01ff0000 ) | 0xC0000001 );
            }
            else if( !( lShift & 0x80000000 ) && wParam != VK_SHIFT && ( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) == 0 )
            {
                PostMessageA( GetFocus(), WM_KEYUP, ( WPARAM )VK_SHIFT, ( lShift & 0x01ff0000 ) | 0xC0000001 );
            }
            // fall through WM_KEYDOWN / WM_SYSKEYDOWN
        case WM_KEYDOWN:
        case WM_SYSKEYDOWN:
            {
                switch( wParam )
                {
                    case VK_MENU:
                        lAlt = lParam;
                        break;
                    case VK_SHIFT:
                        lShift = lParam;
                        break;
                    case VK_CONTROL:
                        lCtrl = lParam;
                        break;
                }
            }
            break;
    }
    return 0;
}

void ImeUi_SetCaretPosition( UINT x, UINT y )
{
    if( !g_bInitialized )
        return;
    g_CaretInfo.caretX = x;
    g_CaretInfo.caretY = y;
}

void ImeUi_SetCompStringAppearance( CImeUiFont_Base* pFont, DWORD color, const RECT* prc )
{
    if( !g_bInitialized )
        return;
    g_CaretInfo.pFont = pFont;
    g_CaretInfo.margins = *prc;

    if( 0 == gSkinIME.candColorText )
        g_CaretInfo.colorCand = color;
    else
        g_CaretInfo.colorCand = gSkinIME.candColorText;
    if( 0 == gSkinIME.compColorText )
        g_CaretInfo.colorComp = color;
    else
        g_CaretInfo.colorComp = gSkinIME.compColorText;
}

void ImeUi_SetState( DWORD dwState )
{
    if( !g_bInitialized )
        return;
    HIMC himc;
    if( dwState == IMEUI_STATE_ON )
    {
        ImeUi_EnableIme( true );
    }
    if( NULL != ( himc = _ImmGetContext( g_hwndCurr ) ) )
    {
        if( g_bDisableImeCompletely )
            dwState = IMEUI_STATE_OFF;

        bool bOn = dwState == IMEUI_STATE_ON;	// for non-Chinese IME
        switch( GETPRIMLANG() )
        {
            case LANG_CHINESE:
            {
                // toggle Chinese IME
                DWORD dwId;
                DWORD dwConvMode = 0, dwSentMode = 0;
                if( ( g_bChineseIME && dwState == IMEUI_STATE_OFF ) ||
                    ( !g_bChineseIME && dwState != IMEUI_STATE_OFF ) )
                {
                    _ImmSimulateHotKey( g_hwndCurr, IME_THOTKEY_IME_NONIME_TOGGLE );
                    _PumpMessage();
                }
                if( dwState != IMEUI_STATE_OFF )
                {
                    dwId = GetImeId();
                    if( dwId )
                    {
                        _ImmGetConversionStatus( himc, &dwConvMode, &dwSentMode );
                        dwConvMode = ( dwState == IMEUI_STATE_ON )
                            ? ( dwConvMode | IME_CMODE_NATIVE )
                            : ( dwConvMode & ~IME_CMODE_NATIVE );
                        _ImmSetConversionStatus( himc, dwConvMode, dwSentMode );
                    }
                }
                break;
            }
            case LANG_KOREAN:
                // toggle Korean IME
                if( ( bOn && g_dwState != IMEUI_STATE_ON ) || ( !bOn && g_dwState == IMEUI_STATE_ON ) )
                {
                    _ImmSimulateHotKey( g_hwndCurr, IME_KHOTKEY_ENGLISH );
                }
                break;
            case LANG_JAPANESE:
                _ImmSetOpenStatus( himc, bOn );
                break;
        }
        _ImmReleaseContext( g_hwndCurr, himc );
        CheckToggleState();
    }
}

DWORD ImeUi_GetState()
{
    if( !g_bInitialized )
        return IMEUI_STATE_OFF;
    CheckToggleState();
    return g_dwState;
}

void ImeUi_EnableIme( bool bEnable )
{
    if( !g_bInitialized || !g_hwndCurr )
        return;
    if( g_bDisableImeCompletely )
        bEnable = false;

    if( g_hwndCurr == g_hwndMain )
    {
        HIMC himcDbg;
        himcDbg = _ImmAssociateContext( g_hwndCurr, bEnable? g_himcOrg : NULL );
    }
    g_bImeEnabled = bEnable;
    if( bEnable )
    {
        CheckToggleState();
    }
    CTsfUiLessMode::EnableUiUpdates( bEnable );
}

bool ImeUi_IsEnabled( void )
{
    return g_bImeEnabled;
}

bool ImeUi_Initialize( HWND hwnd, bool bDisable )
{
    if( g_bInitialized )
    {
        return true;
    }
    g_hwndMain = hwnd;
    g_disableCicero.Initialize();

    g_osi.dwOSVersionInfoSize = sizeof( OSVERSIONINFOA );
    GetVersionExA( &g_osi );

    bool bUnicodeImm = false;
    // IMM in NT or Win98 supports Unicode
    if( g_osi.dwPlatformId == VER_PLATFORM_WIN32_NT ||
        ( g_osi.dwMajorVersion > 4 ) ||
        ( g_osi.dwMajorVersion == 4 ) && ( g_osi.dwMinorVersion > 0 ) )
    {
        bUnicodeImm = true;
    }

    g_hImmDll = LoadLibraryA( "imm32.dll" );
    g_bDisableImeCompletely = false;

    if( g_hImmDll )
    {
        _ImmLockIMC = ( LPINPUTCONTEXT2 ( WINAPI* )( HIMC hIMC ) )GetProcAddress( g_hImmDll, "ImmLockIMC" );
        _ImmUnlockIMC = ( BOOL ( WINAPI* )( HIMC hIMC ) )GetProcAddress( g_hImmDll, "ImmUnlockIMC" );
        _ImmLockIMCC = ( LPVOID ( WINAPI* )( HIMCC hIMCC ) )GetProcAddress( g_hImmDll, "ImmLockIMCC" );
        _ImmUnlockIMCC = ( BOOL ( WINAPI* )( HIMCC hIMCC ) )GetProcAddress( g_hImmDll, "ImmUnlockIMCC" );
        BOOL ( WINAPI* _ImmDisableTextFrameService )( DWORD ) = ( BOOL ( WINAPI* )( DWORD ) )GetProcAddress( g_hImmDll,
                                                                                                             "ImmDisableTextFrameService" );
        if( _ImmDisableTextFrameService )
        {
            _ImmDisableTextFrameService( ( DWORD )-1 );
        }
    }
    else
    {
        g_bDisableImeCompletely = true;
        return false;
    }
#ifdef UNICODE
	if ( bUnicodeImm )
	{
		_ImmGetCompositionString =	ImmGetCompositionStringW;
		_ImmGetCandidateList =		ImmGetCandidateListW;
		_GetCandidateList =			GetCandidateList;
	}
	else
	{
		_ImmGetCandidateList =		ImmGetCandidateListA;
		_ImmGetCompositionString =	AW_ImmGetCompositionString;
		_GetCandidateList =			AW_GetCandidateList;
	}
#else
    if( bUnicodeImm )
    {
        _ImmGetCompositionString = WA_ImmGetCompositionString;
        _ImmGetCandidateList = ImmGetCandidateListA;
        _GetCandidateList = WA_GetCandidateList;
    }
    else
    {
        _ImmGetCompositionString = ImmGetCompositionStringA;
        _ImmGetCandidateList = ImmGetCandidateListA;
        _GetCandidateList = GetCandidateList;
    }
#endif

    // There are the following combinations of code config, window type, and the method of sending characters.
    // Wnd: Unicode, Code: Unicode, Method: SendMessageW (SendMessageW must be supported since RegisterClassW is successful)
    // Wnd: non Uni, Code: Unicode, Method: AW_SendCompString (Send characters in multibyte after W->A conversion)
    // Wnd: Unicode, Code: non Uni, Method: SendMessageA (System does A->W conversion) - possible, but unlikely to be used.
    // Wnd: non Uni, Code: non Uni, Method: SendMessageA
#ifdef UNICODE
	if ( !IsWindowUnicode( hwnd ) )
	{
		_SendCompString = AW_SendCompString;
	}
	else
#endif
    {
        _SendCompString = SendCompString;
#ifdef UNICODE
		_SendMessage = SendMessageW;
#endif
    }

    // turn init flag on so that subsequent calls to ImeUi functions work. 
    g_bInitialized = true;

    ImeUi_SetWindow( g_hwndMain );
    g_himcOrg = _ImmGetContext( g_hwndMain );
    _ImmReleaseContext( g_hwndMain, g_himcOrg );

    if( !g_himcOrg )
    {
        bDisable = true;
    }

    // the following pointers to function has to be initialized before this function is called.
    if( bDisable ||
        !ImeUiCallback_Malloc ||
        !ImeUiCallback_Free
        )
    {
        g_bDisableImeCompletely = true;
        ImeUi_EnableIme( false );
        g_bInitialized = bDisable;
        return false;
    }

    g_uCaretBlinkTime = GetCaretBlinkTime();

#ifndef UNICODE
    // Check if system is SBCS system
    CPINFO cpi;
    BOOL bRc = GetCPInfo( CP_ACP, &cpi );
    if( bRc )
    {
        if( cpi.MaxCharSize == 1 )
        {
            g_bDisableImeCompletely = true;	// SBCS system. Disable IME.
        }
    }
#endif

    g_CaretInfo.caretX = 0;
    g_CaretInfo.caretY = 0;
    g_CaretInfo.pFont = 0;
    g_CaretInfo.colorComp = 0;
    g_CaretInfo.colorCand = 0;
    g_CaretInfo.margins.left = 0;
    g_CaretInfo.margins.right = 640;
    g_CaretInfo.margins.top = 0;
    g_CaretInfo.margins.bottom = 480;

    CheckInputLocale();
    OnInputLangChangeWorker();
    ImeUi_SetSupportLevel( 2 );

    // SetupTSFSinks has to be called before CheckToggleState to make it work correctly.
    g_bUILessMode = CTsfUiLessMode::SetupSinks() != FALSE;
    CheckToggleState();
    if( g_bUILessMode )
    {
        g_bChineseIME = ( GETPRIMLANG() == LANG_CHINESE ) && CTsfUiLessMode::CurrentInputLocaleIsIme();
        CTsfUiLessMode::UpdateImeState();
    }
    ImeUi_EnableIme( false );

    return true;
}

void ImeUi_Uninitialize()
{
    if( !g_bInitialized )
    {
        return;
    }
    CTsfUiLessMode::ReleaseSinks();
    if( g_hwndMain )
    {
        ImmAssociateContext( g_hwndMain, g_himcOrg );
    }
    g_hwndMain = NULL;
    g_himcOrg = NULL;
    if( g_hImmDll )
    {
        FreeLibrary( g_hImmDll );
        g_hImmDll = NULL;
    }
    g_disableCicero.Uninitialize();
    g_bInitialized = false;
}

//
//	GetImeId( UINT uIndex )
//		returns 
//	returned value:
//	0: In the following cases
//		- Non Chinese IME input locale
//		- Older Chinese IME
//		- Other error cases
//
//	Othewise:
//      When uIndex is 0 (default)
//			bit 31-24:	Major version
//			bit 23-16:	Minor version
//			bit 15-0:	Language ID
//		When uIndex is 1
//			pVerFixedInfo->dwFileVersionLS
//
//	Use IMEID_VER and IMEID_LANG macro to extract version and language information.
//	
static DWORD GetImeId( UINT uIndex )
{
    static HKL hklPrev = 0;
    static DWORD dwRet[2] =
    {
        0, 0
    };
	
    DWORD dwVerSize;
    DWORD dwVerHandle;
    LPVOID lpVerBuffer;
    LPVOID lpVerData;
    UINT cbVerData;
    char szTmp[1024];

    if( uIndex >= sizeof( dwRet ) / sizeof( dwRet[0] ) )
        return 0;

    HKL kl = g_hklCurrent;
    if( hklPrev == kl )
    {
        return dwRet[uIndex];
    }
    hklPrev = kl;
    DWORD dwLang = ( ( DWORD )kl & 0xffff );

    if( g_bUILessMode && GETLANG() == LANG_CHT )
    {
        // In case of Vista, artifitial value is returned so that it's not considered as older IME.
        dwRet[0] = IMEID_CHT_VER_VISTA;
        dwRet[1] = 0;
        return dwRet[0];
    }

    if( kl != _CHT_HKL_NEW_PHONETIC && kl != _CHT_HKL_NEW_CHANG_JIE
        && kl != _CHT_HKL_NEW_QUICK && kl != _CHT_HKL_HK_CANTONESE && kl != _CHS_HKL )
    {
        goto error;
    }
	
    if( _ImmGetIMEFileNameA( kl, szTmp, sizeof( szTmp ) - 1 ) <= 0 )
    {
        goto error;
    }

    if( !_GetReadingString )	// IME that doesn't implement private API
    {
#define LCID_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
        if( ( CompareStringA( LCID_INVARIANT, NORM_IGNORECASE, szTmp, -1, _CHT_IMEFILENAME, -1 ) != 2 )
            && ( CompareStringA( LCID_INVARIANT, NORM_IGNORECASE, szTmp, -1, _CHT_IMEFILENAME2, -1 ) != 2 )
            && ( CompareStringA( LCID_INVARIANT, NORM_IGNORECASE, szTmp, -1, _CHT_IMEFILENAME3, -1 ) != 2 )
            && ( CompareStringA( LCID_INVARIANT, NORM_IGNORECASE, szTmp, -1, _CHS_IMEFILENAME, -1 ) != 2 )
            && ( CompareStringA( LCID_INVARIANT, NORM_IGNORECASE, szTmp, -1, _CHS_IMEFILENAME2, -1 ) != 2 )
            )
        {
            goto error;
        }
    }

    dwVerSize = GetFileVersionInfoSizeA( szTmp, &dwVerHandle );
    if( dwVerSize )
    {
        lpVerBuffer = ( LPVOID )ImeUiCallback_Malloc( dwVerSize );
        if( lpVerBuffer )
        {
            if( GetFileVersionInfoA( szTmp, dwVerHandle, dwVerSize, lpVerBuffer ) )
            {
                if( VerQueryValueA( lpVerBuffer, "\\", &lpVerData, &cbVerData ) )
                {
#define pVerFixedInfo ((VS_FIXEDFILEINFO FAR*)lpVerData)
                    DWORD dwVer = pVerFixedInfo->dwFileVersionMS;
                    dwVer = ( dwVer & 0x00ff0000 ) << 8 | ( dwVer & 0x000000ff ) << 16;
                    if( _GetReadingString ||
                        dwLang == LANG_CHT && (
                        dwVer == MAKEIMEVERSION(4, 2) ||
                        dwVer == MAKEIMEVERSION(4, 3) ||
                        dwVer == MAKEIMEVERSION(4, 4) ||
                        dwVer == MAKEIMEVERSION(5, 0) ||
                        dwVer == MAKEIMEVERSION(5, 1) ||
                        dwVer == MAKEIMEVERSION(5, 2) ||
                        dwVer == MAKEIMEVERSION(6, 0) )
                        ||
                        dwLang == LANG_CHS && (
                        dwVer == MAKEIMEVERSION(4, 1) ||
                        dwVer == MAKEIMEVERSION(4, 2) ||
                        dwVer == MAKEIMEVERSION(5, 3) ) )
                    {
                        dwRet[0] = dwVer | dwLang;
                        dwRet[1] = pVerFixedInfo->dwFileVersionLS;
                        ImeUiCallback_Free( lpVerBuffer );
                        return dwRet[0];
                    }
#undef pVerFixedInfo
                }
            }
        }
        ImeUiCallback_Free( lpVerBuffer );
    }

    // The flow comes here in the following conditions
    // - Non Chinese IME input locale
    // - Older Chinese IME
    // - Other error cases
error:
    dwRet[0] = dwRet[1] = 0;
    return dwRet[uIndex];
}

static void GetReadingString( HWND hWnd )
{
    if( g_bUILessMode )
    {
        return;
    }
    DWORD dwId = GetImeId();
    if( !dwId )
    {
        return;
    }
	
    HIMC himc;
    himc = _ImmGetContext( hWnd );
    if( !himc )
        return;

    DWORD dwlen = 0;
    DWORD dwerr = 0;
    WCHAR wzBuf[16];	// We believe 16 wchars are big enough to hold reading string after having discussion with CHT IME team.
    WCHAR* wstr = wzBuf;
    bool unicode = FALSE;
    LPINPUTCONTEXT2 lpIMC = NULL;

    if( _GetReadingString )
    {
        BOOL bVertical;
        UINT uMaxUiLen;
        dwlen = _GetReadingString( himc, 0, NULL, ( PINT )&dwerr, &bVertical, &uMaxUiLen );
        if( dwlen )
        {
            if( dwlen > COUNTOF(wzBuf) )
            {
                dwlen = COUNTOF(wzBuf);
            }
            dwlen = _GetReadingString( himc, dwlen, wstr, ( PINT )&dwerr, &bVertical, &uMaxUiLen );
        }

        g_bHorizontalReading = bVertical == 0;
        unicode = true;
    }
    else //	IMEs that doesn't implement Reading String API
    {
        lpIMC = _ImmLockIMC( himc );
		
        // *** hacking code from Michael Yang ***
		
        LPBYTE p = 0;
		
        switch( dwId )
        {
		
            case IMEID_CHT_VER42: // New(Phonetic/ChanJie)IME98  : 4.2.x.x // Win98
            case IMEID_CHT_VER43: // New(Phonetic/ChanJie)IME98a : 4.3.x.x // WinMe, Win2k
            case IMEID_CHT_VER44: // New ChanJie IME98b          : 4.4.x.x // WinXP
			
                p = *( LPBYTE* )( ( LPBYTE )_ImmLockIMCC( lpIMC->hPrivate ) + 24 );
                if( !p ) break;
                dwlen = *( DWORD* )( p + 7 * 4 + 32 * 4 );	//m_dwInputReadStrLen
                dwerr = *( DWORD* )( p + 8 * 4 + 32 * 4 );	//m_dwErrorReadStrStart
                wstr = ( WCHAR* )( p + 56 );
                unicode = TRUE;
                break;
		
            case IMEID_CHT_VER50: // 5.0.x.x // WinME
			
                p = *( LPBYTE* )( ( LPBYTE )_ImmLockIMCC( lpIMC->hPrivate ) + 3 * 4 ); // PCKeyCtrlManager
                if( !p ) break;
                p = *( LPBYTE* )( ( LPBYTE )p + 1 * 4 + 5 * 4 + 4 * 2 ); // = PCReading = &STypingInfo
                if( !p ) break;
                dwlen = *( DWORD* )( p + 1 * 4 + ( 16 * 2 + 2 * 4 ) + 5 * 4 + 16 );		//m_dwDisplayStringLength;
                dwerr = *( DWORD* )( p + 1 * 4 + ( 16 * 2 + 2 * 4 ) + 5 * 4 + 16 + 1 * 4 );	//m_dwDisplayErrorStart;
                wstr = ( WCHAR* )( p + 1 * 4 + ( 16 * 2 + 2 * 4 ) + 5 * 4 );
                unicode = FALSE;
                break;

            case IMEID_CHT_VER51: // 5.1.x.x // IME2002(w/OfficeXP)
            case IMEID_CHT_VER52: // 5.2.x.x // (w/whistler)
            case IMEID_CHS_VER53: // 5.3.x.x // SCIME2k or MSPY3 (w/OfficeXP and Whistler)

                p = *( LPBYTE* )( ( LPBYTE )_ImmLockIMCC( lpIMC->hPrivate ) + 4 );   // PCKeyCtrlManager
                if( !p ) break;
                p = *( LPBYTE* )( ( LPBYTE )p + 1 * 4 + 5 * 4 );                       // = PCReading = &STypingInfo
                if( !p ) break;
                dwlen = *( DWORD* )( p + 1 * 4 + ( 16 * 2 + 2 * 4 ) + 5 * 4 + 16 * 2 );		//m_dwDisplayStringLength;
                dwerr = *( DWORD* )( p + 1 * 4 + ( 16 * 2 + 2 * 4 ) + 5 * 4 + 16 * 2 + 1 * 4 );	//m_dwDisplayErrorStart;
                wstr = ( WCHAR* )( p + 1 * 4 + ( 16 * 2 + 2 * 4 ) + 5 * 4 );
                unicode = TRUE;
                break;

                // the code tested only with Win 98 SE (MSPY 1.5/ ver 4.1.0.21)
            case IMEID_CHS_VER41:
            {
                int offset;
                offset = ( GetImeId( 1 ) >= 0x00000002 ) ? 8 : 7;

                p = *( LPBYTE* )( ( LPBYTE )_ImmLockIMCC( lpIMC->hPrivate ) + offset * 4 );
                if( !p ) break;
                dwlen = *( DWORD* )( p + 7 * 4 + 16 * 2 * 4 );
                dwerr = *( DWORD* )( p + 8 * 4 + 16 * 2 * 4 );
                dwerr = min( dwerr, dwlen );
                wstr = ( WCHAR* )( p + 6 * 4 + 16 * 2 * 1 );
                unicode = TRUE;
                break;
            }

            case IMEID_CHS_VER42: // 4.2.x.x // SCIME98 or MSPY2 (w/Office2k, Win2k, WinME, etc)
            {
                int nTcharSize = IsNT() ? sizeof( WCHAR ) : sizeof( char );
                p = *( LPBYTE* )( ( LPBYTE )_ImmLockIMCC( lpIMC->hPrivate ) + 1 * 4 + 1 * 4 + 6 * 4 ); // = PCReading = &STypintInfo
                if( !p ) break;
                dwlen = *( DWORD* )( p + 1 * 4 + ( 16 * 2 + 2 * 4 ) + 5 * 4 + 16 * nTcharSize );		//m_dwDisplayStringLength;
                dwerr = *( DWORD* )( p + 1 * 4 + ( 16 * 2 + 2 * 4 ) + 5 * 4 + 16 * nTcharSize + 1 * 4 );	//m_dwDisplayErrorStart;
                wstr = ( WCHAR* )( p + 1 * 4 + ( 16 * 2 + 2 * 4 ) + 5 * 4 );                 //m_tszDisplayString
                unicode = IsNT() ? TRUE : FALSE;
            }
        }	// switch
		
        g_szCandidate[0][0] = 0;
        g_szCandidate[1][0] = 0;
        g_szCandidate[2][0] = 0;
        g_szCandidate[3][0] = 0;
    }	
    g_dwCount = dwlen;
    g_dwSelection = ( DWORD )-1; // do not select any char
    if( unicode )
    {
        int i;
        for( i = 0; ( DWORD )i < dwlen; i++ ) // dwlen > 0, if known IME : yutakah
        {
            if( dwerr <= ( DWORD )i && g_dwSelection == ( DWORD )-1 )
            { // select error char
                g_dwSelection = i;
            }
#ifdef UNICODE
			g_szCandidate[i][0] = wstr[i];
			g_szCandidate[i][1] = 0;
#else
            char mbc[3];
            mbc[1] = 0;
            mbc[2] = 0;
            WideCharToMultiByte( g_uCodePage, 0, wstr + i, 1, mbc, sizeof( mbc ), NULL, NULL );

            g_szCandidate[i][0] = mbc[0];
            g_szCandidate[i][1] = mbc[1];
            g_szCandidate[i][2] = 0;
#endif
        }
        g_szCandidate[i][0] = 0;
    }
    else
    {
        char* p = ( char* )wstr;
        int i, j;
        for( i = 0, j = 0; ( DWORD )i < dwlen; i++, j++ ) // dwlen > 0, if known IME : yutakah
        {
            if( dwerr <= ( DWORD )i && g_dwSelection == ( DWORD )-1 )
            {
                g_dwSelection = ( DWORD )j;
            }
#ifdef UNICODE
			MultiByteToWideChar( g_uCodePage, 0, p + i, 1 + ( _IsLeadByte( p[i] ) ? 1 : 0 ),
				g_szCandidate[j], 1 );
			if ( _IsLeadByte( p[i] ) )
			{
				i++;
			}
#else
            g_szCandidate[j][0] = p[i];
            g_szCandidate[j][1] = 0;
            g_szCandidate[j][2] = 0;
            if( _IsLeadByte(p[i]) )
            {
                i++;
                g_szCandidate[j][1] = p[i];
            }
#endif
        }
        g_szCandidate[j][0] = 0;
        g_dwCount = j;
    }
    if( !_GetReadingString )
    {
        _ImmUnlockIMCC( lpIMC->hPrivate );
        _ImmUnlockIMC( himc );
        GetReadingWindowOrientation( dwId );
    }
    _ImmReleaseContext( hWnd, himc );

    g_bReadingWindow = true;
    if( g_bHorizontalReading )
    {
        g_iReadingError = -1;
        g_szReadingString[0] = 0;
        for( UINT i = 0; i < g_dwCount; i++ )
        {
            if( g_dwSelection == ( DWORD )i )
                g_iReadingError = lstrlen( g_szReadingString );
            LPCTSTR pszTmp = g_szCandidate[i];
            wcscat_s( g_szReadingString, COUNTOF(g_szReadingString), pszTmp );
        }
    }
    g_uCandPageSize = MAX_CANDLIST;
}


static struct
{
    bool m_bCtrl;
    bool m_bShift;
    bool m_bAlt;
    UINT m_uVk;
}
    aHotKeys[] =
{
    false,	false,	false,	VK_APPS,
    true,	false,	false,	'8',
    true,	false,	false,	'Y',
    true,	false,	false,	VK_DELETE,
    true,	false,	false,	VK_F7,
    true,	false,	false,	VK_F9,
    true,	false,	false,	VK_F10,
    true,	false,	false,	VK_F11,
    true,	false,	false,	VK_F12,
    false,	false,	false,	VK_F2,
    false,	false,	false,	VK_F3,
    false,	false,	false,	VK_F4,
    false,	false,	false,	VK_F5,
    false,	false,	false,	VK_F10,
    false,	true,	false,	VK_F6,
    false,	true,	false,	VK_F7,
    false,	true,	false,	VK_F8,
    true,	true,	false,	VK_F10,
    true,	true,	false,	VK_F11,
    true,	false,	false,	VK_CONVERT,
    true,	false,	false,	VK_SPACE,
    true,	false,	true,	0xbc,	// Alt + Ctrl + ',': SW keyboard for Trad. Chinese IME
    true,   false,  false,  VK_TAB, // ATOK2005's Ctrl+TAB
};

//
// Ignores specific keys when IME is on. Returns true if the message is a hot key to ignore.
// - Caller doesn't have to check whether IME is on.
// - This function must be called before TranslateMessage() is called.
//
bool ImeUi_IgnoreHotKey( const MSG* pmsg )
{
    if( !g_bInitialized || !pmsg )
        return false;

    if( pmsg->wParam == VK_PROCESSKEY && ( pmsg->message == WM_KEYDOWN || pmsg->message == WM_SYSKEYDOWN ) )
    {
        bool bCtrl, bShift, bAlt;
        UINT uVkReal = _ImmGetVirtualKey( pmsg->hwnd );
        // special case #1 - VK_JUNJA toggles half/full width input mode in Korean IME.
        // This VK (sent by Alt+'=' combo) is ignored regardless of the modifier state.
        if( uVkReal == VK_JUNJA )
        {
            return true;
        }
        // special case #2 - disable right arrow key that switches the candidate list to expanded mode in CHT IME.
        if( uVkReal == VK_RIGHT && g_bCandList && GETLANG() == LANG_CHT )
        {
            return true;
        }
#ifndef ENABLE_HANJA_KEY
        // special case #3 - we disable VK_HANJA key because 1. some Korean fonts don't Hanja and 2. to reduce testing cost.
        if( uVkReal == VK_HANJA && GETPRIMLANG() == LANG_KOREAN )
        {
            return true;
        }
#endif
        bCtrl = ( GetKeyState( VK_CONTROL ) & 0x8000 ) ? true : false;
        bShift = ( GetKeyState( VK_SHIFT ) & 0x8000 ) ? true : false;
        bAlt = ( GetKeyState( VK_MENU ) & 0x8000 ) ? true : false;
        for( int i = 0; i < COUNTOF(aHotKeys); i++ )
        {
            if( aHotKeys[i].m_bCtrl == bCtrl &&
                aHotKeys[i].m_bShift == bShift &&
                aHotKeys[i].m_bAlt == bAlt &&
                aHotKeys[i].m_uVk == uVkReal )
                return true;
        }
    }
    return false;
}

void ImeUi_FinalizeString( bool bSend )
{
    HIMC himc;
    static bool bProcessing = false; // to avoid infinite recursion
    if( !g_bInitialized || bProcessing || NULL == ( himc = _ImmGetContext( g_hwndCurr ) ) )
        return;
    bProcessing = true;

    if( g_dwIMELevel == 2 && bSend )
    {
        // Send composition string to app.
        LONG lRet = lstrlen( g_szCompositionString );
        assert( lRet >= 2 );
        // In case of CHT IME, don't send the trailing double byte space, if it exists.
#ifdef UNICODE
		if ( GETLANG() == LANG_CHT && (lRet >= 1)
			&& g_szCompositionString[lRet - 1] == 0x3000 )
		{
			lRet--;
		}
#else
        if( GETLANG() == LANG_CHT && ( lRet >= 2 )
            && ( BYTE )( g_szCompositionString[lRet - 2] ) == 0xa1
            && ( BYTE )( g_szCompositionString[lRet - 1] ) == 0x40 )
        {
            lRet -= 2;
        }
#endif
        _SendCompString();
    }

    InitCompStringData();
    // clear composition string in IME
    _ImmNotifyIME( himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0 );
    if( g_bUILessMode )
    {
        // For some reason ImmNotifyIME doesn't work on DaYi and Array CHT IMEs. Cancel composition string by setting zero-length string.
        ImmSetCompositionString( himc, SCS_SETSTR, TEXT( "" ), sizeof( TCHAR ), TEXT( "" ), sizeof( TCHAR ) );
    }
    // the following line is necessary as Korean IME doesn't close cand list when comp string is cancelled.
    _ImmNotifyIME( himc, NI_CLOSECANDIDATE, 0, 0 );	
    _ImmReleaseContext( g_hwndCurr, himc );
    // Zooty2 RAID #4759: Sometimes application doesn't receive IMN_CLOSECANDIDATE on Alt+Tab
    // So the same code for IMN_CLOSECANDIDATE is replicated here.
    CloseCandidateList();
    bProcessing = false;
    return;
}

static void SetCompStringColor()
{
    // change color setting according to current IME level.
    DWORD dwTranslucency = ( g_dwIMELevel == 2 ) ? 0xff000000 : ( ( DWORD )gSkinIME.compTranslucence << 24 );
    gSkinCompStr.colorInput = dwTranslucency | gSkinIME.compColorInput;
    gSkinCompStr.colorTargetConv = dwTranslucency | gSkinIME.compColorTargetConv;
    gSkinCompStr.colorConverted = dwTranslucency | gSkinIME.compColorConverted;
    gSkinCompStr.colorTargetNotConv = dwTranslucency | gSkinIME.compColorTargetNotConv;
    gSkinCompStr.colorInputErr = dwTranslucency | gSkinIME.compColorInputErr;
}

static void SetSupportLevel( DWORD dwImeLevel )
{
    if( dwImeLevel < 2 || 3 < dwImeLevel )
        return;
    if( GETPRIMLANG() == LANG_KOREAN )
    {
        dwImeLevel = 3;
    }
    g_dwIMELevel = dwImeLevel;
    // cancel current composition string.
    ImeUi_FinalizeString();
    SetCompStringColor();
}

void ImeUi_SetSupportLevel( DWORD dwImeLevel )
{
    if( !g_bInitialized )
        return;
    g_dwIMELevelSaved = dwImeLevel;
    SetSupportLevel( dwImeLevel );
}

void ImeUi_SetAppearance( const IMEUI_APPEARANCE* pia )
{
    if( !g_bInitialized || NULL == pia )
        return;
    gSkinIME = *pia;
    gSkinIME.symbolColor &= 0xffffff; // mask translucency
    gSkinIME.symbolColorOff &= 0xffffff; // mask translucency
    gSkinIME.symbolColorText &= 0xffffff; // mask translucency
    gSkinIME.compColorInput &= 0xffffff; // mask translucency
    gSkinIME.compColorTargetConv &= 0xffffff; // mask translucency
    gSkinIME.compColorConverted &= 0xffffff; // mask translucency
    gSkinIME.compColorTargetNotConv &= 0xffffff; // mask translucency
    gSkinIME.compColorInputErr &= 0xffffff; // mask translucency
    SetCompStringColor();
}

void ImeUi_GetAppearance( IMEUI_APPEARANCE* pia )
{
    if( g_bInitialized && pia )
    {
        *pia = gSkinIME;
    }
}

static void CheckToggleState()
{
    CheckInputLocale();

    // In Vista, we have to use TSF since few IMM functions don't work as expected.
    // WARNING: Because of timing, g_dwState and g_bChineseIME may not be updated 
    // immediately after the change on IME states by user.
    if( g_bUILessMode )
    {
        return;
    }

    bool bIme = _ImmIsIME( g_hklCurrent ) != 0
        && ( ( 0xF0000000 & ( DWORD )g_hklCurrent ) == 0xE0000000 ); // Hack to detect IME correctly. When IME is running as TIP, ImmIsIME() returns true for CHT US keyboard.
    g_bChineseIME = ( GETPRIMLANG() == LANG_CHINESE ) && bIme;

    HIMC himc;
    if( NULL != ( himc = _ImmGetContext( g_hwndCurr ) ) )
    {
        if( g_bChineseIME )
        {
            DWORD dwConvMode, dwSentMode;
            _ImmGetConversionStatus( himc, &dwConvMode, &dwSentMode );
            g_dwState = ( dwConvMode & IME_CMODE_NATIVE ) ? IMEUI_STATE_ON : IMEUI_STATE_ENGLISH;
        }
        else
        {
            g_dwState = ( bIme && _ImmGetOpenStatus( himc ) != 0 ) ? IMEUI_STATE_ON : IMEUI_STATE_OFF;
        }
        _ImmReleaseContext( g_hwndCurr, himc );
    }
    else
        g_dwState = IMEUI_STATE_OFF;
}

void ImeUi_SetInsertMode( bool bInsert )
{
    if( !g_bInitialized )
        return;
    g_bInsertMode = bInsert;
}

bool ImeUi_GetCaretStatus()
{
    return !g_bInitialized || !g_szCompositionString[0];
}

void ImeUi_SetScreenDimension( UINT width, UINT height )
{
    if( !g_bInitialized )
        return;
    g_screenWidth = width;
    g_screenHeight = height;
}

// this function is used only in brief time in CHT IME handling, so accelerator isn't processed.
static void _PumpMessage()
{
    MSG msg;
    while( PeekMessageA( &msg, NULL, 0, 0, PM_NOREMOVE ) )
    {
        if( !GetMessageA( &msg, NULL, 0, 0 ) )
        {
            PostQuitMessage( msg.wParam );
            return;
        }
        //		if (0 == TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
        TranslateMessage( &msg );
        DispatchMessageA( &msg );
        //		}
    }
}

static void GetReadingWindowOrientation( DWORD dwId )
{
    g_bHorizontalReading = ( g_hklCurrent == _CHS_HKL ) || ( g_hklCurrent == _CHT_HKL_NEW_CHANG_JIE ) || ( dwId == 0 );
    if( !g_bHorizontalReading && IMEID_LANG( dwId ) == LANG_CHT )
    {
        char szRegPath[MAX_PATH];
        HKEY hkey;
        DWORD dwVer = IMEID_VER( dwId );
        StringCchCopyA( szRegPath, COUNTOF(szRegPath), "software\\microsoft\\windows\\currentversion\\" );
        strcat_s( szRegPath, COUNTOF(szRegPath), ( dwVer >= MAKEIMEVERSION(5, 1) ) ? "MSTCIPH" : "TINTLGNT" );
        LONG lRc = RegOpenKeyExA( HKEY_CURRENT_USER, szRegPath, 0, KEY_READ, &hkey );
        if( lRc == ERROR_SUCCESS )
        {
            DWORD dwSize = sizeof( DWORD ), dwMapping, dwType;
            lRc = RegQueryValueExA( hkey, "keyboard mapping", NULL, &dwType, ( PBYTE )&dwMapping, &dwSize );
            if( lRc == ERROR_SUCCESS )
            {
                if(
                    ( dwVer <= MAKEIMEVERSION( 5, 0 ) &&
                      ( ( BYTE )dwMapping == 0x22 || ( BYTE )dwMapping == 0x23 )
                      ) ||
                    ( ( dwVer == MAKEIMEVERSION( 5, 1 ) || dwVer == MAKEIMEVERSION( 5, 2 ) ) &&
                      ( ( BYTE )dwMapping >= 0x22 && ( BYTE )dwMapping <= 0x24 )
                      )
                    )
                {
                    g_bHorizontalReading = true;
                }
            }
            RegCloseKey( hkey );
        }
    }
}

void ImeUi_ToggleLanguageBar( BOOL bRestore )
{
    static BOOL prevRestore = TRUE;
    bool bCheck = ( prevRestore == TRUE || bRestore == TRUE );
    prevRestore = bRestore;
    if( !bCheck )
        return;

    static int iShowStatusWindow = -1;
    if( iShowStatusWindow == -1 )
    {
        iShowStatusWindow = IsNT() && g_osi.dwMajorVersion >= 5 &&
            ( g_osi.dwMinorVersion > 1 || ( g_osi.dwMinorVersion == 1 && lstrlenA( g_osi.szCSDVersion ) ) ) ? 1 : 0;
    }
    HWND hwndImeDef = _ImmGetDefaultIMEWnd( g_hwndCurr );
    if( hwndImeDef && bRestore && iShowStatusWindow )
        SendMessageA( hwndImeDef, WM_IME_CONTROL, IMC_OPENSTATUSWINDOW, 0 );
    HRESULT hr;
    hr = CoInitialize( NULL );
    if( SUCCEEDED( hr ) )
    {
        ITfLangBarMgr* plbm = NULL;
        hr = CoCreateInstance( CLSID_TF_LangBarMgr, NULL, CLSCTX_INPROC_SERVER, __uuidof( ITfLangBarMgr ),
                               ( void** )&plbm );
        if( SUCCEEDED( hr ) && plbm )
        {
            DWORD dwCur;
            ULONG uRc;
            if( SUCCEEDED( hr ) )
            {
                if( bRestore )
                {
                    if( g_dwPrevFloat )
                        hr = plbm->ShowFloating( g_dwPrevFloat );
                }
                else
                {
                    hr = plbm->GetShowFloatingStatus( &dwCur );
                    if( SUCCEEDED( hr ) )
                        g_dwPrevFloat = dwCur;
                    if( !( g_dwPrevFloat & TF_SFT_DESKBAND ) )
                    {
                        hr = plbm->ShowFloating( TF_SFT_HIDDEN );
                    }
                }
            }
            uRc = plbm->Release();
        }
        CoUninitialize();
    }
    if( hwndImeDef && !bRestore )
    {
        // The following OPENSTATUSWINDOW is required to hide ATOK16 toolbar (FS9:#7546)
        SendMessageA( hwndImeDef, WM_IME_CONTROL, IMC_OPENSTATUSWINDOW, 0 );
        SendMessageA( hwndImeDef, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0 );
    }
}

bool ImeUi_IsSendingKeyMessage()
{
    return bIsSendingKeyMessage;
}

static void OnInputLangChangeWorker()
{
    if( !g_bUILessMode )
    {
        g_iCandListIndexBase = ( g_hklCurrent == _CHT_HKL_DAYI ) ? 0 : 1;
    }
    SetImeApi();
}

static void OnInputLangChange()
{
    UINT uLang = GETPRIMLANG();
    CheckToggleState();
    OnInputLangChangeWorker();
    if( uLang != GETPRIMLANG() )
    {
        // Korean IME always uses level 3 support.
        // Other languages use the level that is specified by ImeUi_SetSupportLevel()
        SetSupportLevel( ( GETPRIMLANG() == LANG_KOREAN ) ? 3 : g_dwIMELevelSaved );
    }
    HWND hwndImeDef = _ImmGetDefaultIMEWnd( g_hwndCurr );
    if( hwndImeDef )
    {
        // Fix for Zooty #3995: prevent CHT IME toobar from showing up
        SendMessageA( hwndImeDef, WM_IME_CONTROL, IMC_OPENSTATUSWINDOW, 0 );
        SendMessageA( hwndImeDef, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0 );
    }
}

static void SetImeApi()
{
    _GetReadingString = NULL;
    _ShowReadingWindow = NULL;
    if( g_bUILessMode )
        return;

    char szImeFile[MAX_PATH + 1];
    HKL kl = g_hklCurrent;
    if( _ImmGetIMEFileNameA( kl, szImeFile, sizeof( szImeFile ) - 1 ) <= 0 )
        return;
    HMODULE hIme = LoadLibraryA( szImeFile );
    if( !hIme )
        return;
    _GetReadingString = ( UINT ( WINAPI* )( HIMC, UINT, LPWSTR, PINT, BOOL*, PUINT ) )
        ( GetProcAddress( hIme, "GetReadingString" ) );
    _ShowReadingWindow = ( BOOL ( WINAPI* )( HIMC himc, BOOL ) )
        ( GetProcAddress( hIme, "ShowReadingWindow" ) );
    if( _ShowReadingWindow )
    {
        HIMC himc;
        if( NULL != ( himc = _ImmGetContext( g_hwndCurr ) ) )
        {
            _ShowReadingWindow( himc, false );
            _ImmReleaseContext( g_hwndCurr, himc );
        }
    }
}

static void CheckInputLocale()
{
    static HKL hklPrev = 0;
    g_hklCurrent = GetKeyboardLayout( 0 );
    if( hklPrev == g_hklCurrent )
    {
        return;
    }
    hklPrev = g_hklCurrent;
    switch( GETPRIMLANG() )
    {
            // Simplified Chinese
        case LANG_CHINESE:
            g_bVerticalCand = true;
            switch( GETSUBLANG() )
            {
                case SUBLANG_CHINESE_SIMPLIFIED:
                    g_pszIndicatior = g_aszIndicator[INDICATOR_CHS];
                    //g_bVerticalCand = GetImeId() == 0;
                    g_bVerticalCand = false;
                    break;
                case SUBLANG_CHINESE_TRADITIONAL:
                    g_pszIndicatior = g_aszIndicator[INDICATOR_CHT];
                    break;
                default:	// unsupported sub-language
                    g_pszIndicatior = g_aszIndicator[INDICATOR_NON_IME];
                    break;
            }
            break;
            // Korean
        case LANG_KOREAN:
            g_pszIndicatior = g_aszIndicator[INDICATOR_KOREAN];
            g_bVerticalCand = false;
            break;
            // Japanese
        case LANG_JAPANESE:
            g_pszIndicatior = g_aszIndicator[INDICATOR_JAPANESE];
            g_bVerticalCand = true;
            break;		
        default:
            g_pszIndicatior = g_aszIndicator[INDICATOR_NON_IME];
    }
    char szCodePage[8];
    int iRc = GetLocaleInfoA( MAKELCID( GETLANG(), SORT_DEFAULT ), LOCALE_IDEFAULTANSICODEPAGE, szCodePage,
                              COUNTOF( szCodePage ) ); iRc;
    g_uCodePage = _strtoul( szCodePage, NULL, 0 );
    for( int i = 0; i < 256; i++ )
    {
        LeadByteTable[i] = ( BYTE )IsDBCSLeadByteEx( g_uCodePage, ( BYTE )i );
    }
}

void ImeUi_SetWindow( HWND hwnd )
{
    g_hwndCurr = hwnd;
    g_disableCicero.DisableCiceroOnThisWnd( hwnd );
}

UINT ImeUi_GetInputCodePage()
{
    return g_uCodePage;
}

DWORD ImeUi_GetFlags()
{
    return g_dwImeUiFlags;
}

void ImeUi_SetFlags( DWORD dwFlags, bool bSet )
{
    if( bSet )
    {
        g_dwImeUiFlags |= dwFlags;
    }
    else
    {
        g_dwImeUiFlags &= ~dwFlags;
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//	CTsfUiLessMode methods
//
///////////////////////////////////////////////////////////////////////////////

//
//	SetupSinks()
//	Set up sinks. A sink is used to receive a Text Service Framework event.
//  CUIElementSink implements multiple sink interfaces to receive few different TSF events.
//
BOOL CTsfUiLessMode::SetupSinks()
{
    // ITfThreadMgrEx is available on Vista or later.
    HRESULT hr;
    hr = CoCreateInstance( CLSID_TF_ThreadMgr,
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           __uuidof( ITfThreadMgrEx ),
                           ( void** )&m_tm );

    if( hr != S_OK )
    {
        return FALSE;
    }

    // ready to start interacting
    TfClientId cid;	// not used
    if( FAILED( m_tm->ActivateEx( &cid, TF_TMAE_UIELEMENTENABLEDONLY ) ) )
    {
        return FALSE;
    }

    // Setup sinks
    BOOL bRc = FALSE;
    m_TsfSink = new CUIElementSink();
    if( m_TsfSink )
    {
        ITfSource* srcTm;
        if( SUCCEEDED( hr = m_tm->QueryInterface( __uuidof( ITfSource ), ( void** )&srcTm ) ) )
        {
            // Sink for reading window change
            if( SUCCEEDED( hr = srcTm->AdviseSink( __uuidof( ITfUIElementSink ), ( ITfUIElementSink* )m_TsfSink,
                                                   &m_dwUIElementSinkCookie ) ) )
            {
                // Sink for input locale change
                if( SUCCEEDED( hr = srcTm->AdviseSink( __uuidof( ITfInputProcessorProfileActivationSink ),
                                                       ( ITfInputProcessorProfileActivationSink* )m_TsfSink,
                                                       &m_dwAlpnSinkCookie ) ) )
                {
                    if( SetupCompartmentSinks() )	// Setup compartment sinks for the first time
                    {
                        bRc = TRUE;
                    }
                }
            }
            srcTm->Release();
        }
    }
    return bRc;
}

void CTsfUiLessMode::ReleaseSinks()
{
    HRESULT hr;
    ITfSource* source;

    // Remove all sinks
    if( m_tm && SUCCEEDED( m_tm->QueryInterface( __uuidof( ITfSource ), ( void** )&source ) ) )
    {
        hr = source->UnadviseSink( m_dwUIElementSinkCookie );
        hr = source->UnadviseSink( m_dwAlpnSinkCookie );
        source->Release();
        SetupCompartmentSinks( TRUE );	// Remove all compartment sinks
        m_tm->Deactivate();
        SAFE_RELEASE( m_tm );
        SAFE_RELEASE( m_TsfSink );
    }	
}

CTsfUiLessMode::CUIElementSink::CUIElementSink()
{
    _cRef = 1;
}


CTsfUiLessMode::CUIElementSink::~CUIElementSink()
{
}

STDAPI CTsfUiLessMode::CUIElementSink::QueryInterface( REFIID riid, void** ppvObj )
{
    if( ppvObj == NULL )
        return E_INVALIDARG;

    *ppvObj = NULL;

    if( IsEqualIID( riid, IID_IUnknown ) )
    {
        *ppvObj = reinterpret_cast<IUnknown*>( this );
    }
    else if( IsEqualIID( riid, __uuidof( ITfUIElementSink ) ) )
    {
        *ppvObj = ( ITfUIElementSink* )this;
    }
    else if( IsEqualIID( riid, __uuidof( ITfInputProcessorProfileActivationSink ) ) )
    {
        *ppvObj = ( ITfInputProcessorProfileActivationSink* )this;
    }
    else if( IsEqualIID( riid, __uuidof( ITfCompartmentEventSink ) ) )
    {
        *ppvObj = ( ITfCompartmentEventSink* )this;
    }

    if( *ppvObj )
    {
        AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

STDAPI_( ULONG )
CTsfUiLessMode::CUIElementSink::AddRef()
{
    return ++_cRef;
}

STDAPI_( ULONG )
CTsfUiLessMode::CUIElementSink::Release()
{
    LONG cr = --_cRef;

    if( _cRef == 0 )
    {
        delete this;
    }

    return cr;
}

STDAPI CTsfUiLessMode::CUIElementSink::BeginUIElement( DWORD dwUIElementId, BOOL* pbShow )
{
    ITfUIElement* pElement = GetUIElement( dwUIElementId );
    if( !pElement )
        return E_INVALIDARG;

    ITfReadingInformationUIElement* preading = NULL;
    ITfCandidateListUIElement* pcandidate = NULL;
    *pbShow = FALSE;
    if( !g_bCandList && SUCCEEDED( pElement->QueryInterface( __uuidof( ITfReadingInformationUIElement ),
                                                             ( void** )&preading ) ) )
    {
        MakeReadingInformationString( preading );
        preading->Release();
    }
    else if( SUCCEEDED( pElement->QueryInterface( __uuidof( ITfCandidateListUIElement ),
                                                  ( void** )&pcandidate ) ) )
    {
        m_nCandidateRefCount++;
        MakeCandidateStrings( pcandidate );
        pcandidate->Release();
    }

    pElement->Release();
    return S_OK;
}

STDAPI CTsfUiLessMode::CUIElementSink::UpdateUIElement( DWORD dwUIElementId )
{
    ITfUIElement* pElement = GetUIElement( dwUIElementId );
    if( !pElement )
        return E_INVALIDARG;

    ITfReadingInformationUIElement* preading = NULL;
    ITfCandidateListUIElement* pcandidate = NULL;
    if( !g_bCandList && SUCCEEDED( pElement->QueryInterface( __uuidof( ITfReadingInformationUIElement ),
                                                             ( void** )&preading ) ) )
    {
        MakeReadingInformationString( preading );
        preading->Release();
    }
    else if( SUCCEEDED( pElement->QueryInterface( __uuidof( ITfCandidateListUIElement ),
                                                  ( void** )&pcandidate ) ) )
    {
        MakeCandidateStrings( pcandidate );
        pcandidate->Release();
    }

    pElement->Release();
    return S_OK;
}

STDAPI CTsfUiLessMode::CUIElementSink::EndUIElement( DWORD dwUIElementId )
{
    ITfUIElement* pElement = GetUIElement( dwUIElementId );
    if( !pElement )
        return E_INVALIDARG;

    ITfReadingInformationUIElement* preading = NULL;
    if( !g_bCandList && SUCCEEDED( pElement->QueryInterface( __uuidof( ITfReadingInformationUIElement ),
                                                             ( void** )&preading ) ) )
    {
        g_dwCount = 0;
        preading->Release();
    }

    ITfCandidateListUIElement* pcandidate = NULL;
    if( SUCCEEDED( pElement->QueryInterface( __uuidof( ITfCandidateListUIElement ),
                                             ( void** )&pcandidate ) ) )
    {
        m_nCandidateRefCount--;
        if( m_nCandidateRefCount == 0 )
            CloseCandidateList();
        pcandidate->Release();
    }

    pElement->Release();
    return S_OK;
}

void CTsfUiLessMode::UpdateImeState( BOOL bResetCompartmentEventSink )
{
    ITfCompartmentMgr* pcm;
    ITfCompartment* pTfOpenMode = NULL;
    ITfCompartment* pTfConvMode = NULL;
    if( GetCompartments( &pcm, &pTfOpenMode, &pTfConvMode ) )
    {
        VARIANT valOpenMode;
        VARIANT valConvMode;
        pTfOpenMode->GetValue( &valOpenMode );
        pTfConvMode->GetValue( &valConvMode );
        if( valOpenMode.vt == VT_I4 )
        {
            if( g_bChineseIME )
            {
                g_dwState = valOpenMode.lVal != 0 && valConvMode.lVal != 0 ? IMEUI_STATE_ON : IMEUI_STATE_ENGLISH;
            }
            else
            {
                g_dwState = valOpenMode.lVal != 0 ? IMEUI_STATE_ON : IMEUI_STATE_OFF;
            }
        }
        VariantClear( &valOpenMode );
        VariantClear( &valConvMode );

        if( bResetCompartmentEventSink )
        {
            SetupCompartmentSinks( FALSE, pTfOpenMode, pTfConvMode );	// Reset compartment sinks
        }
        pTfOpenMode->Release();
        pTfConvMode->Release();
        pcm->Release();
    }
}

STDAPI CTsfUiLessMode::CUIElementSink::OnActivated( DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid,
                                                    REFGUID guidProfile, HKL hkl, DWORD dwFlags )
{
    static GUID TF_PROFILE_DAYI =
    {
        0x037B2C25, 0x480C, 0x4D7F, 0xB0, 0x27, 0xD6, 0xCA, 0x6B, 0x69, 0x78, 0x8A
    };
    g_iCandListIndexBase = IsEqualGUID( TF_PROFILE_DAYI, guidProfile ) ? 0 : 1;
    if( IsEqualIID( catid, GUID_TFCAT_TIP_KEYBOARD ) && ( dwFlags & TF_IPSINK_FLAG_ACTIVE ) )
    {
        g_bChineseIME = ( dwProfileType & TF_PROFILETYPE_INPUTPROCESSOR ) && langid == LANG_CHT;
        if( dwProfileType & TF_PROFILETYPE_INPUTPROCESSOR )
        {
            UpdateImeState( TRUE );
        }
        else
            g_dwState = IMEUI_STATE_OFF;
        OnInputLangChange();
    }
    return S_OK;
}

STDAPI CTsfUiLessMode::CUIElementSink::OnChange( REFGUID rguid )
{
    UpdateImeState();
    return S_OK;
}

void CTsfUiLessMode::MakeReadingInformationString( ITfReadingInformationUIElement* preading )
{
    UINT cchMax;
    UINT uErrorIndex = 0;
    BOOL fVertical;
    DWORD dwFlags;

    preading->GetUpdatedFlags( &dwFlags );
    preading->GetMaxReadingStringLength( &cchMax );
    preading->GetErrorIndex( &uErrorIndex );	// errorIndex is zero-based
    preading->IsVerticalOrderPreferred( &fVertical );
    g_iReadingError = ( int )uErrorIndex;
    g_bHorizontalReading = !fVertical;
    g_bReadingWindow = true;
    g_uCandPageSize = MAX_CANDLIST;
    g_dwSelection = g_iReadingError ? g_iReadingError - 1 : ( DWORD )-1;
    g_iReadingError--;	// g_iReadingError is used only in horizontal window, and has to be -1 if there's no error.
#ifndef UNICODE
    if( g_iReadingError > 0 )
    {
        // convert g_iReadingError to byte based
        LPCSTR pszNext = g_szReadingString;
        for( int i = 0; i < g_iReadingError && pszNext && *pszNext; ++i )
        {
            pszNext = CharNext( pszNext );
        }
        if( pszNext )	// should be non-NULL, but just in case
        {
            g_iReadingError = pszNext - g_szReadingString;
        }
    }
#endif

    BSTR bstr;
    if( SUCCEEDED( preading->GetString( &bstr ) ) )
    {
        if( bstr )
        {
#ifndef UNICODE
            char szStr[COUNTOF(g_szReadingString)*2];
            szStr[0] = 0;
            int iRc = WideCharToMultiByte( CP_ACP, 0, bstr, -1, szStr, sizeof( szStr ), NULL, NULL );
            if( iRc >= sizeof( szStr ) )
            {
                szStr[sizeof( szStr ) - 1] = 0;
            }
            StringCchCopy( g_szReadingString, COUNTOF(g_szReadingString), szStr );
#else
			StringCchCopy( g_szReadingString, COUNTOF(g_szReadingString), bstr );
#endif
            g_dwCount = cchMax;
            LPCTSTR pszSource = g_szReadingString;
            if( fVertical )
            {
                // for vertical reading window, copy each character to g_szCandidate array.
                for( UINT i = 0; i < cchMax; i++ )
                {
                    LPTSTR pszDest = g_szCandidate[i];
                    if( *pszSource )
                    {
                        LPTSTR pszNextSrc = CharNext( pszSource );
                        SIZE_T size = ( LPSTR )pszNextSrc - ( LPSTR )pszSource;
                        CopyMemory( pszDest, pszSource, size );
                        pszSource = pszNextSrc;
                        pszDest += size;
                    }
                    *pszDest = 0;
                }
            }
            else
            {
                g_szCandidate[0][0] = TEXT( ' ' );	// hack to make rendering happen
            }
            SysFreeString( bstr );
        }
    }
}

void CTsfUiLessMode::MakeCandidateStrings( ITfCandidateListUIElement* pcandidate )
{
    UINT uIndex = 0;
    UINT uCount = 0;
    UINT uCurrentPage = 0;
    UINT* IndexList = NULL;
    UINT uPageCnt = 0;
    DWORD dwPageStart = 0;
    DWORD dwPageSize = 0;
    BSTR bstr;

    pcandidate->GetSelection( &uIndex );
    pcandidate->GetCount( &uCount );
    pcandidate->GetCurrentPage( &uCurrentPage );
    g_dwSelection = ( DWORD )uIndex;
    g_dwCount = ( DWORD )uCount;
    g_bCandList = true;
    g_bReadingWindow = false;

    pcandidate->GetPageIndex( NULL, 0, &uPageCnt );
    if( uPageCnt > 0 )
    {
        IndexList = ( UINT* )ImeUiCallback_Malloc( sizeof( UINT ) * uPageCnt );
        if( IndexList )
        {
            pcandidate->GetPageIndex( IndexList, uPageCnt, &uPageCnt );
            dwPageStart = IndexList[uCurrentPage];
            dwPageSize = ( uCurrentPage < uPageCnt - 1 ) ?
                min( uCount, IndexList[uCurrentPage + 1] ) - dwPageStart:
                uCount - dwPageStart;
        }
    }

    g_uCandPageSize = min( dwPageSize, MAX_CANDLIST );
    g_dwSelection = g_dwSelection - dwPageStart;

    memset( &g_szCandidate, 0, sizeof( g_szCandidate ) );
    for( UINT i = dwPageStart, j = 0; ( DWORD )i < g_dwCount && j < g_uCandPageSize; i++, j++ )
    {
        if( SUCCEEDED( pcandidate->GetString( i, &bstr ) ) )
        {
            if( bstr )
            {
#ifndef UNICODE
                char szStr[COUNTOF(g_szCandidate[0])*2];
                szStr[0] = 0;
                int iRc = WideCharToMultiByte( CP_ACP, 0, bstr, -1, szStr, sizeof( szStr ), NULL, NULL );
                if( iRc >= sizeof( szStr ) )
                {
                    szStr[sizeof( szStr ) - 1] = 0;
                }
                ComposeCandidateLine( j, szStr );
#else
				ComposeCandidateLine( j, bstr );
#endif
                SysFreeString( bstr );
            }
        }
    }

    if( GETPRIMLANG() == LANG_KOREAN )
    {
        g_dwSelection = ( DWORD )-1;
    }

    if( IndexList )
    {
        ImeUiCallback_Free( IndexList );
    }
}

ITfUIElement* CTsfUiLessMode::GetUIElement( DWORD dwUIElementId )
{
    ITfUIElementMgr* puiem;
    ITfUIElement* pElement = NULL;

    if( SUCCEEDED( m_tm->QueryInterface( __uuidof( ITfUIElementMgr ), ( void** )&puiem ) ) )
    {
        puiem->GetUIElement( dwUIElementId, &pElement );
        puiem->Release();
    }

    return pElement;
}

BOOL CTsfUiLessMode::CurrentInputLocaleIsIme()
{
    BOOL ret = FALSE;
    HRESULT hr;

    ITfInputProcessorProfiles* pProfiles;
    hr = CoCreateInstance( CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER,
                           __uuidof( ITfInputProcessorProfiles ), ( LPVOID* )&pProfiles );
    if( SUCCEEDED( hr ) )
    {
        ITfInputProcessorProfileMgr* pProfileMgr;
        hr = pProfiles->QueryInterface( __uuidof( ITfInputProcessorProfileMgr ), ( LPVOID* )&pProfileMgr );
        if( SUCCEEDED( hr ) )
        {
            TF_INPUTPROCESSORPROFILE tip;
            hr = pProfileMgr->GetActiveProfile( GUID_TFCAT_TIP_KEYBOARD, &tip );
            if( SUCCEEDED( hr ) )
            {
                ret = ( tip.dwProfileType & TF_PROFILETYPE_INPUTPROCESSOR ) != 0;
            }
            pProfileMgr->Release();
        }
        pProfiles->Release();
    }
    return ret;
}

// Sets up or removes sink for UI element. 
// UI element sink should be removed when IME is disabled,
// otherwise the sink can be triggered when a game has multiple instances of IME UI library.
void CTsfUiLessMode::EnableUiUpdates( bool bEnable )
{
    if( m_tm == NULL ||
        ( bEnable && m_dwUIElementSinkCookie != TF_INVALID_COOKIE ) ||
        ( !bEnable && m_dwUIElementSinkCookie == TF_INVALID_COOKIE ) )
    {
        return;
    }
    ITfSource* srcTm = NULL;
    HRESULT hr = E_FAIL;
    if( SUCCEEDED( hr = m_tm->QueryInterface( __uuidof( ITfSource ), ( void** )&srcTm ) ) )
    {
        if( bEnable )
        {
            hr = srcTm->AdviseSink( __uuidof( ITfUIElementSink ), ( ITfUIElementSink* )m_TsfSink,
                                    &m_dwUIElementSinkCookie );
        }
        else
        {
            hr = srcTm->UnadviseSink( m_dwUIElementSinkCookie );
            m_dwUIElementSinkCookie = TF_INVALID_COOKIE;
        }
        srcTm->Release();
    }
}

// Returns open mode compartments and compartment manager.
// Function fails if it fails to acquire any of the objects to be returned.
BOOL CTsfUiLessMode::GetCompartments( ITfCompartmentMgr** ppcm, ITfCompartment** ppTfOpenMode,
                                      ITfCompartment** ppTfConvMode )
{
    ITfCompartmentMgr* pcm = NULL;
    ITfCompartment* pTfOpenMode = NULL;
    ITfCompartment* pTfConvMode = NULL;

    static GUID _GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION =
    {
        0xCCF05DD8, 0x4A87, 0x11D7, 0xA6, 0xE2, 0x00, 0x06, 0x5B, 0x84, 0x43, 0x5C
    };

    HRESULT hr;
    if( SUCCEEDED( hr = m_tm->QueryInterface( IID_ITfCompartmentMgr, ( void** )&pcm ) ) )
    {
        if( SUCCEEDED( hr = pcm->GetCompartment( GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, &pTfOpenMode ) ) )
        {
            if( SUCCEEDED( hr = pcm->GetCompartment( _GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION,
                                                     &pTfConvMode ) ) )
            {
                *ppcm = pcm;
                *ppTfOpenMode = pTfOpenMode;
                *ppTfConvMode = pTfConvMode;
                return TRUE;
            }
            pTfOpenMode->Release();
        }
        pcm->Release();
    }
    return FALSE;
}

// There are three ways to call this function:
// SetupCompartmentSinks() : initialization
// SetupCompartmentSinks(FALSE, openmode, convmode) : Resetting sinks. This is necessary as DaYi and Array IME resets compartment on switching input locale
// SetupCompartmentSinks(TRUE) : clean up sinks
BOOL CTsfUiLessMode::SetupCompartmentSinks( BOOL bRemoveOnly, ITfCompartment* pTfOpenMode,
                                            ITfCompartment* pTfConvMode )
{
    bool bLocalCompartments = false;
    ITfCompartmentMgr* pcm = NULL;
    BOOL bRc = FALSE;
    HRESULT hr = E_FAIL;

    if( !pTfOpenMode && !pTfConvMode )
    {
        bLocalCompartments = true;
        GetCompartments( &pcm, &pTfOpenMode, &pTfConvMode );
    }
    if( !( pTfOpenMode && pTfConvMode ) )
    {
        // Invalid parameters or GetCompartments() has failed.
        return FALSE;
    }
    ITfSource* srcOpenMode = NULL;
    if( SUCCEEDED( hr = pTfOpenMode->QueryInterface( IID_ITfSource, ( void** )&srcOpenMode ) ) )
    {
        // Remove existing sink for open mode
        if( m_dwOpenModeSinkCookie != TF_INVALID_COOKIE )
        {
            srcOpenMode->UnadviseSink( m_dwOpenModeSinkCookie );
            m_dwOpenModeSinkCookie = TF_INVALID_COOKIE;
        }
        // Setup sink for open mode (toggle state) change
        if( bRemoveOnly || SUCCEEDED( hr = srcOpenMode->AdviseSink( IID_ITfCompartmentEventSink,
                                                                    ( ITfCompartmentEventSink* )m_TsfSink,
                                                                    &m_dwOpenModeSinkCookie ) ) )
        {
            ITfSource* srcConvMode = NULL;
            if( SUCCEEDED( hr = pTfConvMode->QueryInterface( IID_ITfSource, ( void** )&srcConvMode ) ) )
            {
                // Remove existing sink for open mode
                if( m_dwConvModeSinkCookie != TF_INVALID_COOKIE )
                {
                    srcConvMode->UnadviseSink( m_dwConvModeSinkCookie );
                    m_dwConvModeSinkCookie = TF_INVALID_COOKIE;
                }
                // Setup sink for open mode (toggle state) change
                if( bRemoveOnly || SUCCEEDED( hr = srcConvMode->AdviseSink( IID_ITfCompartmentEventSink,
                                                                            ( ITfCompartmentEventSink* )m_TsfSink,
                                                                            &m_dwConvModeSinkCookie ) ) )
                {
                    bRc = TRUE;
                }
                srcConvMode->Release();
            }
        }
        srcOpenMode->Release();
    }
    if( bLocalCompartments )
    {
        pTfOpenMode->Release();
        pTfConvMode->Release();
        pcm->Release();
    }
    return bRc;
}


WORD ImeUi_GetPrimaryLanguage()
{	
    return GETPRIMLANG();
};

DWORD ImeUi_GetImeId( UINT uIndex )
{
    return GetImeId( uIndex );
};

WORD ImeUi_GetLanguage()
{
    return GETLANG();
};

PTSTR ImeUi_GetIndicatior()
{
    return g_pszIndicatior;
};


bool ImeUi_IsShowReadingWindow()
{
    return g_bReadingWindow;
};

bool ImeUi_IsShowCandListWindow()
{
    return g_bCandList;
};

bool ImeUi_IsVerticalCand()
{
    return g_bVerticalCand;
};

bool ImeUi_IsHorizontalReading()
{
    return g_bHorizontalReading;
};

TCHAR* ImeUi_GetCandidate( UINT idx )
{
    if( idx < MAX_CANDLIST )
        return g_szCandidate[idx];
    else
        return g_szCandidate[0];
}

DWORD ImeUi_GetCandidateSelection()
{
    return g_dwSelection;
}

DWORD ImeUi_GetCandidateCount()
{
    return g_dwCount;
}

TCHAR* ImeUi_GetCompositionString()
{
    return g_szCompositionString;
}

BYTE* ImeUi_GetCompStringAttr()
{
    return  g_szCompAttrString;
}

DWORD ImeUi_GetImeCursorChars()
{
    return g_IMECursorChars;
}

