This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/wwui/IMEManager.cpp

1857 lines
39 KiB
C++

/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/******************************************************************************
*
* FILE
* $Archive: /Commando/Code/wwui/IMEManager.cpp $
*
* DESCRIPTION
* Input Method Editor Manager for input of far east characters.
*
* PROGRAMMER
* $Author: Denzil_l $
*
* VERSION INFO
* $Revision: 6 $
* $Modtime: 1/12/02 9:44p $
*
******************************************************************************/
#include "IMEManager.h"
#include "WWString.h"
#include "WWDebug.h"
#include <locale.h>
#include <mbctype.h>
#if defined(_MSC_VER)
#pragma warning(push, 3)
#endif
#include <algorithm>
#include <memory>
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#pragma comment(lib, "imm32.lib")
namespace IME {
/******************************************************************************
*
* NAME
* IMEManager::Create
*
* DESCRIPTION
* Create an IME manager instance
*
* INPUTS
* Window - HWND to associate IME manager instance with.
*
* RESULT
* IMEManager - Pointer to IME manager
*
******************************************************************************/
IMEManager* IMEManager::Create(HWND hwnd)
{
if (hwnd)
{
IMEManager* ime = new IMEManager;
if (ime)
{
if (ime->FinalizeCreate(hwnd))
{
return ime;
}
ime->Release_Ref();
}
}
return NULL;
}
/******************************************************************************
*
* NAME
* IMEManager::IMEManager
*
* DESCRIPTION
* Constructor
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
IMEManager::IMEManager() :
mHWND(NULL),
mHIMC(NULL),
mDisabledHIMC(NULL),
mDisableCount(0),
mCodePage(CP_ACP),
mIMEProperties(0),
mHilite(true),
mStartCandListFrom1(true),
mOSCanUnicode(false),
mUseUnicode(false),
mInComposition(false)
{
WWDEBUG_SAY(("IMEManager: Instantiated\n"));
mLangID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
ResetComposition();
mResultString[0] = 0;
}
/******************************************************************************
*
* NAME
* IMEManager::~IMEManager
*
* DESCRIPTION
* Destructor
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
IMEManager::~IMEManager()
{
WWDEBUG_SAY(("IMEManager: destroyed\n"));
if (mHIMC)
{
ImmAssociateContext(mHWND, mDefaultHIMC);
ImmDestroyContext(mHIMC);
}
mCandidateColl.clear();
}
/******************************************************************************
*
* NAME
* IMEManager::FinalizeCreate
*
* DESCRIPTION
* Post creation finalization.
*
* INPUTS
* HWND - Window to associate IME context with.
*
* RESULT
* True if successful
*
******************************************************************************/
bool IMEManager::FinalizeCreate(HWND hwnd)
{
if (hwnd == NULL)
{
return false;
}
mHWND = hwnd;
// Check the OS version, if Win98 or better then we can use unicode
OSVERSIONINFO osvi;
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
bool isWin98orLater = (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && ((osvi.dwMajorVersion > 4) || ((osvi.dwMajorVersion == 4) && (osvi.dwMinorVersion >= 10)));
bool isNT4orLater = (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) && ((osvi.dwMajorVersion > 4) || ((osvi.dwMajorVersion == 4) && (osvi.dwMinorVersion >= 0)));
mOSCanUnicode = (isWin98orLater || isNT4orLater);
// Create new input context for the specified window.
mHIMC = ImmCreateContext();
if (mHIMC == NULL)
{
return false;
}
// Associate the new context with this window.
mDefaultHIMC = ImmAssociateContext(mHWND, mHIMC);
// Set the language for the current keyboard layout.
InputLanguageChanged(GetKeyboardLayout(0));
mCandidateColl.resize(32);
return true;
}
/******************************************************************************
*
* NAME
* IMEManager::Activate
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMEManager::Activate(void)
{
WWDEBUG_SAY(("IMEManager: Activate\n"));
HIMC imc = ImmGetContext(mHWND);
if (imc)
{
BOOL open = ImmGetOpenStatus(imc);
if (!open)
{
ImmSetOpenStatus(imc, TRUE);
IMEEvent action(IME_ACTIVATED, this);
NotifyObservers(action);
}
ImmReleaseContext(mHWND, imc);
}
}
/******************************************************************************
*
* NAME
* IMEManager::Deactivate
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMEManager::Deactivate(void)
{
WWDEBUG_SAY(("IMEManager: Deactivate\n"));
HIMC imc = ImmGetContext(mHWND);
if (imc)
{
BOOL open = ImmGetOpenStatus(imc);
if (open)
{
ImmSetOpenStatus(imc, FALSE);
IMEEvent action(IME_DEACTIVATED, this);
NotifyObservers(action);
}
ImmReleaseContext(mHWND, imc);
}
ResetComposition();
}
/******************************************************************************
*
* NAME
* IMEManager::IsActive
*
* DESCRIPTION
* Check if IME is active.
*
* INPUTS
* NONE
*
* RESULT
* True if IME is currently active.
*
******************************************************************************/
bool IMEManager::IsActive(void) const
{
bool isActive = false;
HIMC imc = ImmGetContext(mHWND);
if (imc)
{
BOOL open = ImmGetOpenStatus(imc);
isActive = (open != 0);
ImmReleaseContext(mHWND, imc);
}
return isActive;
}
/******************************************************************************
*
* NAME
* IMEManager::Disable
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMEManager::Disable(void)
{
++mDisableCount;
// If this is the first disable lock the perform the actuall disabling.
if (1 == mDisableCount)
{
WWDEBUG_SAY(("IMEManager: Disabled\n"));
mDisabledHIMC = ImmAssociateContext(mHWND, NULL);
IMEEvent action(IME_DISABLED, this);
NotifyObservers(action);
}
}
/******************************************************************************
*
* NAME
* IMEManager::Enable
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMEManager::Enable(void)
{
if (mDisableCount > 0)
{
--mDisableCount;
// Re-enable when there is no disable locks.
if (0 == mDisableCount)
{
WWDEBUG_SAY(("IMEManager: Enabled\n"));
ImmAssociateContext(mHWND, mDisabledHIMC);
mDisabledHIMC = NULL;
IMEEvent action(IME_ENABLED, this);
NotifyObservers(action);
}
}
}
/******************************************************************************
*
* NAME
* IMEManager::IsDisabled
*
* DESCRIPTION
* Check if IME is turned off.
*
* INPUTS
* NONE
*
* RESULT
* True if IME is currently off.
*
******************************************************************************/
bool IMEManager::IsDisabled(void) const
{
return (mDisableCount > 0);
}
/******************************************************************************
*
* NAME
* IMEManager::ProcessMessage
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* True of message handled and therefore should NOT be passed to the
* default window procedure.
*
******************************************************************************/
bool IMEManager::ProcessMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& outResult)
{
if (hwnd != mHWND)
{
return false;
}
bool handled = true;
outResult = 0;
switch (msg)
{
// Request New keyboard layout and / or Input method
case WM_INPUTLANGCHANGEREQUEST:
{
HKL layout = InputLanguageChangeRequest((HKL)lParam);
if (layout)
{
lParam = (LPARAM)layout;
handled = false;
}
}
break;
// Input language has changed.
case WM_INPUTLANGCHANGE:
InputLanguageChanged((HKL)lParam);
outResult = TRUE;
handled = false;
break;
// Sent when the system is about to change the current IME.
case WM_IME_SELECT:
WWDEBUG_SAY(("IMEManager: WM_IME_SELECT\n"));
break;
// We will handle all of the UI so clear all of the flags.
case WM_IME_SETCONTEXT:
lParam &= ~(ISC_SHOWUIALL);
handled = false;
break;
// Sent when a composition string is about to be generated in response to a
// keystroke. This message informs us to prepare for composition.
case WM_IME_STARTCOMPOSITION:
StartComposition();
break;
// Sent when composition status has changed in response to a keystroke.
case WM_IME_COMPOSITION:
DoComposition(wParam, lParam);
break;
// Sent when composition has closed.
case WM_IME_ENDCOMPOSITION:
EndComposition();
break;
// Sent when unable to extend the composition to accomodate any more characters.
case WM_IME_COMPOSITIONFULL:
{
WWDEBUG_SAY(("IMEManager: WM_IME_COMPOSITIONFULL\n"));
CompositionEvent event(COMPOSITION_FULL, this);
NotifyObservers(event);
}
break;
// Sent when the IME status has changed.
case WM_IME_NOTIFY:
outResult = IMENotify(wParam, lParam);
break;
// IMEs send this message when the user accepts the conversion string.
// wParam contains a single-byte or double-byte character.
case WM_IME_CHAR:
handled = IMECharHandler((unsigned short)wParam);
if (handled)
{
outResult = TRUE;
}
break;
case WM_CHAR:
handled = CharHandler((unsigned short)wParam);
if (handled)
{
outResult = TRUE;
}
break;
case WM_KEYDOWN:
if (mInComposition)
{
outResult = DefWindowProc(hwnd, msg, wParam, lParam);
}
else
{
handled = false;
}
break;
case WM_KEYUP:
if (mInComposition)
{
#ifdef SHOW_IME_TYPING
bool typingChanged = false;
UINT virtualKey = wParam;
if (VK_BACK == virtualKey)
{
if (mTypingCursorPos > 0)
{
--mTypingCursorPos;
mTypingString[mTypingCursorPos] = 0;
typingChanged = true;
}
}
else if ((0x30 <= virtualKey && 0x39 >= virtualKey) || (0x41 <= virtualKey && 0x5A >= virtualKey))
{
if (mTypingCursorPos < IME_MAX_TYPING_LEN)
{
mTypingString[mTypingCursorPos] = (wchar_t)virtualKey;
++mTypingCursorPos;
mTypingString[mTypingCursorPos] = 0;
typingChanged = true;
}
}
if (typingChanged)
{
CompositionEvent event(COMPOSITION_TYPING, this);
NotifyObservers(event);
}
#endif
outResult = DefWindowProc(hwnd, msg, wParam, lParam);
}
else
{
handled = false;
}
break;
case WM_IME_CONTROL:
case WM_IME_KEYDOWN:
case WM_IME_KEYUP:
default:
handled = false;
break;
}
return handled;
}
/******************************************************************************
*
* NAME
* IMEManager::IMENotify
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
LRESULT IMEManager::IMENotify(WPARAM wParam, LPARAM lParam)
{
switch (wParam)
{
// The open status of the input context has changed.
case IMN_SETOPENSTATUS:
WWDEBUG_SAY(("IMEManager: IMN_SETOPENSTATUS\n"));
{
HIMC imc = ImmGetContext(mHWND);
if (imc)
{
IMEAction action = IME_DEACTIVATED;
if (!ImmGetOpenStatus(imc))
{
// If the IME conversion engine is closed, we need to
// erase all currently displayed composition chars and any
// candidate windows.
mCandidateColl.clear();
ResetComposition();
}
else
{
action = IME_ACTIVATED;
}
ImmReleaseContext(mHWND, imc);
IMEEvent event(action, this);
NotifyObservers(event);
}
}
break;
// Open the status window
case IMN_OPENSTATUSWINDOW:
WWDEBUG_SAY(("IMEManager: IMN_OPENSTATUSWINDOW\n"));
break;
// Close the status window
case IMN_CLOSESTATUSWINDOW:
WWDEBUG_SAY(("IMEManager: IMN_CLOSESTATUSWINDOW\n"));
break;
// Update the position of the status window.
case IMN_SETSTATUSWINDOWPOS:
WWDEBUG_SAY(("IMEManager: IMN_SETSTATUSWINDOWPOS\n"));
break;
// The font of the input context has changed.
case IMN_SETCOMPOSITIONFONT:
WWDEBUG_SAY(("IMEManager: IMN_SETCOMPOSITIONFONT\n"));
break;
// The style or position of the composition window has changed.
case IMN_SETCOMPOSITIONWINDOW:
WWDEBUG_SAY(("IMEManager: IMN_SETCOMPOSITIONWINDOW\n"));
break;
// The conversion mode of the input context has changed.
case IMN_SETCONVERSIONMODE:
WWDEBUG_SAY(("IMEManager: IMN_SETCONVERSIONMODE\n"));
#ifdef WWDEBUG
{
HIMC imc = ImmGetContext(mHWND);
if (imc)
{
DWORD conversionMode = 0;
DWORD sentenceMode = 0;
BOOL okay = ImmGetConversionStatus(imc, &conversionMode, &sentenceMode);
if (okay)
{
WWDEBUG_SAY(("IMEManager: ConversionMode - "));
static struct convmodestruct {long flag; const char* ondesc; const char* offdesc;} _convModes[] =
{
{IME_CMODE_CHARCODE, "CharCode:On", "CharCode:Off"},
{IME_CMODE_EUDC, " EUDC:On", " EUDC:Off"},
{IME_CMODE_FIXED, " Fixed:On", " Fixed:Off"},
{IME_CMODE_FULLSHAPE, " FullShape", " HalfShape"},
{IME_CMODE_HANJACONVERT, " Hanja:On", " Hanja:Off"},
{IME_CMODE_KATAKANA, " Katakana", " Hiragana"},
{IME_CMODE_NATIVE, " Native", " Alphanumberic"},
{IME_CMODE_NOCONVERSION, " Conversion:Off", " Conversion:On"},
{IME_CMODE_ROMAN, " Roman:On", " Roman:Off"},
{IME_CMODE_SOFTKBD, " SoftKbd:On", " SoftKbd:Off"},
{IME_CMODE_SYMBOL, " Symbol:On", " Symbol:Off"},
{0, ""}
};
int flgidx = 0;
while (_convModes[flgidx].flag)
{
if (conversionMode & _convModes[flgidx].flag)
{
WWDEBUG_SAY((_convModes[flgidx].ondesc));
}
else
{
WWDEBUG_SAY((_convModes[flgidx].offdesc));
}
flgidx++;
}
WWDEBUG_SAY(("\n"));
}
ImmReleaseContext(mHWND, imc);
}
}
#endif
break;
// The sentence mode has changed.
case IMN_SETSENTENCEMODE:
WWDEBUG_SAY(("IMEManager: IMN_SETSENTENCEMODE\n"));
#ifdef WWDEBUG
{
HIMC imc = ImmGetContext(mHWND);
if (imc)
{
DWORD conversionMode = 0;
DWORD sentenceMode = 0;
BOOL okay = ImmGetConversionStatus(imc, &conversionMode, &sentenceMode);
if (okay)
{
WWDEBUG_SAY(("IMEManager: SentenceMode - "));
static struct smodestruct {long flag; const char* ondesc; const char* offdesc;} _sModes[] =
{
{IME_SMODE_AUTOMATIC, "Automatic:On", "Automatic:Off"},
{IME_SMODE_NONE, " SentenceInfo:Off", " SentenceInfo:On"},
{IME_SMODE_PHRASEPREDICT, " PhrasePredict:On", " PhrasePredict:Off"},
{IME_SMODE_PLAURALCLAUSE, " PluralClause:On", " PluralClause:Off"},
{IME_SMODE_SINGLECONVERT, " SingleConvert:On", " SingleConvert:Off"},
{IME_SMODE_CONVERSATION, " Conversation:On", " Conversation:Off"},
{0, ""}
};
int flgidx = 0;
while (_sModes[flgidx].flag)
{
if (sentenceMode & _sModes[flgidx].flag)
{
WWDEBUG_SAY((_sModes[flgidx].ondesc));
}
else
{
WWDEBUG_SAY((_sModes[flgidx].offdesc));
}
flgidx++;
}
WWDEBUG_SAY(("\n"));
}
ImmReleaseContext(mHWND, imc);
}
}
#endif
break;
// Open the candidate window (lParam = candidate flags)
case IMN_OPENCANDIDATE:
OpenCandidate(lParam);
break;
// Close the candidate window. (lParam = candidate flags)
case IMN_CLOSECANDIDATE:
CloseCandidate(lParam);
break;
// Changing the contents of the candidate window (lParam = candidate flags)
case IMN_CHANGECANDIDATE:
ChangeCandidate(lParam);
break;
// Candidate processing is finished; moving the candidate window
case IMN_SETCANDIDATEPOS:
WWDEBUG_SAY(("IMEManager: IMN_SETCANDIDATEPOS\n"));
break;
// Show error message or other information
case IMN_GUIDELINE:
WWDEBUG_SAY(("IMEManager: IMN_GUIDELINE\n"));
{
IMEEvent event(IME_GUIDELINE, this);
NotifyObservers(event);
}
break;
default:
break;
}
return TRUE;
}
/******************************************************************************
*
* NAME
* IMEManager::InputLanguageChangeRequest
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
HKL IMEManager::InputLanguageChangeRequest(HKL hkl)
{
WWDEBUG_SAY(("IMEManager: Input language change request\n"));
// Get the number of Keyboard layouts available to the system
UINT numLayouts = GetKeyboardLayoutList(0, NULL);
if (numLayouts)
{
// Get the list of layouts
std::vector<HKL> layoutList(numLayouts);
layoutList.resize(numLayouts);
numLayouts = GetKeyboardLayoutList(numLayouts, layoutList.begin());
// Find the position in the list of the layout which has been requested.
std::vector<HKL>::iterator iter = std::find(layoutList.begin(), layoutList.end(), hkl);
if (iter != layoutList.end())
{
// Rotate the list so the requested layout is at the head.
std::rotate(layoutList.begin(), iter, layoutList.end());
// Look for the layout that doesn't have the AT_CARET or SPECIAL_UI properties
iter = layoutList.begin();
while (iter != layoutList.end())
{
DWORD property = ImmGetProperty(*iter, IGP_PROPERTY);
if ((property & (IME_PROP_AT_CARET | IME_PROP_SPECIAL_UI)) == (IME_PROP_AT_CARET | IME_PROP_SPECIAL_UI))
{
iter++;
}
else
{
return *iter;
}
}
}
}
return NULL;
}
/******************************************************************************
*
* NAME
* IMEManager::InputLanguageChanged
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
void IMEManager::InputLanguageChanged(HKL hkl)
{
mLangID = LOWORD(hkl);
DWORD lcid = MAKELCID(mLangID, SORT_DEFAULT);
// Get the default codepage for this input language
char localeData[8];
GetLocaleInfo(lcid, LOCALE_IDEFAULTANSICODEPAGE, localeData, sizeof(localeData));
// Set the codepage for character conversion
mCodePage = atoi(localeData);
// Get properties
mIMEProperties = ImmGetProperty(hkl, IGP_PROPERTY);
mStartCandListFrom1 = ((mIMEProperties & IME_PROP_CANDLIST_START_FROM_1) == IME_PROP_CANDLIST_START_FROM_1);
mUseUnicode = (mOSCanUnicode && (mIMEProperties & IME_PROP_UNICODE));
// Get IME description
if (mUseUnicode)
{
UINT descSize = ImmGetDescriptionW(hkl, NULL, 0);
++descSize;
wchar_t* descPtr = mIMEDescription.Get_Buffer(descSize);
ImmGetDescriptionW(hkl, descPtr, descSize);
}
else
{
UINT descSize = ImmGetDescription(hkl, NULL, 0);
++descSize;
StringClass desc((int)descSize, true);
char* descPtr = desc.Get_Buffer(descSize);
ImmGetDescription(hkl, descPtr, descSize);
mIMEDescription = desc;
}
#if(0)
mHilite = true;
static const wchar_t _TradChImeName[] = {0x6CE8,0x97F3,0x8F38,0x5165,0x6CD5,0x0020,0x0034,0x002E,0x0031,0x0020,0x7248,0x0000};
if (mIMEDescription.Compare(_TradChImeName) == 0)
{
mHilite = false;
}
#endif
WWDEBUG_SAY(("IMEManager: Language Changed - LangID = %04X, CodePage = %d, Description: '%S'\n",
mLangID, mCodePage, mIMEDescription));
WWDEBUG_SAY(("IMEManager: Properties - %s%s%s%s%s\n",
mIMEProperties & IME_PROP_AT_CARET ? "At Caret" : "",
mIMEProperties & IME_PROP_SPECIAL_UI ? ", SpecialUI" : "",
mIMEProperties & IME_PROP_CANDLIST_START_FROM_1 ? ", CandlistFrom1" : "",
mIMEProperties & IME_PROP_UNICODE ? ", Unicode" : "",
mIMEProperties & IME_PROP_COMPLETE_ON_UNSELECT ? ", Complete on Unselect" : ""));
IMEEvent action(IME_LANGUAGECHANGED, this);
NotifyObservers(action);
}
/******************************************************************************
*
* NAME
* IMEManager::ResetComposition
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
void IMEManager::ResetComposition(void)
{
mInComposition = false;
#ifdef SHOW_IME_TYPING
mTypingString[0] = 0;
mTypingCursorPos = 0;
#endif
mCompositionString[0] = 0;
memset(mCompositionAttr, 0, sizeof(mCompositionAttr));
memset(mCompositionClause, 0, sizeof(mCompositionClause));
mCompositionCursorPos = 0;
mReadingString[0] = 0;
}
/******************************************************************************
*
* NAME
* IMEManager::StartComposition
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
void IMEManager::StartComposition(void)
{
WWDEBUG_SAY(("IMEManager: StartComposition\n"));
mInComposition = true;
mResultString[0] = 0;
CompositionEvent event(COMPOSITION_START, this);
NotifyObservers(event);
}
/******************************************************************************
*
* NAME
* IMEManager::DoComposition
*
* DESCRIPTION
* Handle composition message.
*
* INPUTS
*
* RESULT
*
******************************************************************************/
void IMEManager::DoComposition(unsigned int dbcs, long compFlags)
{
WWDEBUG_SAY(("IMEManager: DoComposition\n"));
#if(0)
#ifdef _DEBUG
static struct flagstruct {long flag; const char* desc;} _gcsFlags[] =
{
{GCS_COMPATTR, "GCS_COMPATTR"},
{GCS_COMPCLAUSE, " GCS_COMPCLAUSE"},
{GCS_COMPREADATTR, " GCS_COMPREADATTR"},
{GCS_COMPREADCLAUSE, " GCS_COMPREADCLAUSE"},
{GCS_DELTASTART, " GCS_DELTASTART"},
{GCS_RESULTCLAUSE, " GCS_RESULTCLAUSE"},
{GCS_RESULTREADCLAUSE, " GCS_RESULTREADCLAUSE"},
{GCS_RESULTREADSTR, " GCS_RESULTREADSTR"},
{CS_INSERTCHAR, " CS_INSERTCHAR"},
{CS_NOMOVECARET, " CS_NOMOVECARET"},
{0, ""}
};
WWDEBUG_SAY(("Flags: "));
int flgidx = 0;
while (_gcsFlags[flgidx].flag)
{
if (compFlags & _gcsFlags[flgidx].flag)
{
WWDEBUG_SAY((_gcsFlags[flgidx].desc));
}
flgidx++;
}
WWDEBUG_SAY(("\n"));
#endif
#endif
#ifdef SHOW_IME_TYPING
// Reset the typing string
mTypingString[0] = 0;
mTypingCursorPos = 0;
CompositionEvent event(COMPOSITION_TYPING, this);
NotifyObservers(event);
#endif
HIMC imc = ImmGetContext(mHWND);
if (imc)
{
if (compFlags == 0)
{
ResetComposition();
CompositionEvent event(COMPOSITION_CANCEL, this);
NotifyObservers(event);
}
else if (compFlags & GCS_RESULTSTR)
{
// Retrieve the result string
if (ReadCompositionString(imc, GCS_RESULTSTR, mResultString, sizeof(mResultString)))
{
WWDEBUG_SAY(("Result string '%S'\n", mResultString));
CompositionEvent event(COMPOSITION_RESULT, this);
NotifyObservers(event);
}
}
else
{
CompositionAction action = COMPOSITION_INVALID;
// Update reading string.
if (compFlags & GCS_COMPREADSTR)
{
if (ReadCompositionString(imc, GCS_COMPREADSTR, mReadingString, sizeof(mReadingString)))
{
WWDEBUG_SAY(("Reading string '%S'\n", (const wchar_t*)mReadingString));
action = COMPOSITION_CHANGE;
}
}
#ifdef WWDEBUG
if (compFlags & GCS_COMPREADATTR)
{
unsigned char attr[IME_MAX_STRING_LEN * 2];
long size = ReadReadingAttr(imc, attr, sizeof(attr));
WWDEBUG_SAY(("ReadAttr: "));
for (long index = 0; index < size; ++index)
{
WWDEBUG_SAY(("%01x", (int)attr[index]));
}
WWDEBUG_SAY(("\n"));
}
#endif
// Update composition string.
if (compFlags & GCS_COMPSTR)
{
if (ReadCompositionString(imc, GCS_COMPSTR, mCompositionString, sizeof(mCompositionString)))
{
WWDEBUG_SAY(("Composition string '%S'\n", (const wchar_t*)mCompositionString));
action = COMPOSITION_CHANGE;
}
}
if (compFlags & GCS_COMPATTR)
{
long size = ReadCompositionAttr(imc, mCompositionAttr, sizeof(mCompositionAttr));
#ifdef WWDEBUG
WWDEBUG_SAY(("CompAttr: "));
for (long index = 0; index < size; ++index)
{
WWDEBUG_SAY(("%01x", (int)mCompositionAttr[index]));
}
WWDEBUG_SAY(("\n"));
#endif
}
if (compFlags & GCS_COMPCLAUSE)
{
mCompositionClause[0] = 0;
long size = ReadCompositionClause(imc, mCompositionClause, sizeof(mCompositionClause));
#ifdef WWDEBUG
WWDEBUG_SAY(("CompClause: "));
const int count = (size / sizeof(unsigned long));
for (int index = 0; index < count; ++index)
{
WWDEBUG_SAY(("%u ", mCompositionClause[index]));
}
WWDEBUG_SAY(("\n"));
#endif
}
if (compFlags & GCS_CURSORPOS)
{
mCompositionCursorPos = ReadCursorPos(imc);
action = COMPOSITION_CHANGE;
}
if (action != COMPOSITION_INVALID)
{
CompositionEvent event(action, this);
NotifyObservers(event);
}
}
ImmReleaseContext(mHWND, imc);
}
}
/******************************************************************************
*
* NAME
* IMEManager::EndComposition
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
void IMEManager::EndComposition(void)
{
WWDEBUG_SAY(("IMEManager: EndComposition\n"));
HIMC imc = ImmGetContext(mHWND);
if (imc)
{
ReadCompositionString(imc, GCS_COMPSTR, mResultString, sizeof(mResultString));
ImmReleaseContext(mHWND, imc);
}
ResetComposition();
CompositionEvent event(COMPOSITION_END, this);
NotifyObservers(event);
}
/******************************************************************************
*
* NAME
* IMEManager::ReadCompositionString
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
bool IMEManager::ReadCompositionString(HIMC imc, unsigned long flag, wchar_t* buffer, int length)
{
if (mUseUnicode)
{
LONG size = ImmGetCompositionStringW(imc, flag, buffer, length);
if (size < 0)
{
buffer[0] = 0;
return false;
}
// Terminate string
buffer[(size / sizeof(wchar_t))] = 0;
}
else
{
// Read the string as multibyte ANSI
unsigned char string[IME_MAX_STRING_LEN];
LONG size = ImmGetCompositionString(imc, flag, string, sizeof(string));
if (size < 0)
{
buffer[0] = 0;
return false;
}
// Terminate the string
string[size] = 0;
// Convert to Unicode
MultiByteToWideChar(mCodePage, 0, (const char*)string, -1, buffer, (length / sizeof(wchar_t)));
}
return true;
}
/******************************************************************************
*
* NAME
* IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
long IMEManager::ReadReadingAttr(HIMC imc, unsigned char* attr, int length)
{
if (mUseUnicode)
{
LONG size = ImmGetCompositionStringW(imc, GCS_COMPREADATTR, attr, length);
return (size / sizeof(wchar_t));
}
// Read the string as multibyte ANSI
unsigned char string[IME_MAX_STRING_LEN];
LONG size = ImmGetCompositionString(imc, GCS_COMPREADSTR, string, sizeof(string));
if (size <= 0)
{
return 0;
}
// Terminate the string
string[size] = 0;
LONG attrSize = ImmGetCompositionString(imc, GCS_COMPREADATTR, attr, length);
WWASSERT(size == attrSize);
if (attrSize <= size)
{
return 0;
}
return ConvertAttrForUnicode(string, attr);
}
/******************************************************************************
*
* NAME
* IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
long IMEManager::ReadReadingClause(HIMC imc, unsigned long* clause, int length)
{
if (mUseUnicode)
{
LONG size = ImmGetCompositionStringW(imc, GCS_COMPREADCLAUSE, clause, length);
return (size / sizeof(wchar_t));
}
// Read the string as multibyte ANSI
unsigned char string[IME_MAX_STRING_LEN];
LONG size = ImmGetCompositionString(imc, GCS_COMPREADSTR, string, sizeof(string));
if (size <= 0)
{
return 0;
}
// Terminate the string
string[size] = 0;
LONG clauseSize = ImmGetCompositionString(imc, GCS_COMPREADCLAUSE, clause, length);
if (clauseSize <= 0)
{
return 0;
}
return ConvertClauseForUnicode(string, size, clause);
}
/******************************************************************************
*
* NAME
* IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
long IMEManager::ReadCompositionAttr(HIMC imc, unsigned char* attr, int length)
{
if (mUseUnicode)
{
return ImmGetCompositionStringW(imc, GCS_COMPATTR, attr, length);
}
// Read the string as multibyte ANSI
unsigned char string[IME_MAX_STRING_LEN];
LONG size = ImmGetCompositionString(imc, GCS_COMPSTR, string, sizeof(string));
if (size <= 0)
{
return size;
}
// Terminate the string
string[size] = 0;
LONG attrSize = ImmGetCompositionString(imc, GCS_COMPATTR, attr, length);
WWASSERT(size == attrSize);
if (attrSize <= size)
{
return 0;
}
return ConvertAttrForUnicode(string, attr);
}
/******************************************************************************
*
* NAME
* IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
long IMEManager::ReadCompositionClause(HIMC imc, unsigned long* clause, int length)
{
if (mUseUnicode)
{
return ImmGetCompositionStringW(imc, GCS_COMPCLAUSE, clause, length);
}
// Read the string as multibyte ANSI
unsigned char string[IME_MAX_STRING_LEN];
LONG size = ImmGetCompositionString(imc, GCS_COMPSTR, string, sizeof(string));
if (size <= 0)
{
return size;
}
// Terminate the string
string[size] = 0;
LONG clauseSize = ImmGetCompositionString(imc, GCS_COMPCLAUSE, clause, length);
if (clauseSize <= 0)
{
return 0;
}
return ConvertClauseForUnicode(string, size, clause);
}
/******************************************************************************
*
* NAME
* IMEManager::ReadCursorPos
*
* DESCRIPTION
* Read the composition string cursor position.
*
* INPUTS
*
* RESULT
*
******************************************************************************/
long IMEManager::ReadCursorPos(HIMC imc)
{
if (mUseUnicode)
{
long cursorPos = ImmGetCompositionStringW(imc, GCS_CURSORPOS, NULL, 0);
return (cursorPos & 0x0000FFFF);
}
// Get the multibyte string
char string[IME_MAX_STRING_LEN];
LONG size = ImmGetCompositionString(imc, GCS_COMPSTR, string, sizeof(string));
if (size < 0)
{
return 0;
}
string[size] = 0;
long cursorPos = ImmGetCompositionString(imc, GCS_CURSORPOS, NULL, 0);
cursorPos = (cursorPos & 0x0000FFFF);
// Convert multibyte character position in unicode position.
return _mbsnccnt((unsigned char*)string, cursorPos);
}
/******************************************************************************
*
* NAME
* IMEManager::GetTargetClause
*
* DESCRIPTION
* Get the composition string conversion target range. This is the characters
* that are currently be considered for conversion.
*
* INPUTS
*
* RESULT
*
******************************************************************************/
void IMEManager::GetTargetClause(unsigned long& start, unsigned long& end)
{
int index = 0;
const unsigned long compLength = wcslen(mCompositionString);
while (mCompositionClause[index] < compLength)
{
unsigned long offset = mCompositionClause[index];
if (ATTR_TARGET_CONVERTED == mCompositionAttr[offset])
{
start = offset;
end = mCompositionClause[index + 1];
return;
}
++index;
}
start = 0;
end = 0;
}
/******************************************************************************
*
* NAME
* IMEManager::GetCompositionFont
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
bool IMEManager::GetCompositionFont(LPLOGFONT lpFont)
{
BOOL success = FALSE;
HIMC imc = ImmGetContext(mHWND);
if (imc)
{
success = ImmGetCompositionFont(imc, lpFont);
ImmReleaseContext(mHWND, imc);
}
return (success == TRUE);
}
/******************************************************************************
*
* NAME
* IMEManager::OpenCandidate
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
void IMEManager::OpenCandidate(unsigned long candList)
{
WWDEBUG_SAY(("IMEManager: OpenCandidate\n"));
for (int index = 0; index < 32; index++)
{
if (candList & (1 << index))
{
IMECandidate& candidate = mCandidateColl[index];
candidate.Open(index, mHWND, mCodePage, mUseUnicode, mStartCandListFrom1);
candidate.Read();
CandidateEvent event(CANDIDATE_OPEN, &candidate);
NotifyObservers(event);
}
}
}
/******************************************************************************
*
* NAME
* IMEManager::ChangeCandidate
*
* DESCRIPTION
* The contents of the candidate list has changed.
*
* INPUTS
* Changed - Bitfield of the candidate lists that have changed.
*
* RESULT
* NONE
*
******************************************************************************/
void IMEManager::ChangeCandidate(unsigned long candList)
{
WWDEBUG_SAY(("IMEManager: ChangeCandidate\n"));
for (int index = 0; index < 32; index++)
{
if (candList & (1 << index))
{
IMECandidate& candidate = mCandidateColl[index];
candidate.Read();
CandidateEvent event(CANDIDATE_CHANGE, &candidate);
NotifyObservers(event);
}
}
}
/******************************************************************************
*
* NAME
* IMEManager::CloseCandidate
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
void IMEManager::CloseCandidate(unsigned long candList)
{
WWDEBUG_SAY(("IMEManager: CloseCandidate\n"));
for (int index = 0; index < 32; index++)
{
if (candList & (1 << index))
{
IMECandidate& candidate = mCandidateColl[index];
CandidateEvent event(CANDIDATE_CLOSE, &candidate);
NotifyObservers(event);
candidate.Close();
}
}
}
/******************************************************************************
*
* NAME
* IMEManager::GetGuideLine
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
unsigned long IMEManager::GetGuideline(wchar_t* outString, int length)
{
unsigned long level = GL_LEVEL_NOGUIDELINE;
HIMC imc = ImmGetContext(mHWND);
if (imc)
{
level = ImmGetGuideLine(imc, GGL_LEVEL, NULL, 0);
if ((GL_LEVEL_NOGUIDELINE != level) && outString)
{
if (mUseUnicode)
{
DWORD size = ImmGetGuideLineW(imc, GGL_STRING, outString, (length * sizeof(wchar_t)));
WWASSERT(size <= (DWORD)length);
outString[size / sizeof(wchar_t)] = 0;
}
else
{
char temp[512];
DWORD size = ImmGetGuideLine(imc, GGL_STRING, temp, sizeof(temp));
temp[size] = 0;
MultiByteToWideChar(mCodePage, 0, temp, -1, outString, length);
outString[length] = 0;
}
}
ImmReleaseContext(mHWND, imc);
}
return level;
}
/******************************************************************************
*
* NAME
* IMEManager::IMECharHandler
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* True if character converted.
*
******************************************************************************/
bool IMEManager::IMECharHandler(unsigned short dbcs)
{
unsigned long mbcs = dbcs;
// If this char has a lead byte then it is double byte. Swap the bytes
// for generate string order
if (dbcs & 0xFF00)
{
mbcs = (((dbcs & 0xFF) << 8) | (dbcs >> 8));
}
// Convert char to unicode
wchar_t unicode = 0;
MultiByteToWideChar(mCodePage, 0, (const char*)&mbcs, -1, &unicode, 1);
UnicodeChar event(unicode);
NotifyObservers(event);
return true;
}
/******************************************************************************
*
* NAME
* IMEManager::CharHandler
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* True if character handled.
*
******************************************************************************/
bool IMEManager::CharHandler(unsigned short ch)
{
// Because DBCS characters are usually generated by IMEs (as two PostMessages),
// if a lead byte comes in, the trail byte should arrive very soon after.
// We wait here for the trail byte and store them into the text buffer together.
if (!IsDBCSLeadByte((unsigned char)ch))
{
return false;
}
// Wait an arbitrary amount of time for the trail byte to arrive.
// If it doesn't, then discard the lead byte. This could happen if the IME
// screwed up. Or, more likely, the user generated the lead byte through ALT-numpad.
MSG msg;
int i = 10;
while (!PeekMessage(&msg, mHWND, WM_CHAR, WM_CHAR, PM_REMOVE))
{
if (--i == 0)
{
return true;
}
Sleep(0);
}
// Convert char to unicode.
unsigned long dbcs = (unsigned long)(((unsigned)msg.wParam << 8) | ch);
wchar_t unicode = 0;
MultiByteToWideChar(mCodePage, 0, (const char*)&dbcs, 2, &unicode, 1);
UnicodeChar event(unicode);
NotifyObservers(event);
return true;
}
/******************************************************************************
*
* NAME
* IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
long IMEManager::ConvertAttrForUnicode(unsigned char* mbcs, unsigned char* attr)
{
// Scale the attributes for unicode string length
unsigned char* mbsPtr = mbcs;
unsigned char* attrPtr = attr;
while (*mbsPtr != 0)
{
*attrPtr = attr[mbsPtr - mbcs];
++attrPtr;
mbsPtr = _mbsinc(mbsPtr);
}
return (attrPtr - attr);
}
/******************************************************************************
*
* NAME
* IMEManager::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
long IMEManager::ConvertClauseForUnicode(unsigned char* mbcs, long length, unsigned long* clause)
{
//---------------------------------------------------------------------------
// Scale the clause offsets for unicode string
//---------------------------------------------------------------------------
unsigned char* mbsPtr = mbcs;
unsigned long offset = 0;
// The first clause is always zero so there is no need to adjust it.
int index = 1;
// The clause is terminated with the size of the string
while (clause[index] < (unsigned long)length)
{
// Count the number of characters in this clause
unsigned char* mbsStop = (mbcs + clause[index]);
while (mbsPtr < mbsStop)
{
++offset;
mbsPtr = _mbsinc(mbsPtr);
}
clause[index] = offset;
++index;
}
// Terminate the unicode adjusted clause with the string length
clause[index] = _mbslen(mbcs);
++index;
return (&clause[index] - clause);
}
} // namespace IME