|
|
//
// ic.cpp
//
#include "private.h"
#include "ic.h"
#include "range.h"
#include "tim.h"
#include "prop.h"
#include "tsi.h"
#include "rprop.h"
#include "funcprv.h"
#include "immxutil.h"
#include "acp2anch.h"
#include "dim.h"
#include "rangebk.h"
#include "view.h"
#include "compose.h"
#include "anchoref.h"
#include "dam.h"
#define TW_ICOWNERSINK_COOKIE 0x80000000 // high bit must be set to avoid conflict with GenericAdviseSink!
#define TW_ICKBDSINK_COOKIE 0x80000001 // high bit must be set to avoid conflict with GenericAdviseSink!
DBG_ID_INSTANCE(CInputContext);
/* 12e53b1b-7d7f-40bd-8f88-4603ee40cf58 */ extern const IID IID_PRIV_CINPUTCONTEXT = { 0x12e53b1b, 0x7d7f, 0x40bd, {0x8f, 0x88, 0x46, 0x03, 0xee, 0x40, 0xcf, 0x58} };
const IID *CInputContext::_c_rgConnectionIIDs[IC_NUM_CONNECTIONPTS] = { &IID_ITfTextEditSink, &IID_ITfTextLayoutSink, &IID_ITfStatusSink, &IID_ITfStartReconversionNotifySink, &IID_ITfEditTransactionSink, };
//+---------------------------------------------------------------------------
//
// ctor
//
//----------------------------------------------------------------------------
CInputContext::CInputContext(TfClientId tid) : CCompartmentMgr(tid, COMPTYPE_IC) { Dbg_MemSetThisNameIDCounter(TEXT("CInputContext"), PERF_CONTEXT_COUNTER);
// we sometimes use _dwLastLockReleaseID-1, which must still be > IGNORE_LAST_LOCKRELEASED
// Issue: need to handle wrap-around case
_dwLastLockReleaseID = (DWORD)((int)IGNORE_LAST_LOCKRELEASED + 2); }
//+---------------------------------------------------------------------------
//
// _Init
//
//----------------------------------------------------------------------------
HRESULT CInputContext::_Init(CThreadInputMgr *tim, CDocumentInputManager *dm, ITextStoreAnchor *ptsi, ITfContextOwnerCompositionSink *pOwnerComposeSink) { CTextStoreImpl *ptsiACP;
_dm = dm; // no need to AddRef, cleared when the dm dies
// scramble the _ec a bit to help debug calling EditSession on the wrong ic
_ec = (TfEditCookie)((DWORD)(UINT_PTR)this<<8); if (_ec < EC_MIN) // for portability, win32 pointers can't have values this low
{ _ec = EC_MIN; }
if (ptsi == NULL) { if ((ptsiACP = new CTextStoreImpl(this)) == NULL) return E_OUTOFMEMORY;
_ptsi = new CACPWrap(ptsiACP); ptsiACP->Release();
if (_ptsi == NULL) return E_OUTOFMEMORY;
_fCiceroTSI = TRUE; } else { _ptsi = ptsi; _ptsi->AddRef();
Assert(_fCiceroTSI == FALSE); }
_pOwnerComposeSink = pOwnerComposeSink; if (_pOwnerComposeSink != NULL) { _pOwnerComposeSink->AddRef(); }
Assert(_pMSAAState == NULL); if (tim->_IsMSAAEnabled()) { _InitMSAAHook(tim->_GetAAAdaptor()); }
Assert(_dwEditSessionFlags == 0); Assert(_dbg_fInOnLockGranted == FALSE);
Assert(_fLayoutChanged == FALSE); Assert(_dwStatusChangedFlags == 0); Assert(_fStatusChanged == FALSE);
_tidInEditSession = TF_CLIENTID_NULL;
Assert(_pPropTextOwner == NULL); _dwSysFuncPrvCookie = GENERIC_ERROR_COOKIE;
_gaKeyEventFilterTIP[LEFT_FILTERTIP] = TF_INVALID_GUIDATOM; _gaKeyEventFilterTIP[RIGHT_FILTERTIP] = TF_INVALID_GUIDATOM; _fInvalidKeyEventFilterTIP = TRUE;
_pEditRecord = new CEditRecord(this); // perf: delay load
if (!_pEditRecord) return E_OUTOFMEMORY;
Assert(_pActiveView == NULL);
return S_OK; }
//+---------------------------------------------------------------------------
//
// dtor
//
//----------------------------------------------------------------------------
CInputContext::~CInputContext() { CProperty *pProp; int i; SPAN *span;
// being paranoid...these should be NULL
Assert(_pICKbdSink == NULL);
// nix any allocated properties
while (_pPropList != NULL) { pProp = _pPropList; _pPropList = _pPropList->_pNext; delete pProp; }
// nix any cached app changes
for (i=0; i<_rgAppTextChanges.Count(); i++) { span = _rgAppTextChanges.GetPtr(i); span->paStart->Release(); span->paEnd->Release(); }
Assert(_pMSAAState == NULL); // should have already cleaned up msaa hook
Assert(_pOwnerComposeSink == NULL); // should cleared in _UnadviseSinks
SafeRelease(_pOwnerComposeSink);
Assert(_ptsi == NULL); // should be NULL, cleared in _UnadviseSinks
SafeRelease(_ptsi);
Assert(_pCompositionList == NULL); // all compositions should be terminated
Assert(_rgLockQueue.Count() == 0); // all queue items should be freed
//
// all pending flag should be cleared
// Otherwise psfn->_dwfLockRequestICRef could be broken..
//
Assert(_dwPendingLockRequest == 0); }
//+---------------------------------------------------------------------------
//
// _AdviseSinks
//
// Called when this ic is pushed.
//
//----------------------------------------------------------------------------
void CInputContext::_AdviseSinks() { if (_ptsi != NULL) { // attach our ITextStoreAnchorSink
_ptsi->AdviseSink(IID_ITextStoreAnchorSink, SAFECAST(this, ITextStoreAnchorSink *), TS_AS_ALL_SINKS); } }
//+---------------------------------------------------------------------------
//
// _UnadviseSinks
//
// Called when this ic is popped.
// All references to the ITextStore impl should be released here.
//
//----------------------------------------------------------------------------
void CInputContext::_UnadviseSinks(CThreadInputMgr *tim) { // kill any compositions
_AbortCompositions();
SafeReleaseClear(_pEditRecord); SafeReleaseClear(_pActiveView);
// for now just skip any pending edit sessions
// do this here in case any of the edit sessions
// have a ref to this ic
_AbortQueueItems();
if (_ptsi != NULL) { // detach our ITextStoreAnchorSink
_ptsi->UnadviseSink(SAFECAST(this, ITextStoreAnchorSink *)); // if there is an ic owner sink, unadvise it now while we still can
// this is to help buggy clients
_UnadviseOwnerSink();
// if we're msaa hooked, unhook now
// must do this before Releasing _ptsi
if (_pMSAAState != NULL) { Assert(tim->_GetAAAdaptor() != NULL); _UninitMSAAHook(tim->_GetAAAdaptor()); }
// and let the ptsi go
_ptsi->Release(); _ptsi = NULL; }
SafeReleaseClear(_pOwnerComposeSink);
// our owning doc is no longer valid
_dm = NULL; }
//+---------------------------------------------------------------------------
//
// AdviseSink
//
//----------------------------------------------------------------------------
STDAPI CInputContext::AdviseSink(REFIID riid, IUnknown *punk, DWORD *pdwCookie) { ITfContextOwner *pICOwnerSink; CTextStoreImpl *ptsi; IServiceProvider *psp; HRESULT hr;
if (pdwCookie == NULL) return E_INVALIDARG;
*pdwCookie = GENERIC_ERROR_COOKIE;
if (punk == NULL) return E_INVALIDARG;
if (!_IsConnected()) return TF_E_DISCONNECTED;
if (IsEqualIID(riid, IID_ITfContextOwner)) { // there can be only one ic owner sink, so special case it
if (!_IsCiceroTSI()) { Assert(0); // sink should only used for def tsi
return E_UNEXPECTED; }
// use QueryService to get the tsi since msaa may be wrapping it
if (_ptsi->QueryInterface(IID_IServiceProvider, (void **)&psp) != S_OK) { Assert(0); return E_UNEXPECTED; }
hr = psp->QueryService(GUID_SERVICE_TF, IID_PRIV_CTSI, (void **)&ptsi);
psp->Release();
if (hr != S_OK) { Assert(0); return E_UNEXPECTED; }
pICOwnerSink = NULL;
if (ptsi->_HasOwner()) { hr = CONNECT_E_ADVISELIMIT; goto ExitOwner; }
if (FAILED(punk->QueryInterface(IID_ITfContextOwner, (void **)&pICOwnerSink))) { hr = E_UNEXPECTED; goto ExitOwner; }
ptsi->_AdviseOwner(pICOwnerSink);
ExitOwner: ptsi->Release(); SafeRelease(pICOwnerSink);
if (hr == S_OK) { *pdwCookie = TW_ICOWNERSINK_COOKIE; }
return hr; } else if (IsEqualIID(riid, IID_ITfContextKeyEventSink)) { // there can be only one ic kbd sink, so special case it
if (_pICKbdSink != NULL) return CONNECT_E_ADVISELIMIT;
if (FAILED(punk->QueryInterface(IID_ITfContextKeyEventSink, (void **)&_pICKbdSink))) return E_UNEXPECTED;
*pdwCookie = TW_ICKBDSINK_COOKIE;
return S_OK; }
return GenericAdviseSink(riid, punk, _c_rgConnectionIIDs, _rgSinks, IC_NUM_CONNECTIONPTS, pdwCookie); }
//+---------------------------------------------------------------------------
//
// UnadviseSink
//
//----------------------------------------------------------------------------
STDAPI CInputContext::UnadviseSink(DWORD dwCookie) { if (dwCookie == TW_ICOWNERSINK_COOKIE) { // there can be only one ic owner sink, so special case it
return _UnadviseOwnerSink(); } else if (dwCookie == TW_ICKBDSINK_COOKIE) { // there can be only one ic owner sink, so special case it
if (_pICKbdSink == NULL) return CONNECT_E_NOCONNECTION;
SafeReleaseClear(_pICKbdSink); return S_OK; }
return GenericUnadviseSink(_rgSinks, IC_NUM_CONNECTIONPTS, dwCookie); }
//+---------------------------------------------------------------------------
//
// AdviseSingleSink
//
//----------------------------------------------------------------------------
STDAPI CInputContext::AdviseSingleSink(TfClientId tid, REFIID riid, IUnknown *punk) { CTip *ctip; CLEANUPSINK *pCleanup; CThreadInputMgr *tim; ITfCleanupContextSink *pSink;
if (punk == NULL) return E_INVALIDARG;
if ((tim = CThreadInputMgr::_GetThis()) == NULL) return E_FAIL;
if (!tim->_GetCTipfromGUIDATOM(tid, &ctip) && (tid != g_gaApp)) return E_INVALIDARG;
if (IsEqualIID(riid, IID_ITfCleanupContextSink)) { if (_GetCleanupListIndex(tid) >= 0) return CONNECT_E_ADVISELIMIT;
if (punk->QueryInterface(IID_ITfCleanupContextSink, (void **)&pSink) != S_OK) return E_NOINTERFACE;
if ((pCleanup = _rgCleanupSinks.Append(1)) == NULL) { pSink->Release(); return E_OUTOFMEMORY; }
pCleanup->tid = tid; pCleanup->pSink = pSink;
return S_OK; }
return CONNECT_E_CANNOTCONNECT; }
//+---------------------------------------------------------------------------
//
// UnadviseSingleSink
//
//----------------------------------------------------------------------------
STDAPI CInputContext::UnadviseSingleSink(TfClientId tid, REFIID riid) { int i;
if (IsEqualIID(riid, IID_ITfCleanupContextSink)) { if ((i = _GetCleanupListIndex(tid)) < 0) return CONNECT_E_NOCONNECTION;
_rgCleanupSinks.GetPtr(i)->pSink->Release(); _rgCleanupSinks.Remove(i, 1);
return S_OK; }
return CONNECT_E_NOCONNECTION; }
//+---------------------------------------------------------------------------
//
// _UnadviseOwnerSink
//
//----------------------------------------------------------------------------
HRESULT CInputContext::_UnadviseOwnerSink() { IServiceProvider *psp; CTextStoreImpl *ptsi; HRESULT hr;
if (!_IsCiceroTSI()) return E_UNEXPECTED; // only our default tsi can accept an owner sink
if (!_IsConnected()) // _ptsi is not safe if disconnected
return TF_E_DISCONNECTED;
// use QueryService to get the tsi since msaa may be wrapping it
if (_ptsi->QueryInterface(IID_IServiceProvider, (void **)&psp) != S_OK) { Assert(0); return E_UNEXPECTED; }
hr = psp->QueryService(GUID_SERVICE_TF, IID_PRIV_CTSI, (void **)&ptsi);
psp->Release();
if (hr != S_OK) { Assert(0); return E_UNEXPECTED; }
if (!ptsi->_HasOwner()) { hr = CONNECT_E_NOCONNECTION; goto Exit; }
ptsi->_UnadviseOwner();
hr = S_OK;
Exit: ptsi->Release(); return hr; }
//+---------------------------------------------------------------------------
//
// GetProperty
//
//----------------------------------------------------------------------------
STDAPI CInputContext::GetProperty(REFGUID rguidProp, ITfProperty **ppv) { CProperty *property; HRESULT hr;
if (!_IsConnected()) return TF_E_DISCONNECTED;
hr = _GetProperty(rguidProp, &property);
*ppv = property; return hr; }
//+---------------------------------------------------------------------------
//
// _GetProperty
//
//----------------------------------------------------------------------------
HRESULT CInputContext::_GetProperty(REFGUID rguidProp, CProperty **ppv) { CProperty *pProp = _FindProperty(rguidProp); DWORD dwAuthority = PROPA_NONE; TFPROPERTYSTYLE propStyle; DWORD dwPropFlags;
if (ppv == NULL) return E_INVALIDARG;
*ppv = pProp;
if (pProp != NULL) { (*ppv)->AddRef(); return S_OK; }
//
// Overwrite propstyle for known properties.
//
if (IsEqualGUID(rguidProp, GUID_PROP_ATTRIBUTE)) { propStyle = TFPROPSTYLE_STATIC; dwAuthority = PROPA_FOCUSRANGE | PROPA_TEXTOWNER | PROPA_WONT_SERIALZE; dwPropFlags = PROPF_VTI4TOGUIDATOM; } else if (IsEqualGUID(rguidProp, GUID_PROP_READING)) { propStyle = TFPROPSTYLE_CUSTOM; dwPropFlags = 0; } else if (IsEqualGUID(rguidProp, GUID_PROP_COMPOSING)) { propStyle = TFPROPSTYLE_STATICCOMPACT; dwAuthority = PROPA_READONLY | PROPA_WONT_SERIALZE; dwPropFlags = 0; } else if (IsEqualGUID(rguidProp, GUID_PROP_LANGID)) { propStyle = TFPROPSTYLE_STATICCOMPACT; dwPropFlags = 0; } else if (IsEqualGUID(rguidProp, GUID_PROP_TEXTOWNER)) { propStyle = TFPROPSTYLE_STATICCOMPACT; dwAuthority = PROPA_TEXTOWNER; dwPropFlags = PROPF_ACCEPTCORRECTION | PROPF_VTI4TOGUIDATOM; } else { propStyle = _GetPropStyle(rguidProp); dwPropFlags = 0;
// nb: after a property is created, the PROPF_MARKUP_COLLECTION is never
// again set. We make sure to call ITfDisplayAttributeCollectionProvider::GetCollection
// before activating tips that use the property GUID.
if (CDisplayAttributeMgr::_IsInCollection(rguidProp)) { dwPropFlags = PROPF_MARKUP_COLLECTION; } } //
// Allow NULL propStyle for only predefined properties.
// Check the property style is correct.
//
if (!propStyle) { Assert(0); return E_FAIL; }
pProp = new CProperty(this, rguidProp, propStyle, dwAuthority, dwPropFlags);
if (pProp) { pProp->_pNext = _pPropList; _pPropList = pProp;
// Update _pPropTextOner now.
if (IsEqualGUID(rguidProp, GUID_PROP_TEXTOWNER)) _pPropTextOwner = pProp; }
if (*ppv = pProp) { (*ppv)->AddRef(); return S_OK; }
return E_OUTOFMEMORY; }
//+---------------------------------------------------------------------------
//
// GetTextOwnerProperty
//
//----------------------------------------------------------------------------
CProperty *CInputContext::GetTextOwnerProperty() { ITfProperty *prop;
// GetProperty initializes _pPropTextOwner.
if (!_pPropTextOwner) { GetProperty(GUID_PROP_TEXTOWNER, &prop); SafeRelease(prop); }
Assert(_pPropTextOwner); return _pPropTextOwner; }
//+---------------------------------------------------------------------------
//
// _FindProperty
//
//----------------------------------------------------------------------------
CProperty *CInputContext::_FindProperty(TfGuidAtom gaProp) { CProperty *pProp = _pPropList;
// perf: should this be faster?
while (pProp) { if (pProp->GetPropGuidAtom() == gaProp) return pProp;
pProp = pProp->_pNext; }
return NULL; }
//+---------------------------------------------------------------------------
//
// _PropertyTextUpdate
//
//----------------------------------------------------------------------------
void CInputContext::_PropertyTextUpdate(DWORD dwFlags, IAnchor *paStart, IAnchor *paEnd) { CProperty *pProp = _pPropList; DWORD dwPrevESFlag = _dwEditSessionFlags;
_dwEditSessionFlags |= TF_ES_INNOTIFY;
while (pProp) { // clear the values over the edited text
pProp->Clear(paStart, paEnd, dwFlags, TRUE /* fTextUpdate */);
if (pProp->GetPropStyle() == TFPROPSTYLE_STATICCOMPACT || pProp->GetPropStyle() == TFPROPSTYLE_CUSTOM_COMPACT) { pProp->Defrag(paStart, paEnd); }
pProp = pProp->_pNext; }
_dwEditSessionFlags = dwPrevESFlag; }
//+---------------------------------------------------------------------------
//
// _GetStartOrEnd
//
//----------------------------------------------------------------------------
HRESULT CInputContext::_GetStartOrEnd(TfEditCookie ec, BOOL fStart, ITfRange **ppStart) { CRange *range; IAnchor *paStart; IAnchor *paEnd; HRESULT hr;
if (ppStart == NULL) return E_INVALIDARG;
*ppStart = NULL;
if (!_IsConnected()) return TF_E_DISCONNECTED;
if (!_IsValidEditCookie(ec, TF_ES_READ)) { Assert(0); return TF_E_NOLOCK; }
hr = fStart ? _ptsi->GetStart(&paStart) : _ptsi->GetEnd(&paStart);
if (hr == E_NOTIMPL) return E_NOTIMPL; if (hr != S_OK) return E_FAIL;
hr = E_FAIL;
if (FAILED(paStart->Clone(&paEnd))) goto Exit;
if ((range = new CRange) == NULL) { hr = E_OUTOFMEMORY; goto Exit; } if (!range->_InitWithDefaultGravity(this, OWN_ANCHORS, paStart, paEnd)) { range->Release(); goto Exit; }
*ppStart = (ITfRangeAnchor *)range;
hr = S_OK;
Exit: if (hr != S_OK) { SafeRelease(paStart); SafeRelease(paEnd); }
return hr; }
//+---------------------------------------------------------------------------
//
// CreateRange
//
//----------------------------------------------------------------------------
STDAPI CInputContext::CreateRange(IAnchor *paStart, IAnchor *paEnd, ITfRangeAnchor **ppRange) { CRange *range;
if (ppRange == NULL) return E_INVALIDARG;
*ppRange = NULL;
if (paStart == NULL || paEnd == NULL) return E_INVALIDARG;
if (CompareAnchors(paStart, paEnd) > 0) return E_INVALIDARG;
if ((range = new CRange) == NULL) return E_OUTOFMEMORY;
if (!range->_InitWithDefaultGravity(this, COPY_ANCHORS, paStart, paEnd)) { range->Release(); return E_FAIL; }
*ppRange = range;
return S_OK; }
//+---------------------------------------------------------------------------
//
// CreateRange
//
//----------------------------------------------------------------------------
STDAPI CInputContext::CreateRange(LONG acpStart, LONG acpEnd, ITfRangeACP **ppRange) { IServiceProvider *psp; CRange *range; CACPWrap *pACPWrap; IAnchor *paStart; IAnchor *paEnd; HRESULT hr;
if (ppRange == NULL) return E_INVALIDARG;
pACPWrap = NULL; *ppRange = NULL; paEnd = NULL; hr = E_FAIL;
if (acpStart > acpEnd) return E_INVALIDARG;
if (_ptsi->QueryInterface(IID_IServiceProvider, (void **)&psp) == S_OK) { if (psp->QueryService(GUID_SERVICE_TF, IID_PRIV_ACPWRAP, (void **)&pACPWrap) == S_OK) { // the actual impl is acp based, so this is easy
if ((paStart = pACPWrap->_CreateAnchorACP(acpStart, TS_GR_BACKWARD)) == NULL) goto Exit; if ((paEnd = pACPWrap->_CreateAnchorACP(acpEnd, TS_GR_FORWARD)) == NULL) goto Exit; } else { // in case QueryService sets it on failure to garbage...
pACPWrap = NULL; } psp->Release(); }
if (paEnd == NULL) // failure above?
{ Assert(0); // who's calling this?
// caller should know whether or not it has an acp text store.
// we don't, so we won't support this case.
hr = E_FAIL; goto Exit; }
if ((range = new CRange) == NULL) { hr = E_OUTOFMEMORY; goto Exit; }
if (!range->_InitWithDefaultGravity(this, OWN_ANCHORS, paStart, paEnd)) { range->Release(); goto Exit; }
*ppRange = range;
hr = S_OK;
Exit: SafeRelease(pACPWrap); if (hr != S_OK) { SafeRelease(paStart); SafeRelease(paEnd); } return hr; }
//+---------------------------------------------------------------------------
//
// _Pushed
//
//----------------------------------------------------------------------------
void CInputContext::_Pushed() { CThreadInputMgr *tim; if ((tim = CThreadInputMgr::_GetThis()) != NULL) tim->_NotifyCallbacks(TIM_INITIC, NULL, this); }
//+---------------------------------------------------------------------------
//
// _Popped
//
//----------------------------------------------------------------------------
void CInputContext::_Popped() { CThreadInputMgr *tim; if ((tim = CThreadInputMgr::_GetThis()) != NULL) tim->_NotifyCallbacks(TIM_UNINITIC, NULL, this);
//
// We release all properties and property stores.
//
CProperty *pProp;
while (_pPropList != NULL) { pProp = _pPropList; _pPropList = _pPropList->_pNext; pProp->Release(); } // we just free up the cached text property, so make sure
// we don't try to use it later!
_pPropTextOwner = NULL;
//
// We release all compartments.
//
CleanUp(); }
//+---------------------------------------------------------------------------
//
// _GetPropStyle
//
//----------------------------------------------------------------------------
const GUID *CInputContext::_c_rgPropStyle[] = { &GUID_TFCAT_PROPSTYLE_CUSTOM, // {0x24af3031,0x852d,0x40a2,{0xbc,0x09,0x89,0x92,0x89,0x8c,0xe7,0x22}},
&GUID_TFCAT_PROPSTYLE_STATIC, // {0x565fb8d8,0x6bd4,0x4ca1,{0xb2,0x23,0x0f,0x2c,0xcb,0x8f,0x4f,0x96}},
&GUID_TFCAT_PROPSTYLE_STATICCOMPACT, // {0x85f9794b,0x4d19,0x40d8,{0x88,0x64,0x4e,0x74,0x73,0x71,0xa6,0x6d}}
&GUID_TFCAT_PROPSTYLE_CUSTOM_COMPACT, };
TFPROPERTYSTYLE CInputContext::_GetPropStyle(REFGUID rguidProp) { GUID guidStyle = GUID_NULL;
CCategoryMgr::s_FindClosestCategory(rguidProp, &guidStyle, _c_rgPropStyle, ARRAYSIZE(_c_rgPropStyle));
if (IsEqualGUID(guidStyle, GUID_TFCAT_PROPSTYLE_CUSTOM)) return TFPROPSTYLE_CUSTOM; else if (IsEqualGUID(guidStyle, GUID_TFCAT_PROPSTYLE_STATIC)) return TFPROPSTYLE_STATIC; else if (IsEqualGUID(guidStyle, GUID_TFCAT_PROPSTYLE_STATICCOMPACT)) return TFPROPSTYLE_STATICCOMPACT; else if (IsEqualGUID(guidStyle, GUID_TFCAT_PROPSTYLE_CUSTOM_COMPACT)) return TFPROPSTYLE_CUSTOM_COMPACT;
return TFPROPSTYLE_NULL; }
//+---------------------------------------------------------------------------
//
// Serialize
//
//----------------------------------------------------------------------------
STDAPI CInputContext::Serialize(ITfProperty *pProp, ITfRange *pRange, TF_PERSISTENT_PROPERTY_HEADER_ACP *pHdr, IStream *pStream) { ITextStoreACPServices *ptss; HRESULT hr;
if (pHdr == NULL) return E_INVALIDARG;
memset(pHdr, 0, sizeof(*pHdr));
if (!_IsCiceroTSI()) return E_UNEXPECTED;
if (_ptsi->QueryInterface(IID_ITextStoreACPServices, (void **)&ptss) != S_OK) return E_FAIL;
hr = ptss->Serialize(pProp, pRange, pHdr, pStream);
ptss->Release();
return hr; }
//+---------------------------------------------------------------------------
//
// Unserialize
//
//----------------------------------------------------------------------------
STDAPI CInputContext::Unserialize(ITfProperty *pProp, const TF_PERSISTENT_PROPERTY_HEADER_ACP *pHdr, IStream *pStream, ITfPersistentPropertyLoaderACP *pLoader) { ITextStoreACPServices *ptss; HRESULT hr;
if (!_IsCiceroTSI()) return E_UNEXPECTED;
if (_ptsi->QueryInterface(IID_ITextStoreACPServices, (void **)&ptss) != S_OK) return E_FAIL;
hr = ptss->Unserialize(pProp, pHdr, pStream, pLoader);
ptss->Release();
return hr; }
//+---------------------------------------------------------------------------
//
// GetDocumentMgr
//
//----------------------------------------------------------------------------
STDAPI CInputContext::GetDocumentMgr(ITfDocumentMgr **ppDm) { CDocumentInputManager *dm;
if (ppDm == NULL) return E_INVALIDARG;
*ppDm = NULL;
if ((dm = _GetDm()) == NULL) return S_FALSE; // the ic has been popped
*ppDm = dm; (*ppDm)->AddRef();
return S_OK; }
//+---------------------------------------------------------------------------
//
// EnumProperties
//
//----------------------------------------------------------------------------
STDAPI CInputContext::EnumProperties(IEnumTfProperties **ppEnum) { CEnumProperties *pEnum;
if (ppEnum == NULL) return E_INVALIDARG;
*ppEnum = NULL;
if (!_IsConnected()) return TF_E_DISCONNECTED;
pEnum = new CEnumProperties;
if (!pEnum) return E_OUTOFMEMORY;
if (!pEnum->_Init(this)) return E_FAIL;
*ppEnum = pEnum;
return S_OK; } //+---------------------------------------------------------------------------
//
// GetStart
//
//----------------------------------------------------------------------------
STDAPI CInputContext::GetStart(TfEditCookie ec, ITfRange **ppStart) { return _GetStartOrEnd(ec, TRUE, ppStart); }
//+---------------------------------------------------------------------------
//
// GetEnd
//
//----------------------------------------------------------------------------
STDAPI CInputContext::GetEnd(TfEditCookie ec, ITfRange **ppEnd) { return _GetStartOrEnd(ec, FALSE, ppEnd); }
//+---------------------------------------------------------------------------
//
// GetStatus
//
//----------------------------------------------------------------------------
STDAPI CInputContext::GetStatus(TS_STATUS *pdcs) { if (pdcs == NULL) return E_INVALIDARG;
memset(pdcs, 0, sizeof(*pdcs));
if (!_IsConnected()) return TF_E_DISCONNECTED;
return _GetTSI()->GetStatus(pdcs); }
//+---------------------------------------------------------------------------
//
// CreateRangeBackup
//
//----------------------------------------------------------------------------
STDAPI CInputContext::CreateRangeBackup(TfEditCookie ec, ITfRange *pRange, ITfRangeBackup **ppBackup) { CRangeBackup *pRangeBackup; CRange *range; HRESULT hr;
if (!ppBackup) return E_INVALIDARG;
*ppBackup = NULL;
if (!_IsConnected()) return TF_E_DISCONNECTED;
if (!_IsValidEditCookie(ec, TF_ES_READ)) { Assert(0); return TF_E_NOLOCK; } if (!pRange) return E_INVALIDARG;
if ((range = GetCRange_NA(pRange)) == NULL) return E_INVALIDARG;
if (!VerifySameContext(this, range)) return E_INVALIDARG;
range->_QuickCheckCrossedAnchors();
pRangeBackup = new CRangeBackup(this); if (!pRangeBackup) return E_OUTOFMEMORY;
if (FAILED(hr = pRangeBackup->Init(ec, range))) { pRangeBackup->Clear(); pRangeBackup->Release(); return E_FAIL; }
*ppBackup = pRangeBackup; return hr; }
//+---------------------------------------------------------------------------
//
// QueryService
//
//----------------------------------------------------------------------------
STDAPI CInputContext::QueryService(REFGUID guidService, REFIID riid, void **ppv) { IServiceProvider *psp; HRESULT hr;
if (ppv == NULL) return E_INVALIDARG;
*ppv = NULL;
if (IsEqualGUID(guidService, GUID_SERVICE_TEXTSTORE) && IsEqualIID(riid, IID_IServiceProvider)) { // caller wants to talk to the text store
if (_ptsi == NULL) return E_FAIL;
// we use an extra level of indirection, asking the IServiceProvider for an IServiceProvider
// because we want to leave the app free to not expose the ITextStore object
// otherwise tips could QI the IServiceProvider for ITextStore
if (_ptsi->QueryInterface(IID_IServiceProvider, (void **)&psp) != S_OK || psp == NULL) return E_FAIL;
hr = psp->QueryService(GUID_SERVICE_TEXTSTORE, IID_IServiceProvider, ppv);
psp->Release();
return hr; }
if (!IsEqualGUID(guidService, GUID_SERVICE_TF) || !IsEqualIID(riid, IID_PRIV_CINPUTCONTEXT)) { // SVC_E_NOSERVICE is proper return code for wrong service....
// but it's not defined anywhere. So use E_NOINTERFACE for both
// cases as trident is rumored to do
return E_NOINTERFACE; }
*ppv = this; AddRef();
return S_OK; }
//+---------------------------------------------------------------------------
//
// AdviseMouseSink
//
//----------------------------------------------------------------------------
STDAPI CInputContext::AdviseMouseSink(ITfRange *range, ITfMouseSink *pSink, DWORD *pdwCookie) { CRange *pCRange; CRange *pClone; ITfMouseTrackerAnchor *pTrackerAnchor; ITfMouseTrackerACP *pTrackerACP; HRESULT hr;
if (pdwCookie == NULL) return E_INVALIDARG;
*pdwCookie = 0;
if (range == NULL || pSink == NULL) return E_INVALIDARG;
if ((pCRange = GetCRange_NA(range)) == NULL) return E_INVALIDARG;
if (!VerifySameContext(this, pCRange)) return E_INVALIDARG;
if (!_IsConnected()) return TF_E_DISCONNECTED;
pTrackerACP = NULL;
if (_ptsi->QueryInterface(IID_ITfMouseTrackerAnchor, (void **)&pTrackerAnchor) != S_OK) { pTrackerAnchor = NULL; // we also try IID_ITfMouseTrackerACP for the benefit of wrapped implementations who
// just want to forward the request off to an ACP app
if (_ptsi->QueryInterface(IID_ITfMouseTrackerACP, (void **)&pTrackerACP) != S_OK) return E_NOTIMPL; }
hr = E_FAIL;
// need to pass on a clone, so app can hang onto range/anchors
if ((pClone = pCRange->_Clone()) == NULL) goto Exit;
hr = (pTrackerAnchor != NULL) ? pTrackerAnchor->AdviseMouseSink(pClone->_GetStart(), pClone->_GetEnd(), pSink, pdwCookie) : pTrackerACP->AdviseMouseSink((ITfRangeACP *)pClone, pSink, pdwCookie);
pClone->Release();
Exit: SafeRelease(pTrackerAnchor); SafeRelease(pTrackerACP);
return hr; }
//+---------------------------------------------------------------------------
//
// UnadviseMouseSink
//
//----------------------------------------------------------------------------
STDAPI CInputContext::UnadviseMouseSink(DWORD dwCookie) { ITfMouseTrackerAnchor *pTrackerAnchor; ITfMouseTrackerACP *pTrackerACP; HRESULT hr;
if (!_IsConnected()) return TF_E_DISCONNECTED;
if (_ptsi->QueryInterface(IID_ITfMouseTrackerAnchor, (void **)&pTrackerAnchor) == S_OK) { hr = pTrackerAnchor->UnadviseMouseSink(dwCookie); pTrackerAnchor->Release(); } else if (_ptsi->QueryInterface(IID_ITfMouseTrackerACP, (void **)&pTrackerACP) == S_OK) { // we also try IID_ITfMouseTrackerACP for the benefit of wrapped implementations who
// just want to forward the request off to an ACP app
hr = pTrackerACP->UnadviseMouseSink(dwCookie); pTrackerACP->Release(); } else { hr = E_NOTIMPL; }
return hr; }
//+---------------------------------------------------------------------------
//
// GetActiveView
//
//----------------------------------------------------------------------------
STDAPI CInputContext::GetActiveView(ITfContextView **ppView) { CContextView *pView; TsViewCookie vcActiveView; HRESULT hr;
if (ppView == NULL) return E_INVALIDARG;
*ppView = NULL;
if (!_IsConnected()) return TF_E_DISCONNECTED;
hr = _ptsi->GetActiveView(&vcActiveView);
if (hr != S_OK) { Assert(0); // why did it fail?
if (hr != E_NOTIMPL) return E_FAIL;
// for E_NOTIMPL, we will assume a single view and supply
// a constant value here
vcActiveView = 0; }
// Issue: for now, just supporting an active view
// need to to handle COM identity correctly for mult views
if (_pActiveView == NULL) { if ((_pActiveView = new CContextView(this, vcActiveView)) == NULL) return E_OUTOFMEMORY; }
pView = _pActiveView; pView->AddRef();
*ppView = pView;
return S_OK; }
//+---------------------------------------------------------------------------
//
// EnumView
//
//----------------------------------------------------------------------------
STDAPI CInputContext::EnumViews(IEnumTfContextViews **ppEnum) { if (ppEnum == NULL) return E_INVALIDARG;
*ppEnum = NULL;
if (!_IsConnected()) return TF_E_DISCONNECTED;
// Issue: support this
Assert(0); return E_NOTIMPL; }
//+---------------------------------------------------------------------------
//
// QueryInsertEmbedded
//
//----------------------------------------------------------------------------
STDAPI CInputContext::QueryInsertEmbedded(const GUID *pguidService, const FORMATETC *pFormatEtc, BOOL *pfInsertable) { if (pfInsertable == NULL) return E_INVALIDARG;
*pfInsertable = FALSE;
if (!_IsConnected()) return TF_E_DISCONNECTED;
return _ptsi->QueryInsertEmbedded(pguidService, pFormatEtc, pfInsertable); }
//+---------------------------------------------------------------------------
//
// InsertTextAtSelection
//
//----------------------------------------------------------------------------
STDAPI CInputContext::InsertTextAtSelection(TfEditCookie ec, DWORD dwFlags, const WCHAR *pchText, LONG cch, ITfRange **ppRange) { IAS_OBJ iasobj;
iasobj.type = IAS_OBJ::IAS_TEXT; iasobj.state.text.pchText = pchText; iasobj.state.text.cch = cch;
return _InsertXAtSelection(ec, dwFlags, &iasobj, ppRange); }
//+---------------------------------------------------------------------------
//
// InsertEmbeddedAtSelection
//
//----------------------------------------------------------------------------
STDAPI CInputContext::InsertEmbeddedAtSelection(TfEditCookie ec, DWORD dwFlags, IDataObject *pDataObject, ITfRange **ppRange) { IAS_OBJ iasobj;
iasobj.type = IAS_OBJ::IAS_DATAOBJ; iasobj.state.obj.pDataObject = pDataObject;
return _InsertXAtSelection(ec, dwFlags, &iasobj, ppRange); }
//+---------------------------------------------------------------------------
//
// _InsertXAtSelection
//
//----------------------------------------------------------------------------
HRESULT CInputContext::_InsertXAtSelection(TfEditCookie ec, DWORD dwFlags, IAS_OBJ *pObj, ITfRange **ppRange) { IAnchor *paStart; IAnchor *paEnd; CRange *range; CComposition *pComposition; HRESULT hr; BOOL fNoDefaultComposition;
if (ppRange == NULL) return E_INVALIDARG;
*ppRange = NULL;
if (pObj->type == IAS_OBJ::IAS_TEXT) { if (pObj->state.text.pchText == NULL && pObj->state.text.cch != 0) return E_INVALIDARG;
if (!(dwFlags & TS_IAS_QUERYONLY) && (pObj->state.text.pchText == NULL || pObj->state.text.cch == 0)) return E_INVALIDARG; } else { Assert(pObj->type == IAS_OBJ::IAS_DATAOBJ); if (!(dwFlags & TS_IAS_QUERYONLY) && pObj->state.obj.pDataObject == NULL) return E_INVALIDARG; }
if ((dwFlags & (TS_IAS_NOQUERY | TS_IAS_QUERYONLY)) == (TS_IAS_NOQUERY | TS_IAS_QUERYONLY)) return E_INVALIDARG;
if ((dwFlags & ~(TS_IAS_NOQUERY | TS_IAS_QUERYONLY | TF_IAS_NO_DEFAULT_COMPOSITION)) != 0) return E_INVALIDARG;
if (!_IsConnected()) return TF_E_DISCONNECTED;
if (!_IsValidEditCookie(ec, (dwFlags & TF_IAS_QUERYONLY) ? TF_ES_READ : TF_ES_READWRITE)) { Assert(0); return TF_E_NOLOCK; }
// we need to clear out the TF_IAS_NO_DEFAULT_COMPOSITION bit because it is not legal
// for ITextStore methods
fNoDefaultComposition = (dwFlags & TF_IAS_NO_DEFAULT_COMPOSITION); dwFlags &= ~TF_IAS_NO_DEFAULT_COMPOSITION;
if (pObj->type == IAS_OBJ::IAS_TEXT) { if (pObj->state.text.cch < 0) { pObj->state.text.cch = wcslen(pObj->state.text.pchText); }
hr = _ptsi->InsertTextAtSelection(dwFlags, pObj->state.text.pchText, pObj->state.text.cch, &paStart, &paEnd); } else { Assert(pObj->type == IAS_OBJ::IAS_DATAOBJ);
hr = _ptsi->InsertEmbeddedAtSelection(dwFlags, pObj->state.obj.pDataObject, &paStart, &paEnd); }
if (hr == S_OK) { if (!(dwFlags & TS_IAS_QUERYONLY)) { CComposition::_IsRangeCovered(this, _GetClientInEditSession(ec), paStart, paEnd, &pComposition);
_DoPostTextEditNotifications(pComposition, ec, 0, 1, paStart, paEnd, NULL);
// try to start a composition
// any active compositions?
if (!fNoDefaultComposition && pComposition == NULL) { // not covered, need to (try to) create a composition
hr = _StartComposition(ec, paStart, paEnd, NULL, &pComposition);
if (hr == S_OK && pComposition != NULL) { // we just wanted to set the composing property, so end this one immediately
pComposition->EndComposition(ec); pComposition->Release(); } } } } else { // the InsertAtSelection call failed in the app
switch (hr) { case TS_E_NOSELECTION: case TS_E_READONLY: return hr;
case E_NOTIMPL: // the app hasn't implemented InsertAtSelection, so we'll fake it using GetSelection/SetText
if (!_InsertXAtSelectionAggressive(ec, dwFlags, pObj, &paStart, &paEnd)) return E_FAIL; break;
default: return E_FAIL; } }
if (!(dwFlags & TF_IAS_NOQUERY)) { if (paStart == NULL || paEnd == NULL) { Assert(0); // text store returning bogus values
return E_FAIL; }
if ((range = new CRange) == NULL) return E_OUTOFMEMORY;
if (!range->_InitWithDefaultGravity(this, OWN_ANCHORS, paStart, paEnd)) { range->Release(); return E_FAIL; }
*ppRange = (ITfRangeAnchor *)range; }
return S_OK; }
//+---------------------------------------------------------------------------
//
// _InsertXAtSelectionAggressive
//
//----------------------------------------------------------------------------
BOOL CInputContext::_InsertXAtSelectionAggressive(TfEditCookie ec, DWORD dwFlags, IAS_OBJ *pObj, IAnchor **ppaStart, IAnchor **ppaEnd) { CRange *range; TF_SELECTION sel; ULONG pcFetched; HRESULT hr;
// this is more expensive then using ITextStore methods directly, but by using a CRange we
// get all the composition/notification code for free
if (GetSelection(ec, TF_DEFAULT_SELECTION, 1, &sel, &pcFetched) != S_OK) return FALSE;
hr = E_FAIL;
if (pcFetched != 1) goto Exit;
if (dwFlags & TS_IAS_QUERYONLY) { hr = S_OK; goto OutParams; }
if (pObj->type == IAS_OBJ::IAS_TEXT) { hr = sel.range->SetText(ec, 0, pObj->state.text.pchText, pObj->state.text.cch); } else { Assert(pObj->type == IAS_OBJ::IAS_DATAOBJ);
hr = sel.range->InsertEmbedded(ec, 0, pObj->state.obj.pDataObject); }
if (hr == S_OK) { OutParams: range = GetCRange_NA(sel.range);
*ppaStart = range->_GetStart(); (*ppaStart)->AddRef(); *ppaEnd = range->_GetEnd(); (*ppaEnd)->AddRef(); }
Exit: sel.range->Release();
return (hr == S_OK); }
//+---------------------------------------------------------------------------
//
// _DoPostTextEditNotifications
//
//----------------------------------------------------------------------------
void CInputContext::_DoPostTextEditNotifications(CComposition *pComposition, TfEditCookie ec, DWORD dwFlags, ULONG cchInserted, IAnchor *paStart, IAnchor *paEnd, CRange *range) { CProperty *property; VARIANT var;
if (range != NULL) { Assert(paStart == NULL); Assert(paEnd == NULL); paStart = range->_GetStart(); paEnd = range->_GetEnd(); }
if (cchInserted > 0) { // an insert could have crossed some anchors
_IncLastLockReleaseID(); // force a re-check for everyone!
if (range != NULL) { range->_QuickCheckCrossedAnchors(); // and check this guy right away
} }
// the app won't notify us about changes we initiate, so do that now
_OnTextChangeInternal(dwFlags, paStart, paEnd, COPY_ANCHORS);
// let properties know about the update
_PropertyTextUpdate(dwFlags, paStart, paEnd);
// Set text owner property
if (cchInserted > 0 && !IsEqualAnchor(paStart, paEnd)) { // text owner property
TfClientId tid = _GetClientInEditSession(ec); if ((tid != g_gaApp) && (tid != g_gaSystem) && (property = GetTextOwnerProperty())) { var.vt = VT_I4; var.lVal = tid;
Assert(var.lVal != TF_CLIENTID_NULL);
property->_SetDataInternal(ec, paStart, paEnd, &var); }
// composition property
if (range != NULL && _GetProperty(GUID_PROP_COMPOSING, &property) == S_OK) // perf: consider caching property ptr
{ var.vt = VT_I4; var.lVal = TRUE;
property->_SetDataInternal(ec, paStart, paEnd, &var);
property->Release(); } }
// composition update
if (pComposition != NULL && _GetOwnerCompositionSink() != NULL) { _GetOwnerCompositionSink()->OnUpdateComposition(pComposition, NULL); } }
|