/* * @doc INTERNAL * * @module ime.cpp -- support for Win95 IME API | * * Most everything to do with FE composition string editing passes * through here. * * Authors: * Jon Matousek * Hon Wah Chan * Justin Voskuhl * * History: * 10/18/1995 jonmat Cleaned up level 2 code and converted it into * a class hierarchy supporting level 3. * * Copyright (c) 1995-1997 Microsoft Corporation. All rights reserved. */ #include "_common.h" #include "_cmsgflt.h" #include "_ime.h" #include "imeapp.h" #define HAVE_COMPOSITION_STRING() ( 0 != (lparam & (GCS_COMPSTR | GCS_COMPATTR))) #define CLEANUP_COMPOSITION_STRING() ( 0 == lparam ) #define HAVE_RESULT_STRING() ( 0 != (lparam & GCS_RESULTSTR)) ASSERTDATA /* * HRESULT StartCompositionGlue (CTextMsgFilter &TextMsgFilter) * * @func * Initiates an IME composition string edit. * @comm * Called from the message loop to handle WM_IME_STARTCOMPOSITION. * This is a glue routine into the IME object hierarchy. * * @devnote * We decide if we are going to do a level 2 or level 3 IME * composition string edit. Currently, the only reason to * create a level 2 IME is if the IME has a special UI, or it is * a "near caret" IME, such as the ones found in PRC and Taiwan. * Near caret simply means that a very small window opens up * near the caret, but not on or at the caret. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT StartCompositionGlue ( CTextMsgFilter &TextMsgFilter) // @parm containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "StartCompositionGlue"); if(TextMsgFilter.IsIMEComposition() && TextMsgFilter._ime->IsTerminated()) { delete TextMsgFilter._ime; TextMsgFilter._ime = NULL; } if(!TextMsgFilter.IsIMEComposition()) { if(TextMsgFilter._pTextSel->CanEdit(NULL) == NOERROR && !(TextMsgFilter._lFEFlags & ES_NOIME)) { // Hold notification if needed if (!(TextMsgFilter._fIMEAlwaysNotify)) TextMsgFilter._pTextDoc->SetNotificationMode(tomFalse); // If a special UI, or IME is "near caret", then drop into lev. 2 mode. DWORD imeProperties = ImmGetProperty(GetKeyboardLayout(0x0FFFFFFFF), IGP_PROPERTY, TextMsgFilter._fUsingAIMM); // use Unicode if not running under Win95 TextMsgFilter._fUnicodeIME = (imeProperties & IME_PROP_UNICODE) && !W32->OnWin95(); if ((imeProperties & IME_PROP_SPECIAL_UI) || !(imeProperties & IME_PROP_AT_CARET)) { TextMsgFilter._ime = new CIme_Lev2(TextMsgFilter); // level 2 IME. } else TextMsgFilter._ime = new CIme_Lev3(TextMsgFilter); // level 3 IME->TrueInline. } else // Protect or read-only or NOIME: TextMsgFilter._ime = new CIme_Protected; // Ignore all ime input } else { // Ignore further StartCompositionMsg. // Hanin 5.1 CHT symbol could cause multiple StartCompoisitonMsg. return S_OK; } if(TextMsgFilter.IsIMEComposition()) { long lSelFlags; HRESULT hResult; hResult = TextMsgFilter._pTextSel->GetFlags(&lSelFlags); if (hResult == NOERROR) { TextMsgFilter._fOvertypeMode = !!(lSelFlags & tomSelOvertype); if (TextMsgFilter._fOvertypeMode) TextMsgFilter._pTextSel->SetFlags(lSelFlags & ~tomSelOvertype); // Turn off overtype mode } TextMsgFilter._pTextDoc->IMEInProgress(tomTrue); // Inform client IME compostion in progress return TextMsgFilter._ime->StartComposition(TextMsgFilter); // Make the method call. } else TextMsgFilter._pTextDoc->SetNotificationMode(tomTrue); return S_FALSE; } /* * HRESULT CompositionStringGlue (const LPARAM lparam, CTextMsgFilter &TextMsgFilter) * * @func * Handle all intermediary and final composition strings. * * @comm * Called from the message loop to handle WM_IME_COMPOSITION. * This is a glue routine into the IME object hierarchy. * We may be called independently of a WM_IME_STARTCOMPOSITION * message, in which case we return S_FALSE to allow the * DefWindowProc to return WM_IME_CHAR messages. * * @devnote * Side Effect: the _ime object may be deleted if composition * string processing is finished. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT CompositionStringGlue ( const LPARAM lparam, // @parm associated with message. CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CompositionStringGlue"); HRESULT hr = S_FALSE; if(TextMsgFilter.IsIMEComposition()) // A priori fHaveIMMProcs. { TextMsgFilter._ime->_compMessageRefCount++; // For proper deletion. // Make the method call. hr = TextMsgFilter._ime->CompositionString(lparam, TextMsgFilter); TextMsgFilter._ime->_compMessageRefCount--; // For proper deletion. Assert (TextMsgFilter._ime->_compMessageRefCount >= 0); CheckDestroyIME (TextMsgFilter); // Finished processing? } else // even when not in composition mode, we may receive a result string. { DWORD imeProperties = ImmGetProperty(GetKeyboardLayout(0x0FFFFFFFF), IGP_PROPERTY, TextMsgFilter._fUsingAIMM); long lSelFlags; HRESULT hResult; long cpMin, cpMax; TextMsgFilter._pTextDoc->IMEInProgress(tomTrue); // Inform client IME compostion in progress hResult = TextMsgFilter._pTextSel->GetFlags(&lSelFlags); if (hResult == NOERROR) { TextMsgFilter._fOvertypeMode = !!(lSelFlags & tomSelOvertype); if (TextMsgFilter._fOvertypeMode) TextMsgFilter._pTextSel->SetFlags(lSelFlags & ~tomSelOvertype); // Turn off overtype mode } // use Unicode if not running under Win95 TextMsgFilter._fUnicodeIME = (imeProperties & IME_PROP_UNICODE) && !W32->OnWin95(); TextMsgFilter._pTextSel->GetStart(&cpMin); TextMsgFilter._pTextSel->GetEnd(&cpMax); if (cpMin != cpMax) TextMsgFilter._pTextSel->SetText(NULL); // Delete current selection CIme::CheckKeyboardFontMatching (cpMin, TextMsgFilter, NULL); hr = CIme::CheckInsertResultString(lparam, TextMsgFilter); if(TextMsgFilter._fOvertypeMode) TextMsgFilter._pTextSel->SetFlags(lSelFlags | tomSelOvertype); // Turn on overtype mode TextMsgFilter._pTextDoc->IMEInProgress(tomFalse); // Inform client IME compostion is done } return hr; } /* * HRESULT EndCompositionGlue (CTextMsgFilter &TextMsgFilter, BOOL fForceDelete) * * @func * Composition string processing is about to end. * * @comm * Called from the message loop to handle WM_IME_ENDCOMPOSITION. * This is a glue routine into the IME object hierarchy. * * @devnote * The only time we have to handle WM_IME_ENDCOMPOSITION is when the * user changes input method during typing. For such case, we will get * a WM_IME_ENDCOMPOSITION message without getting a WM_IME_COMPOSITION * message with GCS_RESULTSTR later. So, we will call CompositionStringGlue * with GCS_RESULTSTR to let CompositionString to get rid of the string. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT EndCompositionGlue ( CTextMsgFilter &TextMsgFilter, // @parm the containing message filter. BOOL fForceDelete) // @parm forec to terminate { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "EndCompositionGlue"); if(TextMsgFilter.IsIMEComposition()) { // ignore the EndComposition message if necessary. We may // get this from 3rd party IME - EGBRIGDE after we have received // both result and composition strings. if ( !(TextMsgFilter._ime->_fIgnoreEndComposition) ) { // Set this flag. If we are still in composition mode, then // let the CompositionStringGlue() to destroy the ime object. TextMsgFilter._ime->_fDestroy = TRUE; if (!fForceDelete) CompositionStringGlue(GCS_COMPSTR , TextMsgFilter); // Remove any remaining composition string. // Finished with IME, destroy it. CheckDestroyIME(TextMsgFilter); // Turn on undo TextMsgFilter._pTextDoc->Undo(tomResume, NULL); // Inform client IME compostion is done TextMsgFilter._pTextDoc->IMEInProgress(tomFalse); } else { // reset this so we will handle next EndComp msg TextMsgFilter._ime->_fIgnoreEndComposition = FALSE; } if(!TextMsgFilter.IsIMEComposition() && TextMsgFilter._fOvertypeMode) { long lSelFlags; HRESULT hResult; ITextSelection *pLocalTextSel = TextMsgFilter._pTextSel; BOOL fRelease = FALSE; if (!pLocalTextSel) { // Get the selection TextMsgFilter._pTextDoc->GetSelectionEx(&pLocalTextSel); fRelease = TRUE; } if (pLocalTextSel) { hResult = pLocalTextSel->GetFlags(&lSelFlags); if (hResult == NOERROR) pLocalTextSel->SetFlags(lSelFlags | tomSelOvertype); // Turn on overtype mode if (fRelease) pLocalTextSel->Release(); } } } return S_FALSE; } /* * HIMC LocalGetImmContext ( CTextMsgFilter &TextMsgFilter ) * * @func * Get Imm Context from host * */ HIMC LocalGetImmContext( CTextMsgFilter &TextMsgFilter) { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "IMEMessage"); HIMC hIMC = NULL; // Host's IME context. HRESULT hResult; hResult = TextMsgFilter._pTextDoc->GetImmContext((long *)&hIMC); if (hResult != NOERROR) hIMC = ImmGetContext(TextMsgFilter._hwnd); // Get host's IME context. return hIMC; } /* * void LocalReleaseImmContext ( CTextMsgFilter &TextMsgFilter, HIMC hIMC ) * * @func * call host to Release Imm Context * */ void LocalReleaseImmContext( CTextMsgFilter &TextMsgFilter, HIMC hIMC ) { HRESULT hResult; hResult = TextMsgFilter._pTextDoc->ReleaseImmContext((long)hIMC); if (hResult != NOERROR) ImmReleaseContext(TextMsgFilter._hwnd, hIMC); } /* * long IMEShareToTomUL ( UINT ulID ) * * @func * Convert IMEShare underline to Tom underline. * * @rdesc * Tom underline value */ long IMEShareToTomUL ( UINT ulID ) { long lTomUnderline; switch (ulID) { case IMESTY_UL_NONE: lTomUnderline = tomNone; break; case IMESTY_UL_DOTTED: lTomUnderline = tomDotted; break; case IMESTY_UL_THICK: case IMESTY_UL_THICKLOWER: lTomUnderline = tomThick; break; case IMESTY_UL_DITHLOWER: case IMESTY_UL_THICKDITHLOWER: lTomUnderline = tomWave; break; // case IMESTY_UL_SINGLE: // case IMESTY_UL_LOWER: default: lTomUnderline = tomSingle; break; } return lTomUnderline; } /* * void IMEMessage (CTextMsgFilter &TextMsgFilter , UINT uMsg, BOOL bPostMessage) * * @func * Either post or send message to IME * */ BOOL IMEMessage( CTextMsgFilter &TextMsgFilter, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL bPostMessage) { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "IMEMessage"); HIMC hIMC; // Host's IME context. HWND hwndIME; BOOL retCode = FALSE; HWND hHostWnd = TextMsgFilter._hwnd; long hWnd; if (!hHostWnd) // Windowless mode... { if (TextMsgFilter._pTextDoc->GetWindow(&hWnd) != S_OK || !hWnd) return FALSE; hHostWnd = (HWND)(DWORD_PTR)hWnd; } hIMC = LocalGetImmContext(TextMsgFilter); // Get host's IME context. if(hIMC) { hwndIME = ImmGetDefaultIMEWnd(hHostWnd, TextMsgFilter._fUsingAIMM); LocalReleaseImmContext(TextMsgFilter, hIMC); // check if we want to send or post message if (hwndIME) { if (bPostMessage) retCode = PostMessage(hwndIME, uMsg, wParam, lParam); else retCode = SendMessage(hwndIME, uMsg, wParam, lParam); } } return retCode; } /* * void CheckDestroyIME (CTextMsgFilter &TextMsgFilter) * * @func * Check for IME and see detroy if it needs it.. * */ void CheckDestroyIME ( CTextMsgFilter &TextMsgFilter) { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CheckDestroyIME"); if(TextMsgFilter.IsIMEComposition() && TextMsgFilter._ime->_fDestroy) { if(0 == TextMsgFilter._ime->_compMessageRefCount) { if (TextMsgFilter._uKeyBoardCodePage == CP_KOREAN) { TextMsgFilter._pTextDoc->SetCaretType(tomNormalCaret); // Reset Block caret mode TextMsgFilter._fHangulToHanja = FALSE; // Reset korean conversion mode } delete TextMsgFilter._ime; // All done with object. TextMsgFilter._ime = NULL; TextMsgFilter._pTextDoc->SetNotificationMode(tomTrue); // Turn on Notification } } } /* * void PostIMECharGlue (CTextMsgFilter &TextMsgFilter) * * @func * Called after processing a single WM_IME_CHAR in order to * update the position of the IME's composition window. This * is glue code to call the CIME virtual equivalent. */ void PostIMECharGlue ( CTextMsgFilter &TextMsgFilter) // @parm containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "PostIMECharGlue"); if(TextMsgFilter.IsIMEComposition()) TextMsgFilter._ime->PostIMEChar(TextMsgFilter); } /* * BOOL IMEMouseCheck(CTextMsgFilter &TextMsgFilter, UINT *pmsg, * WPARAM *pwparam, LPARAM *plparam, LRESULT *plres) * * @func * Called when receiving a mouse event. Need to pass this event * to MSIME98 for composition handling * */ HRESULT IMEMouseCheck( CTextMsgFilter &TextMsgFilter, // @parm MsgFilter UINT *pmsg, // @parm the message WPARAM *pwparam, // @parm WParam LPARAM *plparam, // @parm LParam LRESULT *plres) // @parm Lresult { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "IMEMouseCheck"); BOOL retCode = FALSE; if(TextMsgFilter.IsIMEComposition()) { retCode = TextMsgFilter._ime->IMEMouseOperation(TextMsgFilter, *pmsg); if ( retCode == FALSE && WM_MOUSEMOVE != *pmsg ) TextMsgFilter._ime->TerminateIMEComposition(TextMsgFilter, CIme::TERMINATE_NORMAL); } return retCode ? S_OK : S_FALSE; } /* * HRESULT IMENotifyGlue (const WPARAM wparam, const LPARAM lparam, * CTextMsgFilter &TextMsgFilter) * * @func * IME is going to change some state. * * @comm * Currently we are interested in knowing when the candidate * window is about to be opened. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT IMENotifyGlue ( const WPARAM wparam, // @parm associated with message. const LPARAM lparam, // @parm associated with message. CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "IMENotifyGlue"); if (TextMsgFilter._fRE10Mode && (wparam == IMN_SETCONVERSIONMODE || wparam == IMN_SETSENTENCEMODE || wparam == IMN_SETOPENSTATUS)) { TextMsgFilter._pTextDoc->Notify(EN_IMECHANGE); } else if(TextMsgFilter.IsIMEComposition()) // A priori fHaveIMMProcs. return TextMsgFilter._ime->IMENotify(wparam, lparam, TextMsgFilter, FALSE);// Make the method call return S_FALSE; } /* * void IMECompositionFull (&TextMsgFilter) * * @func * Current IME Composition window is full. * * @comm * Called from the message loop to handle WM_IME_COMPOSITIONFULL. * This message applied to Level 2 only. We will use the default * IME Composition window. */ void IMECompositionFull ( CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "IMECompositionFull"); if(TextMsgFilter.IsIMEComposition()) { HIMC hIMC = LocalGetImmContext(TextMsgFilter); COMPOSITIONFORM cf; if(hIMC) { // No room for text input in the current level 2 IME window, // fall back to use the default IME window for input. cf.dwStyle = CFS_DEFAULT; ImmSetCompositionWindow(hIMC, &cf, TextMsgFilter._fUsingAIMM); // Set composition window. LocalReleaseImmContext(TextMsgFilter, hIMC); // Done with IME context. } } } /* * LRESULT OnGetIMECompositionMode (CTextMsgFilter &TextMsgFilter) * * @mfunc * Returns whether or not IME composition is being handled by RE, * and if so, what level of processing. * * @rdesc * One of ICM_NOTOPEN, ICM_LEVEL2_5, ICM_LEVEL2_SUI, ICM_LEVEL2, ICM_LEVEL3. */ LRESULT OnGetIMECompositionMode ( CTextMsgFilter &TextMsgFilter) // @parm containing message filter. { LRESULT lres = ICM_NOTOPEN; if(TextMsgFilter.IsIMEComposition()) { if(IME_LEVEL_2 == TextMsgFilter._ime->_imeLevel) { DWORD imeProperties; imeProperties = ImmGetProperty(GetKeyboardLayout(0x0FFFFFFFF), IGP_PROPERTY, TextMsgFilter._fUsingAIMM); if(imeProperties & IME_PROP_AT_CARET) lres = ICM_LEVEL2_5; // level 2.5. else if (imeProperties & IME_PROP_SPECIAL_UI) lres = ICM_LEVEL2_SUI; // special UI. else lres = ICM_LEVEL2; // stock level 2. } else if(IME_LEVEL_3 == TextMsgFilter._ime->_imeLevel) lres = ICM_LEVEL3; } return lres; } /* * void CIme::CheckKeyboardFontMatching (long cp, CTextMsgFilter &TextMsgFilter, ITextFont *pTextFont) * * @mfunc * Setup current font to matches the keyboard Codepage. * * @comm * Called from CIme_Lev2::CIme_Lev2 and CompositionStringGlue * * @devnote * We need to switch to a preferred font for the keyboard during IME input. * Otherwise, we will display garbage. * */ void CIme::CheckKeyboardFontMatching ( long cp, CTextMsgFilter &TextMsgFilter, ITextFont *pTextFont) { long lPitchAndFamily; HRESULT hResult; BSTR bstr = NULL; long lValue; long lNewFontSize=0; float nFontSize; ITextFont *pLocalFont = NULL; if (!pTextFont) { // No font supplied, get current font from selection hResult = TextMsgFilter._pTextSel->GetFont(&pLocalFont); if (hResult != S_OK || !pLocalFont) // Can't get font, forget it return; pTextFont = pLocalFont; } // Check if current font matches the keyboard lValue = tomCharset; hResult = pTextFont->GetLanguageID(&lValue); if (hResult == S_OK) if ((BYTE)(lValue) == (BYTE)GetCharSet(TextMsgFilter._uKeyBoardCodePage)) goto Exit; // Current font is fine hResult = pTextFont->GetSize(&nFontSize); if (hResult != S_OK) goto Exit; hResult = TextMsgFilter._pTextDoc->GetPreferredFont(cp, TextMsgFilter._uKeyBoardCodePage, tomMatchFontCharset, GetCodePage((BYTE)(lValue)), (long)nFontSize, &bstr, &lPitchAndFamily, &lNewFontSize); if (hResult == S_OK) { if (bstr) pTextFont->SetName(bstr); // Set the font charset and Pitch&Family by overloading the SetLanguageID i/f lValue = tomCharset + (((BYTE)lPitchAndFamily) << 8) + (BYTE)GetCharSet(TextMsgFilter._uKeyBoardCodePage); pTextFont->SetLanguageID(lValue); if (lNewFontSize) pTextFont->SetSize((float)lNewFontSize); } Exit: if (pLocalFont) pLocalFont->Release(); if (bstr) SysFreeString(bstr); } /* * INT CIme::GetCompositionStringInfo(HIMC hIMC, DWORD dwIndex, * WCHAR *szCompStr, INT cchMax, BYTE *attrib, INT cbAttrib * LONG cchAttrib, UINT kbCodePage, BOOL bUnicodeIME) * * @mfunc * For WM_IME_COMPOSITION string processing to get the requested * composition string, by type, and convert it to Unicode. * * @devnote * We must use ImmGetCompositionStringA because W is not supported * on Win95. * * @rdesc * INT-cch of the Unicode composition string. * Out param in szCompStr. */ INT CIme::GetCompositionStringInfo( HIMC hIMC, // @parm IME context provided by host. DWORD dwIndex, // @parm The type of composition string. WCHAR *szCompStr, // @parm Out param, unicode result string. INT cchMax, // @parm The cch for the Out param. BYTE *attrib, // @parm Out param, If attribute info is needed. INT cbMax, // @parm The cb of the attribute info. LONG *cpCursor, // @parm Out param, returns the CP of cusor. LONG *cchAttrib, // @parm how many attributes returned. UINT kbCodePage, // @parm codepage BOOL bUnicodeIME, // @parm Unciode IME BOOL bUsingAimm) // @parm Using Aimm { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme::GetCompositionStringInfo"); BYTE compStr[256], attribInfo[256]; INT i, j, iMax, cchCompStr=0, cbAttrib, cursor; INT cchAnsiCompStr=0; Assert(hIMC && szCompStr); if(cpCursor) // Init cursor out param. *cpCursor = -1; if(cchAttrib) *cchAttrib = 0; // Get composition string. if (bUnicodeIME) cchCompStr = ImmGetCompositionStringW(hIMC, dwIndex, szCompStr, cchMax, bUsingAimm )/sizeof(WCHAR); else cchAnsiCompStr = ImmGetCompositionStringA(hIMC, dwIndex, compStr, 255, bUsingAimm); if(cchAnsiCompStr > 0 || cchCompStr > 0) // If valid data. { if (!bUnicodeIME) { Assert(cchAnsiCompStr >> 1 < cchMax - 1); // Convert to Unicode. cchCompStr = UnicodeFromMbcs(szCompStr, cchMax, (CHAR *) compStr, cchAnsiCompStr, kbCodePage); } if(attrib || cpCursor) // Need cursor or attribs? { if (bUnicodeIME) { // Get Unicode Cursor cp. cursor = ImmGetCompositionStringW(hIMC, GCS_CURSORPOS, NULL, 0, bUsingAimm); // Get Unicode attributes. cbAttrib = ImmGetCompositionStringW(hIMC, GCS_COMPATTR, attribInfo, 255, bUsingAimm); iMax = max(cursor, cbAttrib); iMax = min(iMax, cchCompStr); } else { // Get DBCS Cursor cp. cursor = ImmGetCompositionStringA(hIMC, GCS_CURSORPOS, NULL, 0, bUsingAimm); // Get DBCS attributes. cbAttrib = ImmGetCompositionStringA(hIMC, GCS_COMPATTR, attribInfo, 255, bUsingAimm); iMax = max(cursor, cbAttrib); iMax = min(iMax, cchAnsiCompStr); } if(NULL == attrib) cbMax = cbAttrib; for(i = 0, j = 0; i <= iMax && j < cbMax; i++, j++) { if(cursor == i) cursor = j; if(!bUnicodeIME && GetTrailBytesCount(compStr[i], kbCodePage)) i++; if(attrib && i < cbAttrib) *attrib++ = attribInfo[i]; } // attrib cch==unicode cch Assert(0 >= cbAttrib || j-1 == cchCompStr); if(cursor >= 0 && cpCursor) // If client needs cursor *cpCursor = cursor; // or cchAttrib. if(cbAttrib >= 0 && cchAttrib) *cchAttrib = j-1; } } else { if(cpCursor) *cpCursor = 0; cchCompStr = 0; } return cchCompStr; } /* * void CIme::SetCompositionFont (CTextMsgFilter &TextMsgFilter, ITextFont *pTextFont) * * @mfunc * Important for level 2 IME so that the composition window * has the correct font. The lfw to lfa copy is due to the fact that * Win95 does not support the W)ide call. * It is also important for both level 2 and level 3 IME so that * the candidate list window has the proper. font. */ void CIme::SetCompositionFont ( CTextMsgFilter &TextMsgFilter, // @parm the containing message filter. ITextFont *pTextFont) // @parm ITextFont for setting lfa. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme::SetCompositionFont"); HIMC hIMC; LOGFONTA lfa; if (pTextFont) { hIMC = LocalGetImmContext(TextMsgFilter); if (hIMC) { // Build the LOGFONT based on pTextFont float FontSize; long lValue; BSTR bstr; memset (&lfa, 0, sizeof(lfa)); if (pTextFont->GetSize(&FontSize) == NOERROR) lfa.lfHeight = (LONG) FontSize; if (pTextFont->GetBold(&lValue) == NOERROR && lValue == tomTrue) lfa.lfWeight = FW_BOLD; if (pTextFont->GetItalic(&lValue) == NOERROR && lValue == tomTrue) lfa.lfItalic = TRUE; lfa.lfCharSet = (BYTE)GetCharSet(TextMsgFilter._uKeyBoardCodePage); lValue = tomCharset; if (pTextFont->GetLanguageID(&lValue) == NOERROR && lfa.lfCharSet == (BYTE)lValue) lfa.lfPitchAndFamily = (BYTE)(lValue >> 8); if (pTextFont->GetName(&bstr) == NOERROR) { MbcsFromUnicode(lfa.lfFaceName, sizeof(lfa.lfFaceName), bstr, -1, CP_ACP, UN_NOOBJECTS); SysFreeString(bstr); } ImmSetCompositionFontA( hIMC, &lfa, TextMsgFilter._fUsingAIMM ); LocalReleaseImmContext(TextMsgFilter, hIMC); // Done with IME context. } } } /* * void CIme::SetCompositionForm (CTextMsgFilter &TextMsgFilter) * * @mfunc * Important for level 2 IME so that the composition window * is positioned correctly. * * @comm * We go through a lot of work to get the correct height. This requires * getting information from the font cache and the selection. */ void CIme::SetCompositionForm ( CTextMsgFilter &TextMsgFilter) // @parm the containing text edit. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme::SetCompositionForm"); HIMC hIMC; COMPOSITIONFORM cf; if(IME_LEVEL_2 == GetIMELevel()) { hIMC = LocalGetImmContext(TextMsgFilter); // Get IME context. if(hIMC) { // get the location of cpMin cf.ptCurrentPos.x = cf.ptCurrentPos.y = 0; TextMsgFilter._pTextSel->GetPoint( tomStart+tomClientCoord+TA_BOTTOM+TA_LEFT, &(cf.ptCurrentPos.x), &(cf.ptCurrentPos.y) ); // Set-up bounding rect. for the IME (lev 2) composition window, causing // composition text to be wrapped within it. cf.dwStyle = CFS_RECT; TextMsgFilter._pTextDoc->GetClientRect(tomIncludeInset+tomClientCoord, &(cf.rcArea.left), &(cf.rcArea.top), &(cf.rcArea.right), &(cf.rcArea.bottom)); // Make sure the starting point is not // outside the rcArea. This happens when // there is no text on the current line and the user // has selected a large font size. if(cf.ptCurrentPos.y < cf.rcArea.top) cf.ptCurrentPos.y = cf.rcArea.top; else if(cf.ptCurrentPos.y > cf.rcArea.bottom) cf.ptCurrentPos.y = cf.rcArea.bottom; if(cf.ptCurrentPos.x < cf.rcArea.left) cf.ptCurrentPos.x = cf.rcArea.left; else if(cf.ptCurrentPos.x > cf.rcArea.right) cf.ptCurrentPos.x = cf.rcArea.right; ImmSetCompositionWindow(hIMC, &cf, TextMsgFilter._fUsingAIMM); // Set composition window. LocalReleaseImmContext(TextMsgFilter, hIMC); // Done with IME context. } } } /* * * CIme::TerminateIMEComposition (CTextMsgFilter &TextMsgFilter) * * @mfunc Terminate the IME Composition mode using CPS_COMPLETE * @comm The IME will generate WM_IME_COMPOSITION with the result string * */ void CIme::TerminateIMEComposition( CTextMsgFilter &TextMsgFilter, // @parm the containing message filter. TerminateMode mode) { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme::TerminateIMEComposition"); DWORD dwTerminateMethod; HIMC hIMC = LocalGetImmContext(TextMsgFilter); if(TextMsgFilter.IsIMEComposition() && TextMsgFilter._ime->IsTerminated()) { // Turn if off now EndCompositionGlue(TextMsgFilter, TRUE); return; } _fIMETerminated = TRUE; if (mode == TERMINATE_FORCECANCEL) TextMsgFilter._pTextDoc->IMEInProgress(tomFalse); // Inform client IME compostion is done dwTerminateMethod = CPS_COMPLETE; if (IME_LEVEL_2 == GetIMELevel() || // force cancel for near-caret IME mode == TERMINATE_FORCECANCEL || // caller wants force cancel TextMsgFilter._fIMECancelComplete) // Client wants force cancel { dwTerminateMethod = CPS_CANCEL; } // force the IME to terminate the current session if(hIMC) { BOOL retCode; retCode = ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, dwTerminateMethod, 0, TextMsgFilter._fUsingAIMM); if(!retCode && !TextMsgFilter._fIMECancelComplete) { // CPS_COMPLETE fail, try CPS_CANCEL. This happen with some ime which do not support // CPS_COMPLETE option (e.g. ABC IME version 4 with Win95 simplified Chinese) retCode = ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0, TextMsgFilter._fUsingAIMM); } LocalReleaseImmContext(TextMsgFilter, hIMC); } else { // for some reason, we didn't have a context, yet we thought we were still in IME // compostition mode. Just force a shutdown here. EndCompositionGlue(TextMsgFilter, TRUE); } } /* * CIme_Lev2::CIme_Lev2() * * @mfunc * CIme_Lev2 Constructor/Destructor. * * @comm * Needed to make sure _iFormatSave was handled properly. * */ CIme_Lev2::CIme_Lev2( CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { long cpMin, cpMax, cpLoc; HRESULT hResult; ITextFont *pCurrentFont = NULL; _pTextFont = NULL; _cIgnoreIMECharMsg = 0; // setup base Font format for later use during composition hResult = TextMsgFilter._pTextSel->GetStart(&cpMin); cpLoc = cpMin; if (TextMsgFilter._fHangulToHanja) cpMax = cpMin + 1; // Select the Hangul character else hResult = TextMsgFilter._pTextSel->GetEnd(&cpMax); _fSkipFirstOvertype = FALSE; if (cpMax != cpMin) { // selection case, get format for at cpMin ITextRange *pTextRange; HRESULT hResult; hResult = TextMsgFilter._pTextDoc->Range(cpMin, cpMin+1, &pTextRange); Assert (pTextRange != NULL); if (hResult == NOERROR && pTextRange) { pTextRange->GetFont(&pCurrentFont); Assert(pCurrentFont != NULL); pTextRange->Release(); cpLoc = cpMin+1; } if (!TextMsgFilter._fHangulToHanja) _fSkipFirstOvertype = TRUE; // For Korean Overtype support } if (!pCurrentFont) TextMsgFilter._pTextSel->GetFont(&pCurrentFont); Assert(pCurrentFont != NULL); pCurrentFont->GetDuplicate(&_pTextFont); // duplicate the base format for later use pCurrentFont->Release(); Assert(_pTextFont != NULL); // setup font to match current keyboard CIme::CheckKeyboardFontMatching (cpLoc, TextMsgFilter, _pTextFont); _fIgnoreEndComposition = FALSE; _fIMETerminated = FALSE; } CIme_Lev2::~CIme_Lev2() { if ( _pTextFont ) _pTextFont->Release(); } /* * HRESULT CIme_Lev2::StartComposition(CTextMsgFilter &TextMsgFilter) * * @mfunc * Begin IME Level 2 composition string processing. * * @comm * Set the font, and location of the composition window which includes * a bounding rect and the start position of the cursor. Also, reset * the candidate window to allow the IME to set its position. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT CIme_Lev2::StartComposition( CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_Lev2::StartComposition"); _imeLevel = IME_LEVEL_2; SetCompositionFont(TextMsgFilter, _pTextFont); // Set font, & comp window. SetCompositionForm(TextMsgFilter); return S_FALSE; // Allow DefWindowProc } // processing. /* * HRESULT CIme_Lev2::CompositionString(const LPARAM lparam, CTextMsgFilter &TextMsgFilter) * * @mfunc * Handle Level 2 WM_IME_COMPOSITION messages. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing. * * Side effect: * The Host needs to mask out the lparam before calling DefWindowProc to * prevent unnessary WM_IME_CHAR messages. */ HRESULT CIme_Lev2::CompositionString ( const LPARAM lparam, // @parm associated with message. CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_Lev2::CompositionString"); _cIgnoreIMECharMsg = 0; if(HAVE_RESULT_STRING()) { if (_pTextFont) { // setup the font before insert final string ITextFont *pFETextFont=NULL; _pTextFont->GetDuplicate(&pFETextFont); Assert(pFETextFont != NULL); TextMsgFilter._pTextSel->SetFont(pFETextFont); pFETextFont->Release(); } TextMsgFilter._pTextDoc->SetNotificationMode(tomTrue); CheckInsertResultString(lparam, TextMsgFilter, &_cIgnoreIMECharMsg); SetCompositionForm(TextMsgFilter); // Move Composition window. } // Always return S_FALSE so the DefWindowProc will handle the rest. // Host has to mask out the ResultString bit to avoid WM_IME_CHAR coming in. return S_FALSE; } /* * HRESULT CIme::CheckInsertResultString (const LPARAM lparam, CTextMsgFilter &TextMsgFilter) * * @mfunc * handle inserting of GCS_RESULTSTR text, the final composed text. * * @comm * When the final composition string arrives we grab it and set it into the text. * * @devnote * A GCS_RESULTSTR message can arrive and the IME will *still* be in * composition string mode. This occurs because the IME's internal * buffers overflowed and it needs to convert the beginning of the buffer * to clear out some room. When this happens we need to insert the * converted text as normal, but remain in composition processing mode. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT CIme::CheckInsertResultString ( const LPARAM lparam, // @parm associated with message. CTextMsgFilter &TextMsgFilter, // @parm the containing message filter. short *pcch) // @parm number of character read { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CheckInsertResultString"); HRESULT hr = S_FALSE; HIMC hIMC; INT cch; WCHAR szCompStr[256]; if(CLEANUP_COMPOSITION_STRING() || HAVE_RESULT_STRING()) // If result string.. { hIMC = LocalGetImmContext(TextMsgFilter); // Get host's IME context. cch = 0; if(hIMC) // Get result string. { cch = GetCompositionStringInfo(hIMC, GCS_RESULTSTR, szCompStr, sizeof(szCompStr)/sizeof(szCompStr[0]), NULL, 0, NULL, NULL, TextMsgFilter._uKeyBoardCodePage, TextMsgFilter._fUnicodeIME, TextMsgFilter._fUsingAIMM); if (pcch) *pcch = (short)cch; cch = min (cch, 255); szCompStr[cch] = L'\0'; LocalReleaseImmContext(TextMsgFilter, hIMC); // Done with IME context. } // Don't need to replace range when there isn't any text. Otherwise, the character format is // reset to previous run. if(cch) { BSTR bstr = SysAllocString(szCompStr); if (!bstr) return E_OUTOFMEMORY; TextMsgFilter._pTextSel->TypeText(bstr); SysFreeString(bstr); } hr = S_OK; // Don't want WM_IME_CHARs. } return hr; } /* * HRESULT CIme_Lev2::IMENotify(const WPARAM wparam, const LPARAM lparam, * CTextMsgFilter &TextMsgFilter) * * @mfunc * Handle Level 2 WM_IME_NOTIFY messages. * * @comm * Currently we are only interested in knowing when to reset * the candidate window's position. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT CIme_Lev2::IMENotify( const WPARAM wparam, // @parm associated with message. const LPARAM lparam, // @parm associated with message. CTextMsgFilter &TextMsgFilter, // @parm the containing message filter. BOOL fIgnore) // @parm Level3 Chinese Composition window only { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_Lev2::IMENotify"); if(IMN_OPENCANDIDATE == wparam) { Assert (0 != lparam); HIMC hIMC; // Host's IME context. INT index; // Candidate window index. CANDIDATEFORM cdCandForm; hIMC = LocalGetImmContext(TextMsgFilter); // Get host's IME context. if(hIMC) { // Convert bitID to INDEX. for (index = 0; index < 32; index++) // because API. { if((1 << index) & lparam) break; } Assert (((1 << index) & lparam) == lparam); // Only 1 set? Assert (index < 32); // Reset to CFS_DEFAULT if(ImmGetCandidateWindow(hIMC, index, &cdCandForm, TextMsgFilter._fUsingAIMM) && CFS_DEFAULT != cdCandForm.dwStyle) { cdCandForm.dwStyle = CFS_DEFAULT; ImmSetCandidateWindow(hIMC, &cdCandForm, TextMsgFilter._fUsingAIMM); } LocalReleaseImmContext(TextMsgFilter, hIMC); // Done with IME context. } } return S_FALSE; // Allow DefWindowProc } // processing. /* * void CIme_Lev2::PostIMEChar (CTextMsgFilter &TextMsgFilter) * * @mfunc * Called after processing a single WM_IME_CHAR in order to * update the position of the IME's composition window. * */ void CIme_Lev2::PostIMEChar ( CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_Lev2::PostIMEChar"); SetCompositionForm(TextMsgFilter); // Move Composition window. } /* * CIme_Lev3::CIme_Lev3() * * @mfunc * CIme_Lev3 Constructor/Destructor. * */ CIme_Lev3::CIme_Lev3( CTextMsgFilter &TextMsgFilter) : CIme_Lev2 ( TextMsgFilter ) { _sIMESuportMouse = 0; // initial to 0 so we will check mouse operation if need _wParamBefore = 0; _fUpdateWindow = FALSE; } /* * HRESULT CIme_Lev3::StartComposition(CTextMsgFilter &TextMsgFilter) * * @mfunc * Begin IME Level 3 composition string processing. * * @comm * For rudimentary processing, remember the start and * length of the selection. Set the font in case the * candidate window actually uses this information. * * @rdesc * This is a rudimentary solution for remembering were * the composition is in the text. There needs to be work * to replace this with a composition "range". * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT CIme_Lev3::StartComposition( CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_Lev3::StartComposition"); long cpMin; TextMsgFilter._pTextSel->GetStart(&cpMin); _ichStart = cpMin; _cchCompStr = 0; _imeLevel = IME_LEVEL_3; SetCompositionFont (TextMsgFilter, _pTextFont); // Delete current selection TextMsgFilter._pTextSel->SetText(NULL); // turn off undo TextMsgFilter._pTextDoc->Undo(tomSuspend, NULL); if (_pTextFont) { _pTextFont->GetForeColor(&_crTextColor); _pTextFont->GetBackColor(&_crBkColor); } return S_OK; // No DefWindowProc } // processing. /* * HRESULT CIme_Lev3::CompositionString(const LPARAM lparam, CTextMsgFilter &TextMsgFilter) * * @mfunc * Handle Level 3 WM_IME_COMPOSITION messages. * * @comm * Display all of the intermediary composition text as well as the final * reading. * * @devnote * This is a rudimentary solution for replacing text in the backing store. * Work is left to do with the undo list, underlining, and hiliting with * colors and the selection. * * @devnote * A GCS_RESULTSTR message can arrive and the IME will *still* be in * composition string mode. This occurs because the IME's internal * buffers overflowed and it needs to convert the beginning of the buffer * to clear out some room. When this happens we need to insert the * converted text as normal, but remain in composition processing mode. * * Another reason, GCS_RESULTSTR can occur while in composition mode * for Korean because there is only 1 correct choice and no additional * user intervention is necessary, meaning that the converted string can * be sent as the result before composition mode is finished. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT CIme_Lev3::CompositionString( const LPARAM lparam, // @parm associated with message. CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_Lev3::CompositionString"); long cpMin; _fIgnoreEndComposition = FALSE; if (_fUpdateWindow) { TextMsgFilter._pTextDoc->UpdateWindow(); _fUpdateWindow = FALSE; } if(CLEANUP_COMPOSITION_STRING() || HAVE_RESULT_STRING()) // Any final readings? { long lCount; if (!CLEANUP_COMPOSITION_STRING()) TextMsgFilter._pTextDoc->Freeze(&lCount); // Turn off display if (_cchCompStr) { ITextRange *pTextRange = NULL; // Create a range to delete composition text TextMsgFilter._pTextDoc->Range(_ichStart, _ichStart + _cchCompStr, &pTextRange); Assert (pTextRange != NULL); // delete composition text pTextRange->SetText(NULL); pTextRange->Release(); _cchCompStr = 0; // be in composition mode. }; // setup the font before insert final string ITextFont *pFETextFont; _pTextFont->GetDuplicate(&pFETextFont); Assert(pFETextFont != NULL); TextMsgFilter._pTextSel->SetFont(pFETextFont); pFETextFont->Release(); // turn on undo TextMsgFilter._pTextDoc->Undo(tomResume, NULL); // Turn on Notification again TextMsgFilter._pTextDoc->SetNotificationMode(tomTrue); // get final string CheckInsertResultString(lparam, TextMsgFilter); if (!CLEANUP_COMPOSITION_STRING()) TextMsgFilter._pTextDoc->Unfreeze(&lCount); // Turn on display // Reset as we may still in Composition TextMsgFilter._pTextSel->GetStart(&cpMin); _ichStart = cpMin; // turn off undo for Korean IME since we will get Composition string message // again without getting EndComposition if (TextMsgFilter._uKeyBoardCodePage == CP_KOREAN) { TextMsgFilter._pTextDoc->Undo(tomSuspend, NULL); } } if(HAVE_COMPOSITION_STRING()) // In composition mode? { HIMC hIMC; INT cchOld = _cchCompStr; LONG cpCursor = 0, cchAttrib = 0; LONG i, j; // For applying attrib effects. WCHAR szCompStr[256]; BYTE startAttrib, attrib[256]; BSTR bstr = NULL; ITextRange *pTextRange = NULL; long cpMax; long lCount; _cchCompStr = 0; hIMC = LocalGetImmContext(TextMsgFilter); // Get host's IME context. if(hIMC) // Get composition string. { _cchCompStr = GetCompositionStringInfo(hIMC, GCS_COMPSTR, szCompStr, sizeof(szCompStr)/sizeof(szCompStr[0]), attrib, sizeof(attrib)/sizeof(attrib[0]), &cpCursor, &cchAttrib, TextMsgFilter._uKeyBoardCodePage, TextMsgFilter._fUnicodeIME, TextMsgFilter._fUsingAIMM); _cchCompStr = min (_cchCompStr, 255); szCompStr[_cchCompStr] = L'\0'; LocalReleaseImmContext(TextMsgFilter, hIMC); // Done with IME context. } // any new composition string? if(_cchCompStr) { long cchExced = 0; if (TextMsgFilter._pTextDoc->CheckTextLimit(_cchCompStr-cchOld, &cchExced) == NOERROR && cchExced > 0) { // We reach text limit, beep... TextMsgFilter._pTextDoc->SysBeep(); if (_cchCompStr > cchExced) _cchCompStr -= cchExced; else _cchCompStr = 0; szCompStr[_cchCompStr] = L'\0'; if (!_cchCompStr && TextMsgFilter._uKeyBoardCodePage == CP_KOREAN) TextMsgFilter._pTextDoc->SetCaretType(tomNormalCaret); // Turn off Block caret mode } bstr = SysAllocString(szCompStr); if (!bstr) return E_OUTOFMEMORY; if (HAVE_RESULT_STRING()) { // ignore next end composition _fIgnoreEndComposition = TRUE; // turn off undo TextMsgFilter._pTextDoc->Undo(tomSuspend, NULL); // Hold notification if needed if (!(TextMsgFilter._fIMEAlwaysNotify)) TextMsgFilter._pTextDoc->SetNotificationMode(tomFalse); // Get the new format that may have changed by apps (e.g. Outlook) _pTextFont->Release(); ITextFont *pCurrentFont = NULL; TextMsgFilter._pTextSel->GetFont(&pCurrentFont); Assert(pCurrentFont != NULL); pCurrentFont->GetDuplicate(&_pTextFont); // duplicate the base format for later use pCurrentFont->Release(); Assert(_pTextFont != NULL); CIme::CheckKeyboardFontMatching (_ichStart, TextMsgFilter, _pTextFont); } } if (cchOld || _cchCompStr) { bool fFreezeDisplay = false; // Hold notification if needed if (!(TextMsgFilter._fIMEAlwaysNotify)) TextMsgFilter._pTextDoc->SetNotificationMode(tomFalse); // We only support overtype mode in Korean IME if (!cchOld && TextMsgFilter._uKeyBoardCodePage == CP_KOREAN && TextMsgFilter._fOvertypeMode && !_fSkipFirstOvertype) { long cCurrentChar; HRESULT hResult; // Create a range using the next character hResult = TextMsgFilter._pTextDoc->Range(_ichStart, _ichStart+1, &pTextRange); Assert (pTextRange != NULL); // Check if it is par character. If so, we don't want to // delete it. hResult = pTextRange->GetChar(&cCurrentChar); if (hResult == NOERROR) { if (cCurrentChar != (long)'\r' && cCurrentChar != (long)'\n') { TextMsgFilter._pTextDoc->Undo(tomResume, NULL); // Turn on undo pTextRange->SetText(NULL); // Delete the character TextMsgFilter._pTextDoc->Undo(tomSuspend, NULL); // Turn off undo } else { // Unselect the par character hResult = pTextRange->SetRange(_ichStart, _ichStart); } } } else { // Create a range using the preivous composition text and delete the text TextMsgFilter._pTextDoc->Range(_ichStart, _ichStart+cchOld, &pTextRange); Assert (pTextRange != NULL); if (cchOld) { if (cpCursor) { TextMsgFilter._pTextDoc->Freeze(&lCount); // Turn off display fFreezeDisplay = true; } pTextRange->SetText(NULL); } } _fSkipFirstOvertype = FALSE; if (cpCursor && !fFreezeDisplay) TextMsgFilter._pTextDoc->Freeze(&lCount); // Turn off display // Make sure the composition string is formatted with the base font ITextFont *pFETextFont; HRESULT hResult; hResult = _pTextFont->GetDuplicate(&pFETextFont); Assert(pFETextFont != NULL); if (!(hResult != NOERROR || pFETextFont == NULL)) { if (TextMsgFilter._fHangulToHanja) // Hangul to Hanja mode, setup font for selection to // handle the Hanja character the come in after the end composition // message TextMsgFilter._pTextSel->SetFont(pFETextFont); else pTextRange->SetFont(pFETextFont); } pTextRange->SetText(bstr); // Replace with the new text if (pFETextFont) pFETextFont->Release(); // update how many composition characters have been added pTextRange->GetEnd(&cpMax); _cchCompStr = cpMax - _ichStart; if (TextMsgFilter._uKeyBoardCodePage == CP_KOREAN) { // no formatting for Korean POINT ptBottomPos; if (cpCursor) TextMsgFilter._pTextDoc->Unfreeze(&lCount); // Turn on display if (pTextRange->GetPoint( tomEnd+TA_BOTTOM+TA_RIGHT, &(ptBottomPos.x), &(ptBottomPos.y) ) != NOERROR) pTextRange->ScrollIntoView(tomEnd); // Setup Block caret mode TextMsgFilter._pTextDoc->SetCaretType(_cchCompStr ? tomKoreanBlockCaret : tomNormalCaret); } else if (_cchCompStr && _cchCompStr <= cchAttrib) { for ( i = 0; i < _cchCompStr; ) // Parse the attributes... { // to apply styles. ITextFont *pFETextFont; HRESULT hResult; hResult = _pTextFont->GetDuplicate(&pFETextFont); Assert(pFETextFont != NULL); if (hResult != NOERROR || pFETextFont == NULL) break; // Rsest the clone font so we will only apply effects returned // from SetCompositionStyle pFETextFont->Reset(tomUndefined); startAttrib = attrib[i]; // Get attrib's run length. for ( j = i+1; j < _cchCompStr; j++ ) { if ( startAttrib != attrib[j] ) // Same run until diff. break; } SetCompositionStyle(TextMsgFilter, startAttrib, pFETextFont); // Apply FE clause's style pTextRange->SetRange(_ichStart+i, _ichStart+j); pTextRange->SetFont(pFETextFont); pFETextFont->Release(); i = j; } } pTextRange->Release(); } else if (TextMsgFilter._uKeyBoardCodePage == CP_KOREAN) TextMsgFilter._pTextDoc->Update(tomTrue); // Force an Update // setup caret pos if ( !(TextMsgFilter._uKeyBoardCodePage == CP_KOREAN)) { if ( cpCursor > 0 ) { cpCursor = min(cpCursor, _cchCompStr) + _ichStart; TextMsgFilter._pTextSel->SetRange(cpCursor, cpCursor); } else if ( cpCursor == 0 ) { POINT ptTopPos; HRESULT hResult; // make sure the beginning is in view hResult = TextMsgFilter._pTextDoc->Range(_ichStart, _ichStart+1, &pTextRange); Assert (pTextRange != NULL); if (hResult == NO_ERROR) { if (pTextRange->GetPoint( tomStart+TA_TOP+TA_LEFT, &(ptTopPos.x), &(ptTopPos.y) ) != NOERROR) pTextRange->ScrollIntoView(tomStart); pTextRange->Release(); } } if (cpCursor) TextMsgFilter._pTextDoc->Unfreeze(&lCount); // Turn on display } if (bstr) SysFreeString(bstr); // setup composition window for Chinese in-caret IME if (TextMsgFilter._uKeyBoardCodePage == CP_CHINESE_TRAD || TextMsgFilter._uKeyBoardCodePage == CP_CHINESE_SIM) IMENotify ( IMN_OPENCANDIDATE, 0x01, TextMsgFilter, TRUE ); } return S_OK; // No DefWindowProc } // processing. /* * void CIme_Lev3::SetCompositionStyle (CTextMsgFilter &TextMsgFilter, CCharFormat &CF) * * @mfunc * Set up a composition clause's character formmatting. * * @comm * If we loaded Office's IMEShare.dll, then we ask it what the formatting * should be, otherwise we use our own, hardwired default formatting. * * @devnote * Note the use of pointers to functions when dealing with IMEShare funcs. * This is because we dynamically load the IMEShare.dll. * */ void CIme_Lev3::SetCompositionStyle ( CTextMsgFilter &TextMsgFilter, UINT attribute, ITextFont *pTextFont) { const IMESTYLE *pIMEStyle; UINT ulID; COLORREF crText = UINTIMEBOGUS; COLORREF crBackground = UINTIMEBOGUS; COLORREF crUl; if (TextMsgFilter._fRE10Mode) { if (attribute > ATTR_TARGET_NOTCONVERTED) attribute = ATTR_CONVERTED; // IME input for 1.0 mode, need to use IME Color if (TextMsgFilter._crComp[attribute].dwEffects & CFE_BOLD) pTextFont->SetBold(tomTrue); if(TextMsgFilter._crComp[attribute].dwEffects & CFE_ITALIC) pTextFont->SetItalic(tomTrue); if(TextMsgFilter._crComp[attribute].dwEffects & CFE_STRIKEOUT) pTextFont->SetStrikeThrough(tomTrue); if(TextMsgFilter._crComp[attribute].dwEffects & CFE_UNDERLINE) pTextFont->SetUnderline(tomSingle); pTextFont->SetForeColor(TextMsgFilter._crComp[attribute].crText); pTextFont->SetBackColor(TextMsgFilter._crComp[attribute].crBackground); } else if (W32->HaveIMEShare()) { CIMEShare *pIMEShare; if (W32->getIMEShareObject(&pIMEShare)) { // IMEShare 98 interface if (pIMEShare->DwGetIMEStyle(attribute, IdstyIMEShareFBold)) pTextFont->SetBold(tomTrue); if(pIMEShare->DwGetIMEStyle(attribute, IdstyIMEShareFItalic)) pTextFont->SetItalic(tomTrue); if(pIMEShare->DwGetIMEStyle(attribute, IdstyIMEShareFUl)) { ulID = pIMEShare->DwGetIMEStyle(attribute, IdstyIMEShareUKul); if(UINTIMEBOGUS != ulID) { long lUnderlineCrIdx = 0; // get color for underline crUl = GetIMEShareColor(pIMEShare, attribute, IdstyIMEShareSubUl); if(UINTIMEBOGUS != crUl) { // NOTE:- attribute is 0 based and index for EffectColor is 1 based, // so, need to add 1 to attribute HRESULT hResult = TextMsgFilter._pTextDoc->SetEffectColor(attribute+1, crUl); // setup the high nibble for color index if (hResult == NOERROR) lUnderlineCrIdx = (attribute+1) << 4; } pTextFont->SetUnderline(IMEShareToTomUL(ulID) + lUnderlineCrIdx); } } crText = GetIMEShareColor(pIMEShare, attribute, IdstyIMEShareSubText); crBackground = GetIMEShareColor(pIMEShare, attribute, IdstyIMEShareSubBack); // ignore case where text color is same as background color if (crText != crBackground) { if(UINTIMEBOGUS != crText) pTextFont->SetForeColor(crText); if(UINTIMEBOGUS != crBackground) pTextFont->SetBackColor(crBackground); } } else { // IMEShare 96 interface pIMEStyle = PIMEStyleFromAttr(attribute); if(NULL == pIMEStyle) goto defaultStyle; if(FBoldIMEStyle(pIMEStyle)) pTextFont->SetBold(tomTrue); if(FItalicIMEStyle(pIMEStyle)) pTextFont->SetItalic(tomTrue); if(FUlIMEStyle(pIMEStyle)) { ulID = IdUlIMEStyle (pIMEStyle); if(UINTIMEBOGUS != ulID) pTextFont->SetUnderline(IMEShareToTomUL(ulID)); } crText = RGBFromIMEColorStyle(PColorStyleTextFromIMEStyle(pIMEStyle)); if(UINTIMEBOGUS != crText) pTextFont->SetForeColor(crText); crBackground = RGBFromIMEColorStyle(PColorStyleBackFromIMEStyle(pIMEStyle)); if(UINTIMEBOGUS != crBackground) pTextFont->SetBackColor(crBackground); } } else // default styles when no IMEShare.dll exist. { defaultStyle: switch(attribute) { // Apply underline style. case ATTR_INPUT: case ATTR_CONVERTED: pTextFont->SetUnderline(tomDotted); break; case ATTR_TARGET_NOTCONVERTED: pTextFont->SetUnderline(tomSingle); break; case ATTR_TARGET_CONVERTED: // Target *is* selection. { pTextFont->SetForeColor(::GetSysColor(COLOR_HIGHLIGHTTEXT)); pTextFont->SetBackColor(::GetSysColor(COLOR_HIGHLIGHT)); } break; } } } /* * COLORREF CIme_Lev3::GetIMEShareColor (CIMEShare *pIMEShare, DWORD dwAttribute, DWORD dwProperty) * * @mfunc * Get the IME share color for the given dwAttribute and property * * * @rdesc * COLORREF of the color * */ COLORREF CIme_Lev3::GetIMEShareColor( CIMEShare *pIMEShare, DWORD dwAttribute, DWORD dwProperty) { if (pIMEShare->DwGetIMEStyle(dwAttribute,IdstyIMEShareFSpecCol | dwProperty)) { if (pIMEShare->DwGetIMEStyle(dwAttribute,IdstyIMEShareFSpecColText | dwProperty)) return (COLORREF) _crTextColor; else return (COLORREF) _crBkColor; } else return (COLORREF) (pIMEShare->DwGetIMEStyle(dwAttribute, IdstyIMEShareRGBCol | dwProperty)); } /* * HRESULT CIme_Lev3::IMENotify(const WPARAM wparam, const LPARAM lparam, * CTextMsgFilter &TextMsgFilter) * * @mfunc * Handle Level 3 WM_IME_NOTIFY messages. * * @comm * Currently we are only interested in knowing when to update * the n window's position. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT CIme_Lev3::IMENotify( const WPARAM wparam, // @parm associated with message. const LPARAM lparam, // @parm associated with message. CTextMsgFilter &TextMsgFilter, // @parm the containing message filter. BOOL fCCompWindow) // @parm Level3 Chinese Composition window { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_Lev3::IMENotify"); if(IMN_OPENCANDIDATE == wparam || IMN_CLOSECANDIDATE == wparam ) { Assert (0 != lparam); INT index; // Candidate window index. CANDIDATEFORM cdCandForm; POINT ptCaret; HIMC hIMC = LocalGetImmContext(TextMsgFilter); // Get host's IME context. if(hIMC) { for (index = 0; index < 32; index++) // Convert bitID to INDEX { // because API if((1 << index) & lparam) break; } Assert(((1 << index) & lparam) == lparam); // Only 1 set? Assert(index < 32); if(IMN_OPENCANDIDATE == wparam && !(TextMsgFilter._uKeyBoardCodePage == CP_KOREAN)) // Set candidate to caret. { HRESULT hResult; POINT ptCurrentBottomPos; GetCaretPos(&ptCaret); // Start at caret. ptCaret.x = max(0, ptCaret.x); ptCaret.y = max(0, ptCaret.y); cdCandForm.dwStyle = CFS_CANDIDATEPOS; if ( !fCCompWindow ) // Not positioning the Chinese composition { // Window. hResult = TextMsgFilter._pTextSel->GetPoint( tomStart+tomClientCoord+TA_BOTTOM+TA_LEFT, &(ptCurrentBottomPos.x), &(ptCurrentBottomPos.y) ); if (hResult != NOERROR) { RECT rcArea; // GetPoint fails, use application rect in screen coordinates hResult = TextMsgFilter._pTextDoc->GetClientRect(tomIncludeInset+tomClientCoord, &(rcArea.left), &(rcArea.top), &(rcArea.right), &(rcArea.bottom)); ptCurrentBottomPos.y = rcArea.bottom; } if (hResult == NOERROR) { if (TextMsgFilter._uKeyBoardCodePage == CP_JAPAN) { // Change style to CFS_EXCLUDE, this is to // prevent the candidate window from covering // the current selection. cdCandForm.dwStyle = CFS_EXCLUDE; cdCandForm.rcArea.left = ptCaret.x; // FUTURE: for verticle text, need to adjust // the rcArea to include the character width. cdCandForm.rcArea.right = cdCandForm.rcArea.left + 2; cdCandForm.rcArea.top = ptCaret.y; ptCaret.y = ptCurrentBottomPos.y + 4; cdCandForm.rcArea.bottom = ptCaret.y; } else ptCaret.y = ptCurrentBottomPos.y + 4; } } // Most IMEs will have only 1, #0, candidate window. However, some IMEs // may want to have a window organized alphabetically, by stroke, and // by radical. cdCandForm.dwIndex = index; cdCandForm.ptCurrentPos = ptCaret; ImmSetCandidateWindow(hIMC, &cdCandForm, TextMsgFilter._fUsingAIMM); } else // Reset back to CFS_DEFAULT. { if(ImmGetCandidateWindow(hIMC, index, &cdCandForm, TextMsgFilter._fUsingAIMM) && CFS_DEFAULT != cdCandForm.dwStyle) { cdCandForm.dwStyle = CFS_DEFAULT; ImmSetCandidateWindow(hIMC, &cdCandForm, TextMsgFilter._fUsingAIMM); } } LocalReleaseImmContext(TextMsgFilter, hIMC); // Done with IME context. if (TextMsgFilter._fHangulToHanja == TRUE && IMN_CLOSECANDIDATE == wparam && OnWinNTFE()) { // By pass NT4.0 Kor Bug where we didn't get a EndComposition message // when user toggle the VK_HANJA key to terminate the conversion. TerminateIMEComposition(TextMsgFilter, CIme::TERMINATE_NORMAL); } if (IMN_CLOSECANDIDATE == wparam && CP_JAPAN == TextMsgFilter._uKeyBoardCodePage) _fUpdateWindow = TRUE; } } return S_FALSE; // Allow DefWindowProc } // processing. /* * * CIme_Lev3::IMEMouseOperation (CTextMsgFilter &TextMsgFilter, UINT msg) * * @mfunc if current IME support Mouse operation, need to pass * mouse events to IME for processing * * @rdesc * BOOL-TRUE if IME handled the mouse events * */ BOOL CIme_Lev3::IMEMouseOperation( CTextMsgFilter &TextMsgFilter, // @parm the containing message filter. UINT msg) // @parm message id { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_Lev3::IMEMouseOperation"); BOOL bRetCode = FALSE; BOOL fButtonPressed = FALSE; WORD wButtons = 0; POINT ptCursor; WPARAM wParamIME; HWND hHostWnd = TextMsgFilter._hwnd; long hWnd; if (!hHostWnd) // Windowless mode... { if (TextMsgFilter._pTextDoc->GetWindow(&hWnd) != S_OK || !hWnd) return FALSE; hHostWnd = (HWND)(DWORD_PTR)hWnd; } if (IMESupportMouse(TextMsgFilter)) { switch (msg) { case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: fButtonPressed = TRUE; //fall through. case WM_SETCURSOR: case WM_MOUSEMOVE: case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: case WM_RBUTTONUP: case WM_RBUTTONDBLCLK: case WM_MBUTTONUP: case WM_MBUTTONDBLCLK: if (GetKeyState(VK_LBUTTON) & 0x80) wButtons |= IMEMOUSE_LDOWN; if (GetKeyState(VK_MBUTTON) & 0x80) wButtons |= IMEMOUSE_MDOWN; if (GetKeyState(VK_RBUTTON) & 0x80) wButtons |= IMEMOUSE_RDOWN; break; default: return FALSE; } // change in button since last message? if ((wButtons != LOBYTE(LOWORD(_wParamBefore))) && GetCapture() == hHostWnd) { fButtonPressed = FALSE; wButtons = 0; ReleaseCapture(); } if (GetCursorPos(&ptCursor)) { ITextRange *pTextRange; HRESULT hResult; long ichCursor; // get cp at current Cursor position hResult = TextMsgFilter._pTextDoc->RangeFromPoint(ptCursor.x, ptCursor.y, &pTextRange); if (hResult != NOERROR) return FALSE; hResult = pTextRange->GetStart(&ichCursor); pTextRange->Release(); if (hResult != NOERROR) return FALSE; // click within composition text? if (_ichStart <= ichCursor && ichCursor <= _ichStart + _cchCompStr) { wParamIME = MAKEWPARAM(wButtons, ichCursor - _ichStart); fButtonPressed &= (_wParamBefore & 0xff) == 0; if (_wParamBefore != wParamIME || msg == WM_MOUSEMOVE && !fButtonPressed) { HIMC hIMC = LocalGetImmContext(TextMsgFilter); _wParamBefore = wParamIME; if (hIMC) { bRetCode = SendMessage(_hwndIME, MSIMEMouseMsg, _wParamBefore, hIMC); LocalReleaseImmContext(TextMsgFilter, hIMC); } } else // no change from last time, no need to send message to IME bRetCode = TRUE; if (bRetCode && fButtonPressed && GetCapture() != hHostWnd) SetCapture(hHostWnd); } else if (GetCapture() == hHostWnd) //We don't want to determine while dragging... return TRUE; } } return bRetCode; } /* * * CIme_Lev3::IMESupportMouse (CTextMsgFilter &TextMsgFilter) * * @mfunc check if current IME supports Mouse events. This should be * a feature for IME Level 3. * * @comm _sIMESupportMouse is a flag with the following values: * == 0 if we haven't checked IME mouse support * == -1 if we have checked and IME doesn't support mouse events * == 1 if we have checked and IME supports mouse events and we have * retrieved the IME hWnd */ BOOL CIme_Lev3::IMESupportMouse( CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_Lev3::IMESupportMouse"); HIMC hIMC; // Host's IME context. HWND hHostWnd; long hWnd; if (!MSIMEMouseMsg || _sIMESuportMouse == -1) return FALSE; // No mouse operation support if (_sIMESuportMouse == 1) return TRUE; // IME supports mouse operation hHostWnd = TextMsgFilter._hwnd; if (!hHostWnd) // Windowless mode... { if (TextMsgFilter._pTextDoc->GetWindow(&hWnd) != S_OK || !hWnd) return FALSE; hHostWnd = (HWND)(DWORD_PTR)hWnd; } // Check if this IME supports mouse operation hIMC = LocalGetImmContext(TextMsgFilter); // Get host's IME context. _sIMESuportMouse = -1; // Init. to no support if(hIMC) { _hwndIME = ImmGetDefaultIMEWnd(hHostWnd, TextMsgFilter._fUsingAIMM); LocalReleaseImmContext(TextMsgFilter, hIMC); // SendMessage returns TRUE if IME supports mouse operation if (_hwndIME && SendMessage(_hwndIME, MSIMEMouseMsg, (WPARAM)IMEMOUSE_VERSION, hIMC) ) _sIMESuportMouse = 1; } return (_sIMESuportMouse == 1); } /* * BOOL IMEHangeulToHanja (&TextMsgFilter) * * @func * Initiates an IME composition string edit to convert Korean Hanguel to Hanja. * @comm * Called from the message loop to handle VK_KANJI_KEY. * * @devnote * We decide if we need to do a conversion by checking: * - the Fonot is a Korean font, * - the character is a valid SBC or DBC, * - ImmEscape accepts the character and bring up a candidate window * * @rdesc * BOOL - FALSE for no conversion. TRUE if OK. */ BOOL IMEHangeulToHanja ( CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "IMEHangeulToHanja"); if(!TextMsgFilter.IsIMEComposition()) { if(TextMsgFilter._pTextSel->CanEdit(NULL) == NOERROR) { WCHAR szCurrentChar; long cCurrentChar; HRESULT hResult; HKL hKL = GetKeyboardLayout(0x0FFFFFFFF); HIMC hIMC; if (!hKL) goto Exit; hIMC = LocalGetImmContext(TextMsgFilter); if (!hIMC) goto Exit; // Collapse to cpMin hResult = TextMsgFilter._pTextSel->Collapse(tomStart); // get the current character hResult = TextMsgFilter._pTextSel->GetChar(&cCurrentChar); if (hResult != NOERROR) goto Exit; szCurrentChar = (WCHAR)cCurrentChar; // Check if the IME has a conversion for this Hangeul character. if (ImmEscape(hKL, hIMC, IME_ESC_HANJA_MODE, (LPVOID)&szCurrentChar, TextMsgFilter._fUsingAIMM) != FALSE) { ITextRange *pTextRange; POINT ptMiddlePos; LONG cpCurrent; hResult = TextMsgFilter._pTextSel->GetStart(&cpCurrent); if (hResult == S_OK) { hResult = TextMsgFilter._pTextDoc->Range(cpCurrent, cpCurrent+1, &pTextRange); if (hResult == S_OK && pTextRange) { // Check if the character is in view if (pTextRange->GetPoint( tomEnd+TA_BASELINE+TA_LEFT, &(ptMiddlePos.x), &(ptMiddlePos.y) ) != NOERROR) pTextRange->ScrollIntoView(tomEnd); pTextRange->Release(); } } TextMsgFilter._fHangulToHanja = TRUE; TextMsgFilter._ime = new CIme_HangeulToHanja(TextMsgFilter); if(TextMsgFilter.IsIMEComposition()) { // start IME composition for the conversion LocalReleaseImmContext(TextMsgFilter, hIMC); return TextMsgFilter._ime->StartComposition(TextMsgFilter); } else TextMsgFilter._fHangulToHanja = FALSE; } LocalReleaseImmContext(TextMsgFilter, hIMC); } } Exit: return S_FALSE; } /* * CIme_HangeulToHanja::CIme_HangeulToHanja() * * @mfunc * CIme_HangeulToHanja Constructor. * * */ CIme_HangeulToHanja::CIme_HangeulToHanja(CTextMsgFilter &TextMsgFilter) : CIme_Lev3(TextMsgFilter) { } /* * HRESULT CIme_HangeulToHanja::StartComposition(CTextMsgFilter &TextMsgFilter) * * @mfunc * Begin CIme_HangeulToHanja composition string processing. * * @comm * Call Level3::StartComposition. Then setup the Korean block * caret for the Hanguel character. * * @rdesc * Need to adjust _ichStart and _cchCompStr to make the Hanguel character * "become" a composition character. * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. */ HRESULT CIme_HangeulToHanja::StartComposition( CTextMsgFilter &TextMsgFilter ) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_HangeulToHanja::StartComposition"); HRESULT hr; hr = CIme_Lev3::StartComposition(TextMsgFilter); // initialize to 1 so Composition string will get rid of the selected Hangeul _cchCompStr = 1; // turn on undo TextMsgFilter._pTextDoc->Undo(tomResume, NULL); // Setup Block caret mode TextMsgFilter._pTextDoc->SetCaretType(tomKoreanBlockCaret); return hr; } /* * HRESULT CIme_HangeulToHanja::CompositionString(const LPARAM lparam, CTextMsgFilter &TextMsgFilter) * * @mfunc * Handle CIme_HangeulToHanja WM_IME_COMPOSITION messages. * * @comm * call CIme_Lev3::CompositionString to get rid of the selected Hanguel character, * then setup the format for the next Composition message. * * @devnote * When the next Composition message comes in and that we are no longer in IME, * the new character will use the format as set here. */ HRESULT CIme_HangeulToHanja::CompositionString( const LPARAM lparam, // @parm associated with message CTextMsgFilter &TextMsgFilter) { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_HangeulToHanja::CompositionString"); CIme_Lev3::CompositionString(lparam, TextMsgFilter); return S_OK; } /* * HRESULT CIme_Protected::CompositionString(const LPARAM lparam, CTextMsgFilter &TextMsgFilter) * * @mfunc * Handle CIme_Protected WM_IME_COMPOSITION messages. * * @comm * Just throw away the result string since we are * in read-only or protected mode * * * @rdesc * HRESULT-S_FALSE for DefWindowProc processing, S_OK if not. * */ HRESULT CIme_Protected::CompositionString ( const LPARAM lparam, // @parm associated with message. CTextMsgFilter &TextMsgFilter) // @parm the containing message filter. { TRACEBEGIN(TRCSUBSYSFE, TRCSCOPEINTERN, "CIme_Protected::CompositionString"); if(CLEANUP_COMPOSITION_STRING() || HAVE_RESULT_STRING()) // If result string.. { LONG cch = 0; HIMC hIMC = LocalGetImmContext(TextMsgFilter); // Get host's IME context. WCHAR szCompStr[256]; if(hIMC) // Get result string. { cch = GetCompositionStringInfo(hIMC, GCS_RESULTSTR, szCompStr, sizeof(szCompStr)/sizeof(szCompStr[0]), NULL, 0, NULL, NULL, TextMsgFilter._uKeyBoardCodePage, FALSE, TextMsgFilter._fUsingAIMM); LocalReleaseImmContext(TextMsgFilter, hIMC); // Done with IME context. } return NOERROR; // Don't want WM_IME_CHARs. } // Terminate composition to force a end composition message TerminateIMEComposition(TextMsgFilter, CIme::TERMINATE_FORCECANCEL); return S_FALSE; }