// // 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); } }