// Platform implementation of EterLib/IME.h (40250 EterLib/IME.cpp). // The edit buffer (m_wText, cursor, SetText/GetText, OnChar/InsertString, IsMax, the colour-tag aware cursor // moves) and WMChar are the 40250 bodies verbatim. What 40250 gets from Win32 IMM/TSF — composition strings, // the candidate list, the reading window, keyboard-layout code pages — has no counterpart under Godot: the // host delivers committed characters only (Metin2PythonHost.ui_char -> WMChar, or InsertString for text the // input code page cannot hold, which is what 40250's ResultProcess does with a GCS_RESULTSTR). Those entry // points keep 40250's "no IME present" behaviour: no composition, empty candidate/reading lists. #include "EterLib/StdAfx.h" #include "EterLib/IME.h" #include "EterLib/TextTag.h" #include "EterLib/GrpTextInstance.h" #include "EterLib/Util.h" #include #include namespace { // 40250 s_aszIndicator[INDICATOR_NON_IME] (IME.cpp:67); CheckInputLocale overwrites it with the locale's // two-letter abbreviation, which here is the locale code page's language. wchar_t s_aszNonImeIndicator[3] = L"En"; } /*---------------------------------------------------------------------------*/ /* Public */ CIME::CIME() { ms_hWnd = NULL; ms_bCandidateList = false; ms_bReadingInformation = false; Clear(); m_max = 0; m_userMax = 0; m_bOnlyNumberMode = FALSE; m_hOrgIMC = NULL; m_bEnablePaste = false; m_bUseDefaultIME = false; } CIME::~CIME() { } // PORT: 40250 loads imm32.dll, disables Cicero, reads the keyboard layout and sets up the TSF sinks. The // Godot host has no IMM context; only the part the edit buffer depends on — the input/output code page from // CheckInputLocale and SetSupportLevel(2) — is kept. bool CIME::Initialize(HWND hWnd) { if(ms_bInitialized) return true; ms_hWnd = hWnd; ms_bDisableIMECompletely = true; ms_bInitialized = true; CheckInputLocale(); ChangeInputLanguageWorker(); SetSupportLevel(2); ms_bUILessMode = false; CheckToggleState(); return true; } void CIME::Uninitialize() { if ( !ms_bInitialized ) return; ms_hWnd = NULL; m_hOrgIMC = NULL; ms_bInitialized = false; } void CIME::UseDefaultIME() { m_bUseDefaultIME = true; } bool CIME::IsIMEEnabled() { return ms_bImeEnabled; } // PORT: 40250 ImmAssociateContext()s the window; ms_bDisableIMECompletely (no imm32) forces bEnable=false, // which is always the case here. void CIME::EnableIME(bool bEnable) { if (!ms_bInitialized || !ms_hWnd) return; if (ms_bDisableIMECompletely) bEnable = false; ms_bImeEnabled = bEnable; if (bEnable) CheckToggleState(); } void CIME::DisableIME() { EnableIME(false); } void CIME::EnableCaptureInput() { ms_bCaptureInput = true; } void CIME::DisableCaptureInput() { ms_bCaptureInput = false; } bool CIME::IsCaptureEnabled() { return ms_bCaptureInput; } void CIME::Clear() { ms_lastpos = 0; ms_curpos = 0; ms_compLen = 0; ms_ulbegin = 0; ms_ulend = 0; } int CIME::GetReading(std::string & rstrText) { char reading[IMEREADING_MAXLEN]; if(ms_wstrReading.size() == 0) return 0; int readingLen = WideCharToMultiByte(ms_uOutputCodePage, 0, &ms_wstrReading[0], ms_wstrReading.size(), reading, sizeof(reading), NULL, NULL); rstrText.append(GetCodePageText()); rstrText.append(reading, reading + readingLen); return rstrText.size(); } int CIME::GetReadingError() { return ms_iReadingError; } void CIME::SetMax(int iMax) { m_max = iMax; } void CIME::SetUserMax(int iMax) { m_userMax = iMax; } void CIME::SetText(const char* szText, int len) { ms_compLen = 0; ms_ulbegin = 0; ms_ulend = 0; const char* begin = szText; const char* end = begin + len; const char* iter = FindToken(begin, end); int m_wTextLen = sizeof(m_wText)/sizeof(wchar_t); ms_lastpos = MultiByteToWideChar(ms_uInputCodePage, 0, begin, iter-begin, m_wText, m_wTextLen); if (iter < end) ms_lastpos += MultiByteToWideChar(ReadToken(iter), 0, (iter+5), end-(iter+5), m_wText+ms_lastpos, m_wTextLen-ms_lastpos); ms_curpos = std::min(ms_curpos, ms_lastpos); } int CIME::GetText(std::string & rstrText, bool addCodePage) { int outCodePage = ms_uOutputCodePage; int dataCodePage; switch (outCodePage) { //case 1256: // ARABIC case 1268: // VIETNAM dataCodePage = CP_UTF8; break; default: dataCodePage = outCodePage; } int len = 0; char text[IMESTR_MAXLEN]; len += WideCharToMultiByte(dataCodePage, 0, m_wText, ms_curpos, text, sizeof(text)-len, NULL, NULL); len += WideCharToMultiByte(dataCodePage, 0, m_wszComposition, ms_compLen, text+len, sizeof(text)-len, NULL, NULL); len += WideCharToMultiByte(dataCodePage, 0, m_wText+ms_curpos, ms_lastpos-ms_curpos, text+len, sizeof(text)-len, NULL, NULL); int i; for(i=0; i 0x7F) break; if(i == len) { rstrText.append(text, text+len); } else { rstrText.append(text, text+i); //if (addCodePage) // rstrText.append(GetCodePageText()); rstrText.append(text+i, text+len); } return rstrText.size(); } const char* CIME::GetCodePageText() { static char szCodePage[16]; const int defCodePage = GetDefaultCodePage(); const int outCodePage = ms_uOutputCodePage; if (outCodePage != defCodePage) { sprintf(szCodePage, "@%04d", outCodePage); } else { szCodePage[0] = 0; } return szCodePage; } int CIME::GetCodePage() { return ms_uOutputCodePage; } int CIME::GetCandidatePageCount() { return ms_dwCandidatePageSize; } int CIME::GetCandidateCount() { return ms_dwCandidateCount; } int CIME::GetCandidate(DWORD index, std::string & rstrText) { if(index >= MAX_CANDLIST) return 0; LPCWSTR wszText = ms_wszCandidate[index]; if(wszText == NULL) return 0; int wTextLen = wcslen(wszText); if(wTextLen == 0) return 0; char text[IMESTR_MAXLEN]; int len = ::WideCharToMultiByte(CP_UTF8, 0, wszText, wTextLen, text, sizeof(text), 0, 0); rstrText.append("@9999"); rstrText.append(text, text+len); return wTextLen; } int CIME::GetCandidateSelection() { return ms_dwCandidateSelection; } // PORT: 40250 ImmSetConversionStatus / ImmGetConversionStatus. Without an IMM context the conversion mode is // the alphanumeric one (IME_CMODE_ALPHANUMERIC == 0) and cannot be changed. void CIME::SetInputMode(DWORD) { } DWORD CIME::GetInputMode() { return 0; } void CIME::SetNumberMode() { m_bOnlyNumberMode = TRUE; } void CIME::SetStringMode() { m_bOnlyNumberMode = FALSE; } void CIME::AddExceptKey(wchar_t key) { m_exceptKey.push_back(key); } void CIME::ClearExceptKey() { m_exceptKey.clear(); } bool CIME::__IsWritable(wchar_t key) { if ( m_exceptKey.end() == std::find(m_exceptKey.begin(),m_exceptKey.end(),key) ) return true; else return false; } void CIME::EnablePaste(bool bFlag) { m_bEnablePaste = bFlag; } // PORT: 40250 reads CF_TEXT from the Win32 clipboard. The host clipboard is not wired to the extension yet, // so this pastes nothing (the same as an empty clipboard). void CIME::PasteTextFromClipBoard() { if (!m_bEnablePaste) return; } // PORT: 40250 cancels the IMM composition string and closes the candidate list. There is no composition // here; the candidate-list close (and its event) is kept. void CIME::FinalizeString(bool) { static bool s_bProcessing = false; // to avoid infinite recursion if ( !ms_bInitialized || s_bProcessing ) return; s_bProcessing = true; CloseCandidateList(); s_bProcessing = false; } int CIME::GetCompLen() { return ms_compLen; } int CIME::GetULBegin() { return ms_ulbegin; } int CIME::GetULEnd() { return ms_ulend; } void CIME::CloseCandidateList() { ms_bCandidateList = false; ms_dwCandidateCount = 0; memset(&ms_wszCandidate, 0, sizeof(ms_wszCandidate)); if(ms_pEvent) ms_pEvent->OnCloseCandidateList(); } void CIME::CloseReadingInformation() { CIME::ms_bReadingInformation = false; if(CIME::ms_pEvent) CIME::ms_pEvent->OnCloseReadingWnd(); } // PORT: 40250 re-reads the keyboard layout (GETLANG) and re-applies the IME support level when the language // changed. The host has one fixed input locale, so only the toggle check and the code-page event remain. void CIME::ChangeInputLanguage() { CheckToggleState(); ChangeInputLanguageWorker(); if(ms_pEvent) ms_pEvent->OnChangeCodePage(); } void CIME::ChangeInputLanguageWorker() { if ( !ms_bUILessMode ) ms_iCandListIndexBase = 1; SetupImeApi(); } // PORT: 40250 forces level 3 for the Korean keyboard layout; there is no keyboard layout here. void CIME::SetSupportLevel( DWORD dwImeLevel ) { if ( dwImeLevel < 2 || 3 < dwImeLevel ) return; ms_dwIMELevel = dwImeLevel; // cancel current composition string. FinalizeString(); } LRESULT CIME::WMInputLanguage(HWND, UINT, WPARAM, LPARAM) { ChangeInputLanguage(); return 0; } LRESULT CIME::WMStartComposition(HWND, UINT, WPARAM, LPARAM) { return 1L; } // PORT: 40250 pulls GCS_RESULTSTR/GCS_COMPSTR out of the IMM context. The host never sends compositions. LRESULT CIME::WMComposition(HWND, UINT, WPARAM, LPARAM) { return 0; } LRESULT CIME::WMEndComposition(HWND, UINT, WPARAM, LPARAM) { ms_compLen = 0; ms_ulbegin = 0; ms_ulend = 0; if(ms_pEvent) ms_pEvent->OnUpdate(); return 0L; } // PORT: 40250 handles IMN_* candidate/reading/toggle notifications from IMM; none are sent here. LRESULT CIME::WMNotify(HWND, UINT, WPARAM, LPARAM) { return 0; } LRESULT CIME::WMChar(HWND /*hWnd*/, UINT /*uiMsg*/, WPARAM wParam, LPARAM lParam) { unsigned char c = (unsigned char)(wParam & 0xff); switch (c) { case 8: if(ms_bCaptureInput == false) return 0; if (ms_curpos > 0) { DecCurPos(); DelCurPos(); } if(ms_pEvent) ms_pEvent->OnUpdate(); return 0; break; default: if(ms_pEvent) { if (ms_pEvent->OnWM_CHAR(wParam, lParam)) break; } if(ms_bCaptureInput == false) return 0; wchar_t w[10]; MultiByteToWideChar(ms_uInputCodePage, 0, (char*)&c, 1, w, 1); OnChar(w[0]); if (w[0] == L'|') OnChar(w[0]); if(ms_pEvent) ms_pEvent->OnUpdate(); break; } return 0; } /*---------------------------------------------------------------------------*/ /* Protected */ void CIME::IncCurPos() { if (ms_curpos < ms_lastpos) { int pos = FindColorTagEndPosition(m_wText + ms_curpos, ms_lastpos - ms_curpos); if (pos > 0) ms_curpos = std::min(ms_lastpos, std::max(0, ms_curpos + (pos + 1))); else ++ms_curpos; //++ms_curpos; } } void CIME::DecCurPos() { if (ms_curpos > 0) { int pos = FindColorTagStartPosition(m_wText + ms_curpos - 1, ms_curpos); if (pos > 0) ms_curpos = std::min(ms_lastpos, std::max(0, ms_curpos - (pos + 1))); else --ms_curpos; //--ms_curpos; } } int CIME::GetCurPos() { int pos = GetTextTagOutputLen(m_wText, ms_curpos); return pos; //return ms_curpos; } void CIME::SetCurPos(int offset) { if (offset < 0 || offset > ms_lastpos) { ms_curpos = ms_lastpos; return; } else { // offset is a position in the rendered text, so it has to be mapped back to the buffer. //ms_curpos = min(ms_lastpos, offset); ms_curpos = std::min(ms_lastpos, GetTextTagInternalPosFromRenderPos(m_wText, ms_lastpos, offset)); } } void CIME::DelCurPos() { if (ms_curpos < ms_lastpos) { int eraseCount = FindColorTagEndPosition(m_wText + ms_curpos, ms_lastpos - ms_curpos) + 1; // PORT: 40250 wcscpy()s the overlapping tail down; that is undefined for overlapping buffers, and the // buffer is not guaranteed NUL-terminated at ms_lastpos, so move exactly the live tail. memmove(m_wText + ms_curpos, m_wText + ms_curpos + eraseCount, sizeof(wchar_t) * (ms_lastpos - ms_curpos - eraseCount)); ms_lastpos -= eraseCount; ms_curpos = std::min(ms_lastpos, ms_curpos); } } void CIME::PasteString(const char * str) { const char * begin = str; const char * end = str + strlen(str); wchar_t m_wText[IMESTR_MAXLEN]; int wstrLen = MultiByteToWideChar(ms_uInputCodePage, 0, begin, end - begin, m_wText, IMESTR_MAXLEN); InsertString(m_wText, wstrLen); if(ms_pEvent) ms_pEvent->OnUpdate(); } // PORT: 40250 reads the keyboard layout's default ANSI code page (GetLocaleInfoA LOCALE_IDEFAULTANSICODEPAGE). // The host's input locale is the client locale, so its code page (SetDefaultCodePage from locale.cfg) is used. void CIME::CheckInputLocale() { static UINT s_uPrevCodePage = 0xFFFF; ms_uInputCodePage = GetDefaultCodePage(); if ( s_uPrevCodePage == ms_uInputCodePage ) return; s_uPrevCodePage = ms_uInputCodePage; ms_bVerticalCandidate = false; ms_wszCurrentIndicator = s_aszNonImeIndicator; if(ms_uOutputCodePage != 1256) { ms_uOutputCodePage = ms_uInputCodePage; Clear(); } } // PORT: 40250 asks IMM whether an IME is open; there is none, so the state is IMEUI_STATE_OFF (0). void CIME::CheckToggleState() { CheckInputLocale(); ms_bChineseIME = false; ms_dwImeState = 0; } /*---------------------------------------------------------------------------*/ /* Private */ void CIME::InsertString(wchar_t* wString, int iSize) { if (IsMax(wString, iSize)) return; if (ms_curpos < ms_lastpos) memmove(m_wText+ms_curpos+iSize, m_wText+ms_curpos, sizeof(wchar_t)*(ms_lastpos-ms_curpos)); memcpy(m_wText+ms_curpos, wString, sizeof(wchar_t)*iSize); ms_curpos += iSize; ms_lastpos += iSize; } void CIME::OnChar(wchar_t c) { if (m_bOnlyNumberMode) if (!iswdigit(c)) return; if (!__IsWritable(c)) return; InsertString(&c, 1); } // PORT: 40250 maps a keyboard-layout LANGID to its ANSI code page. Only CheckInputLocale's code page is used // here (see there), so this answers with it. UINT CIME::GetCodePageFromLang(LANGID) { return GetDefaultCodePage(); } // PORT: the IMM composition/candidate/reading processors (40250 IME.cpp:951-1227). No IMM context exists. void CIME::ResultProcess(HIMC) {} void CIME::CompositionProcessBuilding(HIMC) {} void CIME::CompositionProcess(HIMC) {} void CIME::AttributeProcess(HIMC) {} void CIME::CandidateProcess(HIMC) {} void CIME::ReadingProcess(HIMC) {} bool CIME::IsMax(const wchar_t* wInput, int len) { if (ms_lastpos + len > IMESTR_MAXLEN) return true; int textLen = WideCharToMultiByte(ms_uOutputCodePage, 0, m_wText, ms_lastpos, 0, 0, NULL, NULL); int inputLen = WideCharToMultiByte(ms_uOutputCodePage, 0, wInput, len, 0, 0, NULL, NULL); //return textLen + inputLen > m_max; if (textLen + inputLen > m_max) return true; else if (m_userMax != 0 && m_max != m_userMax) { std::wstring str = GetTextTagOutputString(m_wText, ms_lastpos); std::wstring input = GetTextTagOutputString(wInput, len); int textLen = WideCharToMultiByte(ms_uOutputCodePage, 0, str.c_str(), str.length(), 0, 0, NULL, NULL); int inputLen = WideCharToMultiByte(ms_uOutputCodePage, 0, input.c_str(), input.length(), 0, 0, NULL, NULL); return textLen + inputLen > m_userMax; } return false; } // PORT: 40250 identifies the CHT/CHS IME DLL version and the reading-window orientation. No IME DLL here. DWORD CIME::GetImeId(UINT) { return 0; } bool CIME::GetReadingWindowOrientation() { return true; } void CIME::SetupImeApi() { _GetReadingString = NULL; _ShowReadingWindow = NULL; } decltype(CIME::_ImmLockIMC) CIME::_ImmLockIMC{}; decltype(CIME::_ImmUnlockIMC) CIME::_ImmUnlockIMC{}; decltype(CIME::_ImmLockIMCC) CIME::_ImmLockIMCC{}; decltype(CIME::_ImmUnlockIMCC) CIME::_ImmUnlockIMCC{}; decltype(CIME::_GetReadingString) CIME::_GetReadingString{}; decltype(CIME::_ShowReadingWindow) CIME::_ShowReadingWindow{}; decltype(CIME::ms_bInitialized) CIME::ms_bInitialized{}; decltype(CIME::ms_bDisableIMECompletely) CIME::ms_bDisableIMECompletely{}; decltype(CIME::ms_bUILessMode) CIME::ms_bUILessMode{}; decltype(CIME::ms_bImeEnabled) CIME::ms_bImeEnabled{}; decltype(CIME::ms_bCaptureInput) CIME::ms_bCaptureInput{}; decltype(CIME::ms_bChineseIME) CIME::ms_bChineseIME{}; decltype(CIME::ms_bUseIMMCandidate) CIME::ms_bUseIMMCandidate{}; decltype(CIME::ms_hWnd) CIME::ms_hWnd{}; decltype(CIME::ms_hklCurrent) CIME::ms_hklCurrent{}; decltype(CIME::ms_szKeyboardLayout) CIME::ms_szKeyboardLayout{}; decltype(CIME::ms_stOSVI) CIME::ms_stOSVI{}; decltype(CIME::ms_hImm32Dll) CIME::ms_hImm32Dll{}; decltype(CIME::ms_hCurrentImeDll) CIME::ms_hCurrentImeDll{}; decltype(CIME::ms_dwImeState) CIME::ms_dwImeState{}; decltype(CIME::ms_adwId) CIME::ms_adwId{}; decltype(CIME::ms_dwIMELevel) CIME::ms_dwIMELevel{}; decltype(CIME::ms_dwIMELevelSaved) CIME::ms_dwIMELevelSaved{}; decltype(CIME::ms_bCandidateList) CIME::ms_bCandidateList{}; decltype(CIME::ms_dwCandidateCount) CIME::ms_dwCandidateCount{}; decltype(CIME::ms_bVerticalCandidate) CIME::ms_bVerticalCandidate{}; decltype(CIME::ms_iCandListIndexBase) CIME::ms_iCandListIndexBase{}; decltype(CIME::ms_wszCandidate) CIME::ms_wszCandidate{}; decltype(CIME::ms_dwCandidateSelection) CIME::ms_dwCandidateSelection{}; decltype(CIME::ms_dwCandidatePageSize) CIME::ms_dwCandidatePageSize{}; decltype(CIME::ms_bReadingInformation) CIME::ms_bReadingInformation{}; decltype(CIME::ms_iReadingError) CIME::ms_iReadingError{}; decltype(CIME::ms_bHorizontalReading) CIME::ms_bHorizontalReading{}; decltype(CIME::ms_wstrReading) CIME::ms_wstrReading{}; decltype(CIME::ms_wszCurrentIndicator) CIME::ms_wszCurrentIndicator{}; decltype(CIME::ms_pEvent) CIME::ms_pEvent{}; decltype(CIME::m_wText) CIME::m_wText{}; decltype(CIME::ms_compLen) CIME::ms_compLen{}; decltype(CIME::ms_curpos) CIME::ms_curpos{}; decltype(CIME::ms_lastpos) CIME::ms_lastpos{}; decltype(CIME::ms_ulbegin) CIME::ms_ulbegin{}; decltype(CIME::ms_ulend) CIME::ms_ulend{}; decltype(CIME::ms_uOutputCodePage) CIME::ms_uOutputCodePage{}; decltype(CIME::ms_uInputCodePage) CIME::ms_uInputCodePage{};