void CIME::CheckInputLocale() { static HKL hklPrev = 0; s_hklCurrent = GetKeyboardLayout(0); if (hklPrev == s_hklCurrent) return; hklPrev = s_hklCurrent; switch (GetPrimaryLanguage()) { // Simplified Chinese case LANG_CHINESE: s_bVerticalCand = true; switch (GetSubLanguage()) { case SUBLANG_CHINESE_SIMPLIFIED: s_wszCurrIndicator = s_aszIndicator[INDICATOR_CHS]; s_bVerticalCand = GetImeId() == 0; break; case SUBLANG_CHINESE_TRADITIONAL: s_wszCurrIndicator = s_aszIndicator[INDICATOR_CHT]; break; default: // unsupported sub-language s_wszCurrIndicator = s_aszIndicator[INDICATOR_NON_IME]; break; } break; // Korean case LANG_KOREAN: s_wszCurrIndicator = s_aszIndicator[INDICATOR_KOREAN]; s_bVerticalCand = false; break; // Japanese case LANG_JAPANESE: s_wszCurrIndicator = s_aszIndicator[INDICATOR_JAPANESE]; s_bVerticalCand = true; break; default: // A non-IME language. Obtain the language abbreviation // and store it for rendering the indicator later. s_wszCurrIndicator = s_aszIndicator[INDICATOR_NON_IME]; } // If non-IME, use the language abbreviation. if(s_wszCurrIndicator == s_aszIndicator[INDICATOR_NON_IME]) { WCHAR wszLang[5]; GetLocaleInfoW(MAKELCID(LOWORD(s_hklCurrent), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, wszLang, 5); s_wszCurrIndicator[0] = wszLang[0]; s_wszCurrIndicator[1] = towlower(wszLang[1]); } }
void CIME::CheckToggleState() { CheckInputLocale(); bool bIme = _ImmIsIME(s_hklCurrent) != 0; s_bChineseIME = (GetPrimaryLanguage() == LANG_CHINESE) && bIme; HIMC hImc; if(NULL != (hImc = ImmGetContext(UIGetHWND()))) { if(s_bChineseIME) { DWORD dwConvMode, dwSentMode; _ImmGetConversionStatus(hImc, &dwConvMode, &dwSentMode); s_ImeState = (dwConvMode & IME_CMODE_NATIVE) ? IMEUI_STATE_ON : IMEUI_STATE_ENGLISH; } else { s_ImeState = (bIme && _ImmGetOpenStatus(hImc) != 0) ? IMEUI_STATE_ON : IMEUI_STATE_OFF; } _ImmReleaseContext(UIGetHWND(), hImc); } else s_ImeState = IMEUI_STATE_OFF; }
//-------------------------------------------------------------------------------------- DXUTAPI void CDXUTIMEEditBox::RenderComposition() { s_CompString.SetText(ImeUi_GetCompositionString()); RECT rcCaret = { 0, 0, 0, 0 }; int nX, nXFirst; m_Buffer.CPtoX(m_nCaret, FALSE, &nX); m_Buffer.CPtoX(m_nFirstVisible, FALSE, &nXFirst); CDXUTElement* pElement = m_Elements[1]; // Get the required width RECT rc = { m_rcText.left + nX - nXFirst, m_rcText.top, m_rcText.left + nX - nXFirst, m_rcText.bottom }; m_pDialog->CalcTextRect(s_CompString.GetBuffer(), pElement, &rc); // If the composition string is too long to fit within // the text area, move it to below the current line. // This matches the behavior of the default IME. if (rc.right > m_rcText.right) OffsetRect(&rc, m_rcText.left - rc.left, rc.bottom - rc.top); // Save the rectangle position for processing highlighted text. RECT rcFirst = rc; // Update s_ptCompString for RenderCandidateReadingWindow(). s_ptCompString.x = rc.left; s_ptCompString.y = rc.top; DWORD TextColor = m_CompColor; // Render the window and string. // If the string is too long, we must wrap the line. pElement->FontColor.SetCurrent(TextColor); const WCHAR* pwszComp = s_CompString.GetBuffer(); int nCharLeft = s_CompString.GetTextSize(); for (;;) { // Find the last character that can be drawn on the same line. int nLastInLine; int bTrail; s_CompString.XtoCP(m_rcText.right - rc.left, &nLastInLine, &bTrail); int nNumCharToDraw = std::min(nCharLeft, nLastInLine); m_pDialog->CalcTextRect(pwszComp, pElement, &rc, nNumCharToDraw); // Draw the background // For Korean IME, blink the composition window background as if it // is a cursor. if (GetPrimaryLanguage() == LANG_KOREAN) { if (m_bCaretOn) { m_pDialog->DrawRect(&rc, m_CompWinColor); } else { // Not drawing composition string background. We // use the editbox's text color for composition // string text. TextColor = m_Elements[0]->FontColor.States[DXUT_STATE_NORMAL]; } } else { // Non-Korean IME. Always draw composition background. m_pDialog->DrawRect(&rc, m_CompWinColor); } // Draw the text pElement->FontColor.SetCurrent(TextColor); m_pDialog->DrawText(pwszComp, pElement, &rc, false); // Advance pointer and counter nCharLeft -= nNumCharToDraw; pwszComp += nNumCharToDraw; if (nCharLeft <= 0) break; // Advance rectangle coordinates to beginning of next line OffsetRect(&rc, m_rcText.left - rc.left, rc.bottom - rc.top); } // Load the rect for the first line again. rc = rcFirst; // Inspect each character in the comp string. // For target-converted and target-non-converted characters, // we display a different background color so they appear highlighted. int nCharFirst = 0; nXFirst = 0; s_nFirstTargetConv = -1; BYTE* pAttr; const WCHAR* pcComp; for (pcComp = s_CompString.GetBuffer(), pAttr = ImeUi_GetCompStringAttr(); *pcComp != L'\0'; ++pcComp, ++pAttr) { DWORD bkColor; // Render a different background for this character int nXLeft, nXRight; s_CompString.CPtoX(int(pcComp - s_CompString.GetBuffer()), FALSE, &nXLeft); s_CompString.CPtoX(int(pcComp - s_CompString.GetBuffer()), TRUE, &nXRight); // Check if this character is off the right edge and should // be wrapped to the next line. if (nXRight - nXFirst > m_rcText.right - rc.left) { // Advance rectangle coordinates to beginning of next line OffsetRect(&rc, m_rcText.left - rc.left, rc.bottom - rc.top); // Update the line's first character information nCharFirst = int(pcComp - s_CompString.GetBuffer()); s_CompString.CPtoX(nCharFirst, FALSE, &nXFirst); } // If the caret is on this character, save the coordinates // for drawing the caret later. if (ImeUi_GetImeCursorChars() == (DWORD)(pcComp - s_CompString.GetBuffer())) { rcCaret = rc; rcCaret.left += nXLeft - nXFirst - 1; rcCaret.right = rcCaret.left + 2; } // Set up color based on the character attribute if (*pAttr == ATTR_TARGET_CONVERTED) { pElement->FontColor.SetCurrent(m_CompTargetColor); bkColor = m_CompTargetBkColor; } else if (*pAttr == ATTR_TARGET_NOTCONVERTED) { pElement->FontColor.SetCurrent(m_CompTargetNonColor); bkColor = m_CompTargetNonBkColor; } else { continue; } RECT rcTarget = { rc.left + nXLeft - nXFirst, rc.top, rc.left + nXRight - nXFirst, rc.bottom }; m_pDialog->DrawRect(&rcTarget, bkColor); m_pDialog->DrawText(pcComp, pElement, &rcTarget, false, 1); // Record the first target converted character's index if (-1 == s_nFirstTargetConv) s_nFirstTargetConv = int(pAttr - ImeUi_GetCompStringAttr()); } // Render the composition caret if (m_bCaretOn) { // If the caret is at the very end, its position would not have // been computed in the above loop. We compute it here. if (ImeUi_GetImeCursorChars() == (DWORD)s_CompString.GetTextSize()) { s_CompString.CPtoX(ImeUi_GetImeCursorChars(), FALSE, &nX); rcCaret = rc; rcCaret.left += nX - nXFirst - 1; rcCaret.right = rcCaret.left + 2; } m_pDialog->DrawRect(&rcCaret, m_CompCaretColor); } }
//-------------------------------------------------------------------------------------- _Use_decl_annotations_ DXUTAPI void CDXUTIMEEditBox::RenderCandidateReadingWindow(bool bReading) { RECT rc; UINT nNumEntries = bReading ? 4 : MAX_CANDLIST; int nX, nXFirst, nXComp; m_Buffer.CPtoX(m_nCaret, FALSE, &nX); m_Buffer.CPtoX(m_nFirstVisible, FALSE, &nXFirst); DWORD TextColor, TextBkColor, SelTextColor, SelBkColor; if (bReading) { TextColor = m_ReadingColor; TextBkColor = m_ReadingWinColor; SelTextColor = m_ReadingSelColor; SelBkColor = m_ReadingSelBkColor; } else { TextColor = m_CandidateColor; TextBkColor = m_CandidateWinColor; SelTextColor = m_CandidateSelColor; SelBkColor = m_CandidateSelBkColor; } // For Japanese IME, align the window with the first target converted character. // For all other IMEs, align with the caret. This is because the caret // does not move for Japanese IME. if (GetLanguage() == MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL) && !GetImeId()) nXComp = 0; else if (GetPrimaryLanguage() == LANG_JAPANESE) s_CompString.CPtoX(s_nFirstTargetConv, FALSE, &nXComp); else s_CompString.CPtoX(ImeUi_GetImeCursorChars(), FALSE, &nXComp); // Compute the size of the candidate window int nWidthRequired = 0; int nHeightRequired = 0; int nSingleLineHeight = 0; if ((ImeUi_IsVerticalCand() && !bReading) || (!ImeUi_IsHorizontalReading() && bReading)) { // Vertical window for (UINT i = 0; i < nNumEntries; ++i) { if (*(ImeUi_GetCandidate(i)) == L'\0') break; SetRect(&rc, 0, 0, 0, 0); m_pDialog->CalcTextRect(ImeUi_GetCandidate(i), m_Elements[1], &rc); nWidthRequired = std::max<int>(nWidthRequired, rc.right - rc.left); nSingleLineHeight = std::max<int>(nSingleLineHeight, rc.bottom - rc.top); } nHeightRequired = nSingleLineHeight * nNumEntries; } else { // Horizontal window SetRect(&rc, 0, 0, 0, 0); if (bReading) m_pDialog->CalcTextRect(s_wszReadingString, m_Elements[1], &rc); else { WCHAR wszCand[256] = L""; s_CandList.nFirstSelected = 0; s_CandList.nHoriSelectedLen = 0; for (UINT i = 0; i < MAX_CANDLIST; ++i) { if (*ImeUi_GetCandidate(i) == L'\0') break; WCHAR wszEntry[32]; swprintf_s(wszEntry, 32, L"%s ", ImeUi_GetCandidate(i)); // If this is the selected entry, mark its char position. if (ImeUi_GetCandidateSelection() == i) { s_CandList.nFirstSelected = (int)wcslen(wszCand); s_CandList.nHoriSelectedLen = (int)wcslen(wszEntry) - 1; // Minus space } wcscat_s(wszCand, 256, wszEntry); } wszCand[wcslen(wszCand) - 1] = L'\0'; // Remove the last space s_CandList.HoriCand.SetText(wszCand); m_pDialog->CalcTextRect(s_CandList.HoriCand.GetBuffer(), m_Elements[1], &rc); } nWidthRequired = rc.right - rc.left; nSingleLineHeight = nHeightRequired = rc.bottom - rc.top; } // Now that we have the dimension, calculate the location for the candidate window. // We attempt to fit the window in this order: // bottom, top, right, left. bool bHasPosition = false; // Bottom SetRect(&rc, s_ptCompString.x + nXComp, s_ptCompString.y + m_rcText.bottom - m_rcText.top, s_ptCompString.x + nXComp + nWidthRequired, s_ptCompString.y + m_rcText.bottom - m_rcText.top + nHeightRequired); // if the right edge is cut off, move it left. if (rc.right > m_pDialog->GetWidth()) { rc.left -= rc.right - m_pDialog->GetWidth(); rc.right = m_pDialog->GetWidth(); } if (rc.bottom <= m_pDialog->GetHeight()) bHasPosition = true; // Top if (!bHasPosition) { SetRect(&rc, s_ptCompString.x + nXComp, s_ptCompString.y - nHeightRequired, s_ptCompString.x + nXComp + nWidthRequired, s_ptCompString.y); // if the right edge is cut off, move it left. if (rc.right > m_pDialog->GetWidth()) { rc.left -= rc.right - m_pDialog->GetWidth(); rc.right = m_pDialog->GetWidth(); } if (rc.top >= 0) bHasPosition = true; } // Right if (!bHasPosition) { int nXCompTrail; s_CompString.CPtoX(ImeUi_GetImeCursorChars(), TRUE, &nXCompTrail); SetRect(&rc, s_ptCompString.x + nXCompTrail, 0, s_ptCompString.x + nXCompTrail + nWidthRequired, nHeightRequired); if (rc.right <= m_pDialog->GetWidth()) bHasPosition = true; } // Left if (!bHasPosition) { SetRect(&rc, s_ptCompString.x + nXComp - nWidthRequired, 0, s_ptCompString.x + nXComp, nHeightRequired); if (rc.right >= 0) bHasPosition = true; } if (!bHasPosition) { // The dialog is too small for the candidate window. // Fall back to render at 0, 0. Some part of the window // will be cut off. rc.left = 0; rc.right = nWidthRequired; } // If we are rendering the candidate window, save the position // so that mouse clicks are checked properly. if (!bReading) s_CandList.rcCandidate = rc; // Render the elements m_pDialog->DrawRect(&rc, TextBkColor); if ((ImeUi_IsVerticalCand() && !bReading) || (!ImeUi_IsHorizontalReading() && bReading)) { // Vertical candidate window for (UINT i = 0; i < nNumEntries; ++i) { // Here we are rendering one line at a time rc.bottom = rc.top + nSingleLineHeight; // Use a different color for the selected string if (ImeUi_GetCandidateSelection() == i) { m_pDialog->DrawRect(&rc, SelBkColor); m_Elements[1]->FontColor.SetCurrent(SelTextColor); } else m_Elements[1]->FontColor.SetCurrent(TextColor); m_pDialog->DrawText(ImeUi_GetCandidate(i), m_Elements[1], &rc); rc.top += nSingleLineHeight; } } else { // Horizontal candidate window m_Elements[1]->FontColor.SetCurrent(TextColor); if (bReading) m_pDialog->DrawText(s_wszReadingString, m_Elements[1], &rc); else m_pDialog->DrawText(s_CandList.HoriCand.GetBuffer(), m_Elements[1], &rc); // Render the selected entry differently if (!bReading) { int nXLeft, nXRight; s_CandList.HoriCand.CPtoX(s_CandList.nFirstSelected, FALSE, &nXLeft); s_CandList.HoriCand.CPtoX(s_CandList.nFirstSelected + s_CandList.nHoriSelectedLen, FALSE, &nXRight); rc.right = rc.left + nXRight; rc.left += nXLeft; m_pDialog->DrawRect(&rc, SelBkColor); m_Elements[1]->FontColor.SetCurrent(SelTextColor); m_pDialog->DrawText(s_CandList.HoriCand.GetBuffer() + s_CandList.nFirstSelected, m_Elements[1], &rc, false); } } }
//-------------------------------------------------------------------------------------- _Use_decl_annotations_ DXUTAPI bool CDXUTIMEEditBox::HandleMouse(UINT uMsg, const POINT& pt, WPARAM wParam, LPARAM lParam) { if (!m_bEnabled || !m_bVisible) return false; switch (uMsg) { case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: { DXUTFontNode* pFont = m_pDialog->GetFont(m_Elements[9]->iFont); // Check if this click is on top of the composition string int nCompStrWidth; s_CompString.CPtoX(s_CompString.GetTextSize(), FALSE, &nCompStrWidth); if (s_ptCompString.x <= pt.x && s_ptCompString.y <= pt.y && s_ptCompString.x + nCompStrWidth > pt.x && s_ptCompString.y + pFont->nHeight > pt.y) { int nCharBodyHit, nCharHit; int nTrail; // Determine the character clicked on. s_CompString.XtoCP(pt.x - s_ptCompString.x, &nCharBodyHit, &nTrail); if (nTrail && nCharBodyHit < s_CompString.GetTextSize()) nCharHit = nCharBodyHit + 1; else nCharHit = nCharBodyHit; switch (GetPrimaryLanguage()) { case LANG_JAPANESE: // For Japanese, there are two cases. If s_nFirstTargetConv is // -1, the comp string hasn't been converted yet, and we use // s_nCompCaret. For any other value of s_nFirstTargetConv, // the string has been converted, so we use clause information. if (s_nFirstTargetConv != -1) { int nClauseClicked = 0; while ((int)s_adwCompStringClause[nClauseClicked + 1] <= nCharBodyHit) ++nClauseClicked; int nClauseSelected = 0; while ((int)s_adwCompStringClause[nClauseSelected + 1] <= s_nFirstTargetConv) ++nClauseSelected; BYTE nVirtKey = nClauseClicked > nClauseSelected ? VK_RIGHT : VK_LEFT; int nSendCount = abs(nClauseClicked - nClauseSelected); while (nSendCount-- > 0) SendKey(nVirtKey); return true; } // Not converted case. Fall thru to Chinese case. case LANG_CHINESE: { // For Chinese, use s_nCompCaret. BYTE nVirtKey = nCharHit > (int)ImeUi_GetImeCursorChars() ? VK_RIGHT : VK_LEFT; int nSendCount = abs(nCharHit - (int)ImeUi_GetImeCursorChars()); while (nSendCount-- > 0) SendKey(nVirtKey); break; } } return true; } // Check if the click is on top of the candidate window if (ImeUi_IsShowCandListWindow() && PtInRect(&s_CandList.rcCandidate, pt)) { if (ImeUi_IsVerticalCand()) { // Vertical candidate window // Compute the row the click is on int nRow = (pt.y - s_CandList.rcCandidate.top) / pFont->nHeight; if (nRow < (int)ImeUi_GetCandidateCount()) { // nRow is a valid entry. // Now emulate keystrokes to select the candidate at this row. switch (GetPrimaryLanguage()) { case LANG_CHINESE: case LANG_KOREAN: // For Chinese and Korean, simply send the number keystroke. SendKey((BYTE)('0' + nRow + 1)); break; case LANG_JAPANESE: // For Japanese, move the selection to the target row, // then send Right, then send Left. BYTE nVirtKey; if (nRow > (int)ImeUi_GetCandidateSelection()) nVirtKey = VK_DOWN; else nVirtKey = VK_UP; int nNumToHit = abs(int(nRow - ImeUi_GetCandidateSelection())); for (int nStrike = 0; nStrike < nNumToHit; ++nStrike) SendKey(nVirtKey); // Do this to close the candidate window without ending composition. SendKey(VK_RIGHT); SendKey(VK_LEFT); break; } } } else { // Horizontal candidate window // Determine which the character the click has hit. int nCharHit; int nTrail; s_CandList.HoriCand.XtoCP(pt.x - s_CandList.rcCandidate.left, &nCharHit, &nTrail); // Determine which candidate string the character belongs to. int nCandidate = ImeUi_GetCandidateCount() - 1; int nEntryStart = 0; for (UINT i = 0; i < ImeUi_GetCandidateCount(); ++i) { if (nCharHit >= nEntryStart) { // Haven't found it. nEntryStart += (int)wcslen(ImeUi_GetCandidate(i)) + 1; // plus space separator } else { // Found it. This entry starts at the right side of the click point, // so the char belongs to the previous entry. nCandidate = i - 1; break; } } // Now emulate keystrokes to select the candidate entry. switch (GetPrimaryLanguage()) { case LANG_CHINESE: case LANG_KOREAN: // For Chinese and Korean, simply send the number keystroke. SendKey((BYTE)('0' + nCandidate + 1)); break; } } return true; } } } // If we didn't care for the msg, let the parent process it. return CDXUTEditBox::HandleMouse(uMsg, pt, wParam, lParam); }
bool CIME::StaticMsgProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { HIMC hImc; if(!s_bEnableImeSystem) return false; #if defined(DEBUG) || defined(_DEBUG) m_bIMEStaticMsgProcCalled = true; #endif switch(uMsg) { case WM_ACTIVATEAPP: if(wParam) { // Populate s_Locale with the list of keyboard layouts. UINT cKL = GetKeyboardLayoutList(0, NULL); s_Locale.clear(); HKL *phKL = new HKL[cKL]; if(phKL) { GetKeyboardLayoutList(cKL, phKL); for(UINT i = 0; i < cKL; ++i) { CInputLocale Locale; // Filter out East Asian languages that are not IME. if((PRIMARYLANGID(LOWORD(phKL[i])) == LANG_CHINESE || PRIMARYLANGID(LOWORD(phKL[i])) == LANG_JAPANESE || PRIMARYLANGID(LOWORD(phKL[i])) == LANG_KOREAN) && !_ImmIsIME(phKL[i])) continue; // If this language is already in the list, don't add it again. bool bBreak = false; for(size_t e = 0; e < s_Locale.size(); ++e) if(LOWORD(s_Locale[e].m_hKL) == LOWORD(phKL[i])) { bBreak = true; break; } if(bBreak) break; Locale.m_hKL = phKL[i]; WCHAR wszDesc[128] = L""; switch(PRIMARYLANGID(LOWORD(phKL[i]))) { // Simplified Chinese case LANG_CHINESE: switch(SUBLANGID(LOWORD(phKL[i]))) { case SUBLANG_CHINESE_SIMPLIFIED: Locale.m_wstrLangAbb = s_aszIndicator[INDICATOR_CHS]; break; case SUBLANG_CHINESE_TRADITIONAL: Locale.m_wstrLangAbb = s_aszIndicator[INDICATOR_CHT]; break; default: // unsupported sub-language GetLocaleInfoW(MAKELCID(LOWORD(phKL[i]), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, wszDesc, 128); Locale.m_wstrLangAbb = wszDesc[0]; Locale.m_wstrLangAbb += towlower(wszDesc[1]); break; } break; // Korean case LANG_KOREAN: Locale.m_wstrLangAbb = s_aszIndicator[INDICATOR_KOREAN]; break; // Japanese case LANG_JAPANESE: Locale.m_wstrLangAbb = s_aszIndicator[INDICATOR_JAPANESE]; break; default: // A non-IME language. Obtain the language abbreviation // and store it for rendering the indicator later. GetLocaleInfoW(MAKELCID(LOWORD(phKL[i]), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, wszDesc, 128); Locale.m_wstrLangAbb = wszDesc[0]; Locale.m_wstrLangAbb += towlower(wszDesc[1]); break; } GetLocaleInfoW(MAKELCID(LOWORD(phKL[i]), SORT_DEFAULT), LOCALE_SLANGUAGE, wszDesc, 128); Locale.m_wstrLang = wszDesc; s_Locale.push_back(Locale); } delete[] phKL; } } break; case WM_INPUTLANGCHANGE: UITRACE(L"WM_INPUTLANGCHANGE\n"); { UINT uLang = GetPrimaryLanguage(); CheckToggleState(); if (uLang != GetPrimaryLanguage()) { // Korean IME always inserts on keystroke. Other IMEs do not. s_bInsertOnType = (GetPrimaryLanguage() == LANG_KOREAN); } // IME changed. Setup the new IME. SetupImeApi(); if(_ShowReadingWindow) { if (NULL != (hImc = ImmGetContext(UIGetHWND()))) { _ShowReadingWindow(hImc, false); _ImmReleaseContext(UIGetHWND(), hImc); } } } return true; case WM_IME_SETCONTEXT: UITRACE(L"WM_IME_SETCONTEXT\n"); // // We don't want anything to display, so we have to clear this // lParam = 0; return false; // Handle WM_IME_STARTCOMPOSITION here since // we do not want the default IME handler to see // this when our fullscreen app is running. case WM_IME_STARTCOMPOSITION: UITRACE(L"WM_IME_STARTCOMPOSITION\n"); ResetCompositionString(); // Since the composition string has its own caret, we don't render // the edit control's own caret to avoid double carets on screen. ///ghghgh s_bHideCaret = true; return true; case WM_IME_COMPOSITION: UITRACE(L"WM_IME_COMPOSITION\n"); return false; } return false; }