1665 lines
38 KiB
C++
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|