/* * @doc INTERNAL * * @module CMSGFLT.CPP -- Text Message Implementation | * * Most everything to do with IME message handling. * * Original Author: * Hon Wah Chan * * History: * 2/6/98 v-honwch * * Copyright (c) 1995-1998, Microsoft Corporation. All rights reserved. */ #include "_common.h" #include "_cmsgflt.h" #include "_ime.h" #define MAX_RECONVERSION_SIZE 100 #define CONTROL(_ch) (_ch - 'A' + 1) /* * void CreateIMEMessageFilter(ITextMsgFilter **ppMsgFilter) * * @func * TextMsgFilter class factory. */ void CreateIMEMessageFilter(ITextMsgFilter **ppMsgFilter) { CTextMsgFilter *pNewFilter = new CTextMsgFilter; *ppMsgFilter = pNewFilter ? pNewFilter : NULL; } /* * void CTextMsgFilter::~CTextMsgFilter * * @mfunc * CTextMsgFilter Destructor * Release objects being used. * */ CTextMsgFilter::~CTextMsgFilter () { if (_hIMCContext) ImmAssociateContext(_hwnd, _hIMCContext, _fUsingAIMM); // Restore IME before exit // Release various objects if (_fUsingAIMM) DeactivateAIMM(); if (_pFilter) _pFilter->Release(); if (_pTextSel) _pTextSel->Release(); _pFilter = NULL; _pTextDoc = NULL; _pTextSel = NULL; _hwnd = NULL; _hIMCContext = NULL; } /* * STDMETHODIMP CTextMsgFilter::QueryInterface (riid, ppv) * * @mfunc * IUnknown QueryInterface support * * @rdesc * NOERROR if interface supported * */ STDMETHODIMP CTextMsgFilter::QueryInterface (REFIID riid, void ** ppv) { TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CTextMsgFilter::QueryInterface"); if( IsEqualIID(riid, IID_IUnknown) ) { *ppv = (IUnknown *)this; } else if( IsEqualIID(riid, IID_ITextMsgFilter) ) { *ppv = (ITextMsgFilter *)this; } else { *ppv = NULL; return E_NOINTERFACE; } AddRef(); return NOERROR; } /* * STDMETHODIMP_(ULONG) CTextMsgFilter::AddRef * * @mfunc * IUnknown AddRef support * * @rdesc * Reference count */ STDMETHODIMP_(ULONG) CTextMsgFilter::AddRef() { TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::AddRef"); return ++_crefs; } /* * STDMETHODIMP_(ULONG) CTextMsgFilter::Release() * * @mfunc * IUnknown Release support - delete object when reference count is 0 * * @rdesc * Reference count */ STDMETHODIMP_(ULONG) CTextMsgFilter::Release() { TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CTextMsgFilter::Release"); _crefs--; if( _crefs == 0 ) { delete this; return 0; } return _crefs; } /* * STDMETHODIMP_(HRESULT) CTextMsgFilter::AttachDocument(HWND, ITextDocument2) * * @mfunc * Attach message filter. Perform genral initialization * * @rdesc * NOERROR */ STDMETHODIMP_(HRESULT) CTextMsgFilter::AttachDocument( HWND hwnd, ITextDocument2 *pTextDoc) { HRESULT hResult; // Cache the values for possible later use. // The TextDocument interface pointer is not AddRefed because it is a back pointer // and the lifetime of message filters is assumed to be nested inside text documents _hwnd = hwnd; _pTextDoc = pTextDoc; // Don't get selection until it is needed _pTextSel = NULL; _fUnicodeWindow = 0; if (hwnd) _fUnicodeWindow = IsWindowUnicode(hwnd); _fUsingAIMM = 0; // We will activate AIMM if it has been loaded by previous instances // NOTE: we don't support AIMM for windowless mode. if (_hwnd && IsAIMMLoaded()) { // activate AIMM hResult = ActivateAIMM(FALSE); if (hResult == NOERROR) { DWORD dwAtom; ATOM aClass; // filter client windows if (dwAtom = GetClassLong(hwnd, GCW_ATOM)) { aClass = dwAtom; hResult = FilterClientWindowsAIMM(&aClass, 1); } _fUsingAIMM = 1; } } // Check if current keyboard is MSIME98 or later. CheckIMEType(NULL); // Initialize some member data _fHangulToHanja = FALSE; _fIMECancelComplete = FALSE; _fIMEAlwaysNotify = FALSE; _hIMCContext = NULL; _pTextDoc->GetFEFlags(&_lFEFlags); _fRE10Mode = (_lFEFlags & tomRE10Mode); // For 1.0 mode IME color memset(_crComp, 0, sizeof(_crComp)); _crComp[0].crBackground = 0x0ffffff; _crComp[0].dwEffects = CFE_UNDERLINE; _crComp[1].crBackground = 0x0808080; _crComp[2].crBackground = 0x0ffffff; _crComp[2].dwEffects = CFE_UNDERLINE; _crComp[3].crText = 0x0ffffff; _uSystemCodePage = GetACP(); return NOERROR; } /* * STDMETHODIMP_(HRESULT) CTextMsgFilter::HandleMessage(UINT *, WPARAM *, LPARAM *, LRESULT *) * * @mfunc * Main Message filter message loop handling * * @rdesc * S_OK if we have handled the message * S_FALSE if we want the caller to process the message */ STDMETHODIMP_(HRESULT) CTextMsgFilter::HandleMessage( UINT * pmsg, WPARAM * pwparam, LPARAM * plparam, LRESULT * plres) { HRESULT hr = S_FALSE; BOOL bReleaseSelction = FALSE; HRESULT hResult; // Give other message filters a chance to handle message // Stop with the first guy that handles the message if (_pFilter) hr = _pFilter->HandleMessage(pmsg, pwparam, plparam, plres); if (hr == S_OK) return hr; if (IsIMEComposition()) { // During IME Composition, there are some messages we should // not handle. Also, there are other messages we need to handle by // terminating the IME composition first. // For WM_KEYDOWN, this is handled inside edit.c OnTxKeyDown(). switch( *pmsg ) { case WM_COPY: case WM_CUT: case WM_DROPFILES: case EM_REDO: case EM_SETCHARFORMAT: case WM_SETFONT: return S_OK; // Just ignore these case EM_UNDO: case WM_UNDO: // just terminate and exist for undo cases _ime->TerminateIMEComposition(*this, CIme::TERMINATE_NORMAL); return S_OK; case WM_SETTEXT: case WM_CLEAR: case EM_STREAMIN: // these messages are used to reset our state, so reset // IME as well _ime->TerminateIMEComposition(*this, CIme::TERMINATE_FORCECANCEL); break; case EM_SETTEXTEX: if (!_fRE10Mode) // Don't terminate if running in 10 mode _ime->TerminateIMEComposition(*this, CIme::TERMINATE_FORCECANCEL); break; case WM_SYSKEYDOWN: // Don't terminate IME composition on VK_PROCESSKEY (F10) since Japanese // IME will process the F10 key if ( *pwparam == VK_PROCESSKEY ) break; // otherwise we want to terminate the IME case EM_SETWORDBREAKPROC: case WM_PASTE: case EM_PASTESPECIAL: case EM_SCROLL: case EM_SCROLLCARET: case WM_VSCROLL: case WM_HSCROLL: case WM_KILLFOCUS: case EM_STREAMOUT: case EM_SETREADONLY: case EM_SETSEL: case EM_SETPARAFORMAT: case WM_INPUTLANGCHANGEREQUEST: _ime->TerminateIMEComposition(*this, CIme::TERMINATE_NORMAL); break; case WM_KEYDOWN: if(GetKeyState(VK_CONTROL) & 0x8000) { // During IME Composition, there are some key events we should // not handle. Also, there are other key events we need to handle by // terminating the IME composition first. switch((WORD) *pwparam) { case VK_TAB: case VK_CLEAR: case VK_NUMPAD5: case 'A': // Ctrl-A => select all case 'C': // Ctrl-C => copy case 'X': // Ctrl-X => cut case 'Y': // Ctrl-Y => redo return S_OK; // Just ignore these case 'V': // Ctrl-V => paste case 'Z': // Ctrl-Z => undo _ime->TerminateIMEComposition(*this, CIme::TERMINATE_NORMAL); if ((WORD) *pwparam == 'Z') // Early exist for undo case return S_OK; } } else { switch((WORD) *pwparam) { case VK_F16: return S_OK; // Just ignore these case VK_BACK: case VK_INSERT: // Ins case VK_LEFT: // Left arrow case VK_RIGHT: // Right arrow case VK_UP: // Up arrow case VK_DOWN: // Down arrow case VK_HOME: // Home case VK_END: // End case VK_PRIOR: // PgUp case VK_NEXT: // PgDn case VK_DELETE: // Del case CONTROL('J'): case VK_RETURN: _ime->TerminateIMEComposition(*this, CIme::TERMINATE_NORMAL); break; } } break; default: // only need to handle mouse related msgs during composition if (IN_RANGE(WM_MOUSEFIRST, *pmsg, WM_MBUTTONDBLCLK) || *pmsg == WM_SETCURSOR) { bReleaseSelction = GetTxSelection(); if (_pTextSel) hr = IMEMouseCheck( *this, pmsg, pwparam, plparam, plres); goto Exit; } } } // Get Fe Flags for ES_NOIME or ES_SELFIME setting _lFEFlags = 0; // ... Local mucking with msg, params, etc, ... switch ( *pmsg ) { case WM_CHAR: hr = OnWMChar (pmsg, pwparam, plparam, plres); break; case WM_IME_CHAR: _uKeyBoardCodePage = GetKeyboardCodePage(0x0FFFFFFFF); hResult = _pTextDoc->GetFEFlags(&_lFEFlags); if ((_lFEFlags & ES_NOIME)) hr = S_OK; else hr = OnWMIMEChar (pmsg, pwparam, plparam, plres); break; case WM_IME_STARTCOMPOSITION: _uKeyBoardCodePage = GetKeyboardCodePage(0x0FFFFFFFF); hResult = _pTextDoc->GetFEFlags(&_lFEFlags); if (!(_lFEFlags & ES_SELFIME)) { bReleaseSelction = GetTxSelection(); if (_pTextSel) hr = StartCompositionGlue (*this); } break; case WM_IME_COMPOSITION: _uKeyBoardCodePage = GetKeyboardCodePage(0x0FFFFFFFF); hResult = _pTextDoc->GetFEFlags(&_lFEFlags); if ((_lFEFlags & ES_NOIME) && !IsIMEComposition()) hr = S_OK; else if (!(_lFEFlags & ES_SELFIME)) { bReleaseSelction = GetTxSelection(); if (_pTextSel) { hr = CompositionStringGlue ( *plparam, *this ); // Turn off Result string bit to avoid WM_IME_CHAR message. *plparam &= ~GCS_RESULTSTR; } } if (_hwnd && IsIMEComposition() && _ime->IgnoreIMECharMsg()) { _ime->AcceptIMECharMsg(); if (fHaveAIMM) hr = CallAIMMDefaultWndProc(_hwnd, *pmsg, *pwparam, *plparam, plres); else *plres = ::DefWindowProc(_hwnd, *pmsg, *pwparam, *plparam); hr = S_OK; } break; case WM_IME_ENDCOMPOSITION: hResult = _pTextDoc->GetFEFlags(&_lFEFlags); if (!(_lFEFlags & ES_SELFIME)) { bReleaseSelction = GetTxSelection(); if (_pTextSel) hr = EndCompositionGlue ( *this, FALSE ); } break; case WM_IME_NOTIFY: hResult = _pTextDoc->GetFEFlags(&_lFEFlags); if (!(_lFEFlags & (ES_SELFIME | ES_NOIME))) { bReleaseSelction = GetTxSelection(); if (_pTextSel) hr = IMENotifyGlue ( *pwparam, *plparam, *this ); } break; case WM_IME_COMPOSITIONFULL: // Level 2 comp string about to overflow. hResult = _pTextDoc->GetFEFlags(&_lFEFlags); if (!(_lFEFlags & ES_SELFIME)) { IMECompositionFull ( *this ); } hr = S_FALSE; break; case WM_KEYDOWN: bReleaseSelction = GetTxSelection(); if (_pTextSel) { if (*pwparam == VK_KANJI) { hResult = _pTextDoc->GetFEFlags(&_lFEFlags); _uKeyBoardCodePage = GetKeyboardCodePage(0x0FFFFFFFF); // for Korean, need to convert the next Korean Hangul character to Hanja if(CP_KOREAN == _uKeyBoardCodePage && !(_lFEFlags & (ES_SELFIME | ES_NOIME))) hr = IMEHangeulToHanja ( *this ); } } break; case WM_INPUTLANGCHANGE: CheckIMEType((HKL)*plparam); hr = S_FALSE; break; case WM_KILLFOCUS: OnKillFocus(); break; case WM_SETFOCUS: OnSetFocus(); break; case EM_SETIMEOPTIONS: *plres = OnSetIMEOptions(*pwparam, *plparam); hr = S_OK; break; case EM_GETIMEOPTIONS: *plres = OnGetIMEOptions(); hr = S_OK; break; case WM_IME_REQUEST: hResult = _pTextDoc->GetFEFlags(&_lFEFlags); if (!(_lFEFlags & (ES_SELFIME | ES_NOIME))) { bReleaseSelction = GetTxSelection(); if (_pTextSel) { _uKeyBoardCodePage = GetKeyboardCodePage(0x0FFFFFFFF); if (*pwparam == IMR_RECONVERTSTRING || *pwparam == IMR_CONFIRMRECONVERTSTRING || *pwparam == IMR_DOCUMENTFEED) hr = OnIMEReconvert(pmsg, pwparam, plparam, plres, _fUnicodeWindow); else if (*pwparam == IMR_QUERYCHARPOSITION) hr = OnIMEQueryPos(pmsg, pwparam, plparam, plres, _fUnicodeWindow); } } break; case EM_RECONVERSION: hResult = _pTextDoc->GetFEFlags(&_lFEFlags); if (!(_lFEFlags & (ES_SELFIME | ES_NOIME))) { // Application initiates reconversion bReleaseSelction = GetTxSelection(); if (_pTextSel) { if (!IsIMEComposition()) { if (_fMSIME && MSIMEReconvertRequestMsg) // Use private message if it is available IMEMessage( *this, MSIMEReconvertRequestMsg, 0, (LPARAM)_hwnd, TRUE ); else { hr = OnIMEReconvert(pmsg, pwparam, plparam, plres, TRUE); *plres = 0; } } } } hr = S_OK; break; case EM_SETLANGOPTIONS: // Setup IME related setting. // hr is not S_OK so textserv could handle other language setting _fIMEAlwaysNotify = (*plparam & IMF_IMEALWAYSSENDNOTIFY) != 0; _fIMECancelComplete = (*plparam & IMF_IMECANCELCOMPLETE) != 0; *plres = 1; break; case EM_GETLANGOPTIONS: // Report IME related setting. // hr is not S_OK so textserv could fill in other language setting if ( _fIMECancelComplete ) *plres |= IMF_IMECANCELCOMPLETE; if ( _fIMEAlwaysNotify ) *plres |= IMF_IMEALWAYSSENDNOTIFY; break; case EM_GETIMECOMPMODE: // Get current IME level *plres = OnGetIMECompositionMode( *this ); hr = S_OK; break; case EM_SETEDITSTYLE: if (*pwparam & SES_USEAIMM) { if (_hwnd && !_fUsingAIMM && LoadAIMM()) { hResult = _pTextDoc->GetFEFlags(&_lFEFlags); if (!(_lFEFlags & ES_NOIME)) // No IME style on? { // activate AIMM hResult = ActivateAIMM(FALSE); if (hResult == NOERROR) { DWORD dwAtom; ATOM aClass; // filter client windows if (dwAtom = GetClassLong(_hwnd, GCW_ATOM)) { aClass = dwAtom; hResult = FilterClientWindowsAIMM(&aClass, 1); } _fUsingAIMM = 1; } } } } if ((*plparam == 0 || *plparam & SES_NOIME) && _hwnd) { if (*pwparam & SES_NOIME) { if (!_hIMCContext) _hIMCContext = ImmAssociateContext(_hwnd, NULL, _fUsingAIMM); // turn off IME } else if (*plparam & SES_NOIME) { if (_hIMCContext) ImmAssociateContext(_hwnd, _hIMCContext, _fUsingAIMM); // turn on IME _hIMCContext = NULL; } } // remove settings that are handled. *pwparam &= ~(SES_NOIME | SES_USEAIMM); *plparam &= ~(SES_NOIME | SES_USEAIMM); // fall thru to return the edit style case EM_GETEDITSTYLE: if (_hIMCContext) *plres = SES_NOIME; // IME has been turned off if (_fUsingAIMM) *plres |= SES_USEAIMM; // AIMM is on break; case EM_SETIMECOLOR: if (_fRE10Mode) { memcpy(&_crComp, (const void *)(*plparam), sizeof(_crComp)); *plres = 1; } hr = S_OK; break; case EM_GETIMECOLOR: if (_fRE10Mode) { memcpy((void *)(*plparam), &_crComp, sizeof(_crComp)); *plres = 1; } hr = S_OK; break; default: if (*pmsg) { // Look for IME98 private messages if (*pmsg == MSIMEReconvertMsg || *pmsg == MSIMEDocFeedMsg || *pmsg == MSIMEQueryPositionMsg) { hResult = _pTextDoc->GetFEFlags(&_lFEFlags); if (!(_lFEFlags & (ES_SELFIME | ES_NOIME))) { bReleaseSelction = GetTxSelection(); if (_pTextSel) { if (*pmsg == MSIMEQueryPositionMsg) hr = OnIMEQueryPos(pmsg, pwparam, plparam, plres, TRUE); else hr = OnIMEReconvert(pmsg, pwparam, plparam, plres, TRUE); } } } } break; } Exit: // Release Selection if we get it for this message if (bReleaseSelction && _pTextSel) { _pTextSel->Release(); _pTextSel = NULL; } // Return the value that will cause message to be processed normally return hr; } /* * HRESULT CTextMsgFilter::AttachMsgFilter(ITextMsgFilter *) * * @mfunc * Add another message filter to the chain * * @rdesc * NOERROR if added */ HRESULT STDMETHODCALLTYPE CTextMsgFilter::AttachMsgFilter( ITextMsgFilter *pMsgFilter) { HRESULT hr = NOERROR; if (_pFilter) hr = _pFilter->AttachMsgFilter( pMsgFilter ); else { _pFilter = pMsgFilter; _pFilter->AddRef(); } return hr; } /* * HRESULT CTextMsgFilter::OnWMChar(UINT *, WPARAM *, LPARAM *, LRESULT *) * * @mfunc * Handle WM_CHAR message - look for Japanese keyboard with Kana key on * Convert the SB Kana to Unicode if needed. * * @rdesc * S_FALSE so caller will handle the modified character in wparam */ HRESULT CTextMsgFilter::OnWMChar( UINT * pmsg, WPARAM * pwparam, LPARAM * plparam, LRESULT * plres) { // For Japanese keyboard, if Kana mode is on, // Kana characters (single byte Japanese chars) are coming in via WM_CHAR. if ( GetKeyState(VK_KANA) & 0x1 ) { _uKeyBoardCodePage = GetKeyboardCodePage(0x0FFFFFFFF); if (_uKeyBoardCodePage == CP_JAPAN) { // check if this is a single byte character. TCHAR unicodeConvert; BYTE bytes[2]; bytes[0] = (BYTE)(*pwparam >> 8); // Interchange DBCS bytes in endian bytes[1] = (BYTE)*pwparam; // independent fashion (use byte array) if (!bytes[0]) { if(UnicodeFromMbcs((LPWSTR)&unicodeConvert, 1, (LPCSTR)&bytes[1], 1, _uKeyBoardCodePage) == 1) *pwparam = unicodeConvert; } return InputFEChar(*pwparam); } } return S_FALSE; } /* * HRESULT CTextMsgFilter::OnWMIMEChar(UINT *, WPARAM *, LPARAM *, LRESULT *) * * @mfunc * Handle WM_IMECHAR message - convert the character to unicode. * * @rdesc * S_OK - caller to ignore the message * S_FALSE - caller to handle the message. wparam may contains a new char */ HRESULT CTextMsgFilter::OnWMIMEChar( UINT * pmsg, WPARAM * pwparam, LPARAM * plparam, LRESULT * plres) { TCHAR unicodeConvert; BYTE bytes[2]; // We may receive IMECHAR even if we have handled the composition char already. // This is the case when the host does not call the DefWinProc with the composition // bit masked off. So, we need to ignore this message to avoid double entry. if (IsIMEComposition() && _ime->IgnoreIMECharMsg()) { _ime->SkipIMECharMsg(); // Skip this ime char msg return S_OK; } bytes[0] = *pwparam >> 8; // Interchange DBCS bytes in endian bytes[1] = *pwparam; // independent fashion (use byte array) // need to convert both single-byte KANA and DBC if (!bytes[0] || GetTrailBytesCount(bytes[0], _uKeyBoardCodePage)) { if( UnicodeFromMbcs((LPWSTR)&unicodeConvert, 1, bytes[0] == 0 ? (LPCSTR)&bytes[1] : (LPCSTR)bytes, bytes[0] == 0 ? 1 : 2, _uKeyBoardCodePage) == 1 ) *pwparam = unicodeConvert; return InputFEChar(*pwparam); } return S_FALSE; } /* * HRESULT CTextMsgFilter::OnIMEReconvert(UINT *, WPARAM *, LPARAM *, LRESULT *) * * @mfunc * Handle IME Reconversion and Document feed. We only handle Unicode messages. * We use a limit of MAX_RECONVERSION_SIZE(100) characters in both cases. * * @rdesc * S_OK if we have handled the message */ HRESULT CTextMsgFilter::OnIMEReconvert( UINT * pmsg, WPARAM * pwparam, LPARAM * plparam, LRESULT * plres, BOOL fUnicode) { HRESULT hr = S_OK; LPRECONVERTSTRING lpRCS = (LPRECONVERTSTRING)(*plparam); long cbStringSize; long cpMin, cpMax; long cpParaStart, cpParaEnd; HRESULT hResult; ITextRange *pTextRange, *pTempTextRange; long cbAdded; BOOL bDocumentFeed; long cLastChar; BOOL fAdjustedRange = FALSE; *plres = 0; // NT doesn't support Ansi window when the CP_ACP isn't the same // as keyboard codepage. if (!fUnicode && !(W32->OnWin9x()) && _uKeyBoardCodePage != _uSystemCodePage) return S_OK; bDocumentFeed = (MSIMEDocFeedMsg && *pmsg == MSIMEDocFeedMsg) || (*pmsg == WM_IME_REQUEST && *pwparam == IMR_DOCUMENTFEED); if (bDocumentFeed && IsIMEComposition() && _ime->GetIMELevel() == IME_LEVEL_3) { // Composition in progress, use composition string as selection cpMin = ((CIme_Lev3 *)_ime)->GetIMECompositionStart(); cpMax = ((CIme_Lev3 *)_ime)->GetIMECompositionLen() + cpMin; } else { // Get current selection hResult = _pTextSel->GetStart(&cpMin); hResult = _pTextSel->GetEnd(&cpMax); } // Expand to include the current paragraph hResult = _pTextDoc->Range(cpMin, cpMax, &pTextRange); Assert (pTextRange != NULL); if (hResult != NOERROR) return S_OK; hResult = pTextRange->Expand(tomParagraph, &cbAdded); // Fail to get Paragraph, get the story // Note:- Expand will return S_FALSE for plain text when // the whole story is selected if (hResult != NOERROR) hResult = pTextRange->Expand(tomStory, &cbAdded); hResult = pTextRange->GetStart(&cpParaStart); hResult = pTextRange->GetEnd(&cpParaEnd); if (*pwparam == IMR_CONFIRMRECONVERTSTRING) { *plres = CheckIMEChange(lpRCS, cpParaStart, cpParaEnd, cpMin, cpMax, fUnicode); goto Exit; } // Initialize to hugh number _cpReconvertStart = tomForward; // Check if Par included hResult = _pTextDoc->Range(cpParaEnd-1, cpParaEnd, &pTempTextRange); if (hResult != NOERROR) goto Exit; Assert (pTempTextRange != NULL); hResult = pTempTextRange->GetChar(&cLastChar); pTempTextRange->Release(); if (hResult == NOERROR && (WCHAR)cLastChar == CR) { if (cpMax == cpParaEnd) { // Par is selected, change selection to exclude the par char cpMax--; _pTextSel->SetEnd(cpMax); if (cpMin > cpMax) { // Adjust cpMin as well cpMin = cpMax; _pTextSel->SetStart(cpMin); } } // Get rid of par char cpParaEnd--; fAdjustedRange = TRUE; } // Check for MAX_RECONVERSION_SIZE since we don't want to pass a hugh buffer // to IME long cchSelected; cchSelected = cpMax - cpMin; if (cpParaEnd - cpParaStart > MAX_RECONVERSION_SIZE) { // Too many character selected, forget it if (cchSelected > MAX_RECONVERSION_SIZE) goto Exit; if (cchSelected == MAX_RECONVERSION_SIZE) { // Selection reaches the limit cpParaStart = cpMin; cpParaEnd = cpMax; } else { long cchBeforeSelection = cpMin - cpParaStart; long cchAfterSelection = cpParaEnd - cpMax; long cchNeeded = MAX_RECONVERSION_SIZE - cchSelected; if (cchBeforeSelection < cchNeeded/2) { // Put in all characters from the Par start // and move Par end cpParaEnd = cpParaStart + MAX_RECONVERSION_SIZE - 1; } else if (cchAfterSelection < cchNeeded/2) { // Put in all character to the Par end // and move Par start cpParaStart = cpParaEnd - MAX_RECONVERSION_SIZE + 1; } else { // Adjust both end cpParaStart = cpMin - cchNeeded/2; cpParaEnd = cpParaStart + MAX_RECONVERSION_SIZE - 1; } } fAdjustedRange = TRUE; } if (fAdjustedRange) { // Adjust the text range hResult = pTextRange->SetRange(cpParaStart, cpParaEnd); if (hResult != NOERROR) goto Exit; } cbStringSize = (cpParaEnd - cpParaStart) * 2; // No char in current par, forget it. if (cbStringSize <= 0) goto Exit; if (EM_RECONVERSION == *pmsg) { // RE reconversion msg, allocate the Reconversion buffer lpRCS = (LPRECONVERTSTRING) PvAlloc(sizeof(RECONVERTSTRING) + cbStringSize + 2, GMEM_ZEROINIT); Assert(lpRCS != NULL); if (lpRCS) lpRCS->dwSize = sizeof(RECONVERTSTRING) + cbStringSize + 2; } if (lpRCS) { BSTR bstr = NULL; LPSTR lpReconvertBuff; hResult = pTextRange->GetText(&bstr); if (hResult != NOERROR || bstr == NULL) { if (EM_RECONVERSION == *pmsg) FreePv(lpRCS); goto Exit; // forget it } if (lpRCS->dwSize - sizeof(RECONVERTSTRING) - 2 < (DWORD)cbStringSize) cbStringSize = lpRCS->dwSize - sizeof(RECONVERTSTRING) - 2; lpReconvertBuff = (LPSTR)(lpRCS) + sizeof(RECONVERTSTRING); if (fUnicode) { // fill in the buffer memcpy(lpReconvertBuff, (LPSTR)bstr, cbStringSize); *(lpReconvertBuff+cbStringSize) = '\0'; *(lpReconvertBuff+cbStringSize+1) = '\0'; lpRCS->dwStrLen = (cpParaEnd - cpParaStart); lpRCS->dwCompStrLen = (cpMax - cpMin); lpRCS->dwCompStrOffset = (cpMin - cpParaStart)*2; // byte offset from beginning of string } else { // Ansi case, need to find byte offset and Ansi string long cch = WideCharToMultiByte(_uKeyBoardCodePage, 0, bstr, -1, lpReconvertBuff, cbStringSize+1, NULL, NULL); Assert (cch > 0); if (cch > 0) { CTempCharBuf tcb; char *psz = tcb.GetBuf(cch); if (cch > 1 && lpReconvertBuff[cch-1] == '\0') cch--; // Get rid of the null char lpRCS->dwStrLen = cch; lpRCS->dwCompStrOffset = WideCharToMultiByte(_uKeyBoardCodePage, 0, bstr, cpMin - cpParaStart, psz, cch, NULL, NULL); lpRCS->dwCompStrLen = 0; if (cpMax > cpMin) lpRCS->dwCompStrLen = WideCharToMultiByte(_uKeyBoardCodePage, 0, bstr+cpMin, cpMax - cpMin, psz, cch, NULL, NULL); } else { SysFreeString (bstr); if (EM_RECONVERSION == *pmsg) FreePv(lpRCS); goto Exit; // forget it } } // Fill in the rest of the RCS struct lpRCS->dwVersion = 0; lpRCS->dwStrOffset = sizeof(RECONVERTSTRING); // byte offset from beginning of struct lpRCS->dwTargetStrLen = lpRCS->dwCompStrLen; lpRCS->dwTargetStrOffset = lpRCS->dwCompStrOffset; *plres = sizeof(RECONVERTSTRING) + cbStringSize + 2; // Save this for the CONFIRMRECONVERTSTRING handling _cpReconvertStart = cpParaStart; _cpReconvertEnd = cpParaEnd; SysFreeString (bstr); if (EM_RECONVERSION == *pmsg) { HIMC hIMC = ImmGetContext(_hwnd); if (hIMC) { DWORD imeProperties = ImmGetProperty(GetKeyboardLayout(0x0FFFFFFFF), IGP_SETCOMPSTR, _fUsingAIMM); if ((imeProperties & (SCS_CAP_SETRECONVERTSTRING | SCS_CAP_MAKEREAD)) == (SCS_CAP_SETRECONVERTSTRING | SCS_CAP_MAKEREAD)) { if (ImmSetCompositionStringW(hIMC, SCS_QUERYRECONVERTSTRING, lpRCS, *plres, NULL, 0)) { // Check if there is any change in selection CheckIMEChange(lpRCS, cpParaStart, cpParaEnd, cpMin, cpMax, TRUE); ImmSetCompositionStringW(hIMC, SCS_SETRECONVERTSTRING, lpRCS, *plres, NULL, 0); } } ImmReleaseContext(_hwnd, hIMC); } FreePv(lpRCS); } } else { // return size for IME to allocate the buffer *plres = sizeof(RECONVERTSTRING) + cbStringSize + 2; } Exit: pTextRange->Release(); return hr; } /* * BOOL CTextMsgFilter::CheckIMEChange(LPRECONVERTSTRING,long,long,long,long) * * @mfunc * Verify if IME wants to re-adjust the selection * * @rdesc * TRUE - allow IME to change the selection */ BOOL CTextMsgFilter::CheckIMEChange( LPRECONVERTSTRING lpRCS, long cpParaStart, long cpParaEnd, long cpMin, long cpMax, BOOL fUnicode) { long cpImeSelectStart = 0; long cpImeSelectEnd = 0; HRESULT hResult; if (!lpRCS || _cpReconvertStart == tomForward) // Never initialize, forget it return FALSE; if (fUnicode) { cpImeSelectStart = _cpReconvertStart + lpRCS->dwCompStrOffset / 2; cpImeSelectEnd = cpImeSelectStart + lpRCS->dwCompStrLen; } else { // Need to convert the byte offset to char offset. ITextRange *pTextRange; BSTR bstr = NULL; hResult = _pTextDoc->Range(_cpReconvertStart, _cpReconvertEnd, &pTextRange); if (hResult != NOERROR) return FALSE; // Get the text hResult = pTextRange->GetText(&bstr); if (hResult == S_OK) { long cchReconvert = _cpReconvertEnd - _cpReconvertStart + 1; CTempCharBuf tcb; char *psz = tcb.GetBuf((cchReconvert)*2); long cch = WideCharToMultiByte(_uKeyBoardCodePage, 0, bstr, -1, psz, (cchReconvert)*2, NULL, NULL); if (cch > 0) { long dwCompStrOffset, dwCompStrLen; CTempWcharBuf twcb; WCHAR *pwsz = twcb.GetBuf(cchReconvert); dwCompStrOffset = MultiByteToWideChar(_uKeyBoardCodePage, 0, psz, lpRCS->dwCompStrOffset, pwsz, cchReconvert); dwCompStrLen = MultiByteToWideChar(_uKeyBoardCodePage, 0, psz+lpRCS->dwCompStrOffset, lpRCS->dwCompStrLen, pwsz, cchReconvert); Assert(dwCompStrOffset > 0 || dwCompStrLen > 0); cpImeSelectStart = _cpReconvertStart + dwCompStrOffset; cpImeSelectEnd = cpImeSelectStart + dwCompStrLen; } else hResult = S_FALSE; } if (bstr) SysFreeString (bstr); pTextRange->Release(); if (hResult != S_OK) return FALSE; } if (cpParaStart <= cpImeSelectStart && cpImeSelectEnd <= cpParaEnd) { if (_pTextSel && (cpImeSelectStart != cpMin || cpImeSelectEnd != cpMax)) { // IME changes selection. hResult = _pTextSel->SetRange(cpImeSelectStart, cpImeSelectEnd); if (hResult != NOERROR) return FALSE; } return TRUE; // Allow Ime to change selection } return FALSE; } /* * BOOL CTextMsgFilter::GetTxSelection() * * @mfunc * Get Selection if we haven't got it before * * @rdesc * TRUE if this is first time getting the selection * FALSE if it is already exist or no selection available. */ BOOL CTextMsgFilter::GetTxSelection() { HRESULT hResult; if (_pTextSel) return FALSE; // Already there hResult = _pTextDoc->GetSelectionEx(&_pTextSel); return _pTextSel ? TRUE : FALSE; } /* * HRESULT CTextMsgFilter::OnIMEQueryPos(UINT *, WPARAM *, LPARAM *, LRESULT *, BOOL) * * @mfunc * Fill in the current character size and window rect. size. * * @rdesc * S_OK * *plres = 0 if we do not filled in data */ HRESULT CTextMsgFilter::OnIMEQueryPos( UINT * pmsg, WPARAM * pwparam, LPARAM * plparam, LRESULT * plres, BOOL fUnicode) { HRESULT hResult; PIMECHARPOSITION pIMECharPos = (PIMECHARPOSITION)*plparam; long cpRequest; RECT rcArea; ITextRange *pTextRange = NULL; POINT ptTopPos, ptBottomPos = {0, 0}; bool fGetBottomPosFail = false; if (pIMECharPos->dwSize != sizeof(IMECHARPOSITION)) goto Exit; // NT doesn't support Ansi window when the CP_ACP isn't the same // as keyboard codepage. if (!fUnicode && !(W32->OnWin9x()) && _uKeyBoardCodePage != _uSystemCodePage) goto Exit; if (IsIMEComposition() && _ime->GetIMELevel() == IME_LEVEL_3) { cpRequest = ((CIme_Lev3 *)_ime)->GetIMECompositionStart(); if (fUnicode) cpRequest += pIMECharPos->dwCharPos; else if (pIMECharPos->dwCharPos > 0) { // Need to convert pIMECharPos->dwCharPos from Acp to Cp long cchComp = ((CIme_Lev3 *)_ime)->GetIMECompositionLen(); long cchAcp = (long)(pIMECharPos->dwCharPos); BSTR bstr; WCHAR *pChar; if (cchComp) { hResult = _pTextDoc->Range(cpRequest, cpRequest+cchComp, &pTextRange); Assert (pTextRange != NULL); if (hResult != NOERROR || !pTextRange) goto Exit; hResult = pTextRange->GetText(&bstr); if (hResult != NOERROR ) goto Exit; // The algorithm assumes that for a DBCS charset any character // above 128 has two bytes, except for the halfwidth KataKana, // which are single bytes in ShiftJis. pChar = (WCHAR *)bstr; Assert (pChar); while (cchAcp > 0 && cchComp > 0) { cchAcp--; if(*pChar >= 128 && (CP_JAPAN != _uKeyBoardCodePage || !IN_RANGE(0xFF61, *pChar, 0xFF9F))) cchAcp--; pChar++; cchComp--; cpRequest++; } SysFreeString (bstr); pTextRange->Release(); pTextRange = NULL; } } } else if (pIMECharPos->dwCharPos == 0) { // Get current selection hResult = _pTextSel->GetStart(&cpRequest); if (hResult != NOERROR) goto Exit; } else goto Exit; // Get requested cp location in screen coordinates hResult = _pTextDoc->Range(cpRequest, cpRequest+1, &pTextRange); Assert (pTextRange != NULL); if (hResult != NOERROR || !pTextRange) goto Exit; hResult = pTextRange->GetPoint( tomStart+TA_TOP+TA_LEFT, &(ptTopPos.x), &(ptTopPos.y) ); if (hResult != NOERROR) { // Scroll and try again hResult = pTextRange->ScrollIntoView(tomStart); if (hResult == NOERROR) hResult = pTextRange->GetPoint( tomStart+TA_TOP+TA_LEFT, &(ptTopPos.x), &(ptTopPos.y) ); } if (hResult == NOERROR) { hResult = pTextRange->GetPoint( tomStart+TA_BOTTOM+TA_LEFT, &(ptBottomPos.x), &(ptBottomPos.y) ); if (hResult != NOERROR) fGetBottomPosFail = true; } pIMECharPos->pt = ptTopPos; // Get application rect in screen coordinates hResult = _pTextDoc->GetClientRect(tomIncludeInset, &(rcArea.left), &(rcArea.top), &(rcArea.right), &(rcArea.bottom)); if (hResult != NOERROR) goto Exit; // Get line height in pixel if (fGetBottomPosFail) pIMECharPos->cLineHeight = rcArea.bottom - ptTopPos.y; else pIMECharPos->cLineHeight = ptBottomPos.y - ptTopPos.y; pIMECharPos->rcDocument = rcArea; *plres = TRUE; Exit: if (pTextRange) pTextRange->Release(); return S_OK; } /* * CTextMsgFilter::CheckIMEType(HKL hKL) * * @mfunc * Check for MSIME98 or later * */ void CTextMsgFilter::CheckIMEType( HKL hKL) { if (!hKL) hKL = GetKeyboardLayout(0x0FFFFFFFF); // Get default HKL if caller pass in NULL // initialize to non MS IME _fMSIME = 0; if (IsFELCID((WORD)hKL)) { // Check what kind of IME user selected if (MSIMEServiceMsg && IMEMessage( *this, MSIMEServiceMsg, 0, 0, FALSE )) _fMSIME = 1; } } /* * CTextMsgFilter::InputFEChar(WCHAR wchFEChar) * * @mfunc * Input the FE character and ensure we have a correct font. * * @rdesc * S_OK if handled */ HRESULT CTextMsgFilter::InputFEChar( WCHAR wchFEChar) { BOOL bReleaseSelction = GetTxSelection(); long cchExced; HRESULT hr = S_FALSE; if (wchFEChar > 256 && _pTextSel->CanEdit(NULL) == NOERROR && _pTextDoc->CheckTextLimit(1, &cchExced) == NOERROR && cchExced == 0) { // setup FE font to handle the FE character long cpMin, cpMax; TCHAR wchFE[2]; BOOL fSelect = FALSE; ITextRange *pTextRange = NULL; ITextFont *pTextFont = NULL; ITextFont *pFEFont = NULL; HRESULT hResult = S_FALSE; BSTR bstr = NULL; // Inform client IME compostion is on to by-pass some font setting // problem in Arabic systems _pTextDoc->IMEInProgress(tomTrue); wchFE[0] = wchFEChar; wchFE[1] = L'\0'; _pTextSel->GetStart(&cpMin); _pTextSel->GetEnd(&cpMax); // For selection case, we want font to the right of first character if (cpMin != cpMax) { hResult = _pTextDoc->Range(cpMin, cpMin, &pTextRange); if (hResult != S_OK) goto ERROR_EXIT; hResult = pTextRange->GetFont(&pTextFont); cpMin++; fSelect = TRUE; } else hResult = _pTextSel->GetFont(&pTextFont); // Get a duplicate font and setup the correct FE font hResult = pTextFont->GetDuplicate(&pFEFont); if (hResult != S_OK) goto ERROR_EXIT; CIme::CheckKeyboardFontMatching (cpMin, *this, pFEFont); if (fSelect) _pTextSel->SetText(NULL); // Delete the selection bstr = SysAllocString(wchFE); if (!bstr) { hResult = E_OUTOFMEMORY; goto ERROR_EXIT; } _pTextSel->SetFont(pFEFont); // Setup FE font _pTextSel->TypeText(bstr); // Input the new FE character ERROR_EXIT: if (hResult == S_OK) hr = S_OK; if (pFEFont) pFEFont->Release(); if (pTextFont) pTextFont->Release(); if (pTextRange) pTextRange->Release(); if (bstr) SysFreeString(bstr); // Inform client IME compostion is done _pTextDoc->IMEInProgress(tomFalse); } if (bReleaseSelction && _pTextSel) { _pTextSel->Release(); _pTextSel = NULL; } return hr; } /* * CTextMsgFilter::OnSetFocus() * * @mfunc * Restore the previous keyboard if we are in FORCEREMEMBER mode. * Otherwise, setup the FE keyboard. * */ void CTextMsgFilter::OnSetFocus() { if (!_hwnd) return; if (_fForceRemember && _fIMEHKL) { // Restore previous keyboard ActivateKeyboardLayout(_fIMEHKL, 0); if (IsFELCID((WORD)_fIMEHKL)) { // Set Open status and Conversion mode HIMC hIMC = ImmGetContext(_hwnd); if (hIMC) { if (ImmSetOpenStatus(hIMC, _fIMEEnable, _fUsingAIMM) && _fIMEEnable) ImmSetConversionStatus(hIMC, _fIMEConversion, _fIMESentence, _fUsingAIMM); // Set conversion status ImmReleaseContext(_hwnd, hIMC); } } } else SetupIMEOptions(); } /* * CTextMsgFilter::OnKillFocus() * * @mfunc * If we are in FORCE_REMEMBER mode, save the current keyboard * and conversion setting. * */ void CTextMsgFilter::OnKillFocus() { if (!_hwnd) return; if (_fForceRemember) { // Get current keyboard _fIMEHKL = GetKeyboardLayout(0x0FFFFFFFF); if (IsFELCID((WORD)_fIMEHKL)) { // Get Open status HIMC hIMC = ImmGetContext(_hwnd); if (hIMC) { _fIMEEnable = ImmGetOpenStatus(hIMC, _fUsingAIMM); if (_fIMEEnable) ImmGetConversionStatus(hIMC, &_fIMEConversion, &_fIMESentence, _fUsingAIMM); // get conversion status ImmReleaseContext(_hwnd, hIMC); } } } } /* * CTextMsgFilter::OnSetIMEOptions(WPARAM wparam, LPARAM lparam) * * @mfunc * * @rdesc */ LRESULT CTextMsgFilter::OnSetIMEOptions( WPARAM wparam, LPARAM lparam) { LRESULT lIMEOptionCurrent = OnGetIMEOptions(); LRESULT lIMEOptionNew = 0; // Mask off bits that we will support for now lparam &= (IMF_FORCEACTIVE | IMF_FORCEENABLE | IMF_FORCEREMEMBER); switch(wparam) { case ECOOP_SET: lIMEOptionNew = lparam; break; case ECOOP_OR: lIMEOptionNew = lIMEOptionCurrent | lparam; break; case ECOOP_AND: lIMEOptionNew = lIMEOptionCurrent & lparam; break; case ECOOP_XOR: lIMEOptionNew = lIMEOptionCurrent ^ lparam; break; default: return 0; // Bad option } if (lIMEOptionNew == lIMEOptionCurrent) // Nothing change return 1; _fForceActivate = FALSE; if (lIMEOptionNew & IMF_FORCEACTIVE) _fForceActivate = TRUE; _fForceEnable = FALSE; if (lIMEOptionNew & IMF_FORCEENABLE) _fForceEnable = TRUE; _fForceRemember = FALSE; if (lIMEOptionNew & IMF_FORCEREMEMBER) _fForceRemember = TRUE; SetupIMEOptions(); return 1; } /* * CTextMsgFilter::OnGetIMEOptions() * * @mfunc * * @rdesc */ LRESULT CTextMsgFilter::OnGetIMEOptions() { LRESULT lres = 0; if (_fForceActivate) lres |= IMF_FORCEACTIVE; if (_fForceEnable) lres |= IMF_FORCEENABLE; if (_fForceRemember) lres |= IMF_FORCEREMEMBER; return lres; } /* * CTextMsgFilter::SetupIMEOptions() * * @mfunc * */ void CTextMsgFilter::SetupIMEOptions() { if (!_hwnd) return; _uKeyBoardCodePage = GetKeyboardCodePage(0x0FFFFFFFF); if (_fForceEnable) { LONG cpgLocale = GetACP(); BYTE bCharSet = (BYTE)GetCharSet(cpgLocale); if (W32->IsFECodePage(cpgLocale)) { if (_uKeyBoardCodePage != (UINT)cpgLocale) W32->CheckChangeKeyboardLayout(bCharSet); HIMC hIMC = ImmGetContext(_hwnd); if (hIMC) { if (ImmSetOpenStatus(hIMC, TRUE, _fUsingAIMM) && _fForceActivate) { // Activate native input mode DWORD dwConversion; DWORD dwSentence; if (ImmGetConversionStatus(hIMC, &dwConversion, &dwSentence, _fUsingAIMM)) { dwConversion |= IME_CMODE_NATIVE; if (bCharSet == SHIFTJIS_CHARSET) dwConversion |= IME_CMODE_FULLSHAPE; ImmSetConversionStatus(hIMC, dwConversion, dwSentence, _fUsingAIMM); } } ImmReleaseContext(_hwnd, hIMC); } } } }