diff options
| author | Dave Clark <[email protected]> | 2018-02-28 17:22:22 -0500 |
|---|---|---|
| committer | Dave Clark <[email protected]> | 2018-02-28 17:22:22 -0500 |
| commit | 25528fd230f5f4298c35123a833cdb112675808e (patch) | |
| tree | f5aca3f5ee5a7734df41e7b974a04c37ddff528e /samples/DX_APIUsage/DXUT/Optional/ImeUi.cpp | |
| parent | Push GfeSDK #173 (diff) | |
| download | gfesdk-25528fd230f5f4298c35123a833cdb112675808e.tar.xz gfesdk-25528fd230f5f4298c35123a833cdb112675808e.zip | |
Push SDK # 1.1.186
Documentation updates.
Diffstat (limited to 'samples/DX_APIUsage/DXUT/Optional/ImeUi.cpp')
| -rw-r--r-- | samples/DX_APIUsage/DXUT/Optional/ImeUi.cpp | 3662 |
1 files changed, 3662 insertions, 0 deletions
diff --git a/samples/DX_APIUsage/DXUT/Optional/ImeUi.cpp b/samples/DX_APIUsage/DXUT/Optional/ImeUi.cpp new file mode 100644 index 0000000..f7a0cb2 --- /dev/null +++ b/samples/DX_APIUsage/DXUT/Optional/ImeUi.cpp @@ -0,0 +1,3662 @@ +//-------------------------------------------------------------------------------------- +// 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; +} + |