windows-nt/Source/XPSP1/NT/windows/richedit/re30/cmsgflt.cpp
2020-09-26 16:20:57 +08:00

1665 lines
38 KiB
C++

/*
* @doc INTERNAL
*
* @module CMSGFLT.CPP -- Text Message Implementation |
*
* Most everything to do with IME message handling.
*
* Original Author: <nl>
* Hon Wah Chan
*
* History: <nl>
* 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);
}
}
}
}