// // compose.cpp // // Composition code. // #include "globals.h" #include "mark.h" #include "editsess.h" class CCompositionEditSession : public CEditSessionBase { public: CCompositionEditSession(ITfContext *pContext, CMarkTextService *pMark) : CEditSessionBase(pContext) { _pMark = pMark; _pMark->AddRef(); } ~CCompositionEditSession() { _pMark->Release(); } // ITfEditSession STDMETHODIMP DoEditSession(TfEditCookie ec); private: CMarkTextService *_pMark; }; class CTerminateCompositionEditSession : public CEditSessionBase { public: CTerminateCompositionEditSession(CMarkTextService *pMark, ITfContext *pContext) : CEditSessionBase(pContext) { _pMark = pMark; _pMark->AddRef(); } ~CTerminateCompositionEditSession() { _pMark->Release(); } // ITfEditSession STDMETHODIMP DoEditSession(TfEditCookie ec) { _pMark->_TerminateComposition(ec); return S_OK; } private: CMarkTextService *_pMark; }; //+--------------------------------------------------------------------------- // // _TerminateCompositionInContext // //---------------------------------------------------------------------------- void CMarkTextService::_TerminateCompositionInContext(ITfContext *pContext) { CTerminateCompositionEditSession *pEditSession; HRESULT hr; if (pEditSession = new CTerminateCompositionEditSession(this, pContext)) { pContext->RequestEditSession(_tfClientId, pEditSession, TF_ES_ASYNCDONTCARE | TF_ES_READWRITE, &hr); pEditSession->Release(); } } //+--------------------------------------------------------------------------- // // _Menu_OnComposition // // Callback for the "Start/End Composition" menu item. // If we have a composition, end it. Otherwise start a new composition over // the selection of the current focus context. //---------------------------------------------------------------------------- /* static */ void CMarkTextService::_Menu_OnComposition(CMarkTextService *_this) { ITfDocumentMgr *pFocusDoc; ITfContext *pContext; CCompositionEditSession *pCompositionEditSession; HRESULT hr; // get the focus document if (_this->_pThreadMgr->GetFocus(&pFocusDoc) != S_OK) return; // we want the topmost context, since the main doc context could be // superceded by a modal tip context if (pFocusDoc->GetTop(&pContext) != S_OK) { pContext = NULL; goto Exit; } if (pCompositionEditSession = new CCompositionEditSession(pContext, _this)) { // we need a document write lock // the CCompositionEditSession will do all the work when the // CCompositionEditSession::DoEditSession method is called by the context pContext->RequestEditSession(_this->_tfClientId, pCompositionEditSession, TF_ES_READWRITE | TF_ES_ASYNCDONTCARE, &hr); pCompositionEditSession->Release(); } Exit: SafeRelease(pContext); pFocusDoc->Release(); } //+--------------------------------------------------------------------------- // // DoEditSession // //---------------------------------------------------------------------------- STDAPI CCompositionEditSession::DoEditSession(TfEditCookie ec) { ITfInsertAtSelection *pInsertAtSelection; ITfContextComposition *pContextComposition; ITfComposition *pComposition; ITfRange *pRangeComposition; ITfRange *pRangeInsert; ITfContext *pCompositionContext; HRESULT hr; BOOL fEqualContexts; // get an interface on the context we can use to deal with compositions if (_pContext->QueryInterface(IID_ITfContextComposition, (void **)&pContextComposition) != S_OK) return E_FAIL; hr = E_FAIL; pInsertAtSelection = NULL; if (_pMark->_IsComposing()) { // we have a composition, let's terminate it // it's possible our current composition is in another context...let's find out fEqualContexts = TRUE; if (_pMark->_GetComposition()->GetRange(&pRangeComposition) == S_OK) { if (pRangeComposition->GetContext(&pCompositionContext) == S_OK) { fEqualContexts = IsEqualUnknown(pCompositionContext, _pContext); if (!fEqualContexts) { // need an edit session in the composition context _pMark->_TerminateCompositionInContext(pCompositionContext); } pCompositionContext->Release(); } pRangeComposition->Release(); } // if the composition is in pContext, we already have an edit cookie if (fEqualContexts) { _pMark->_TerminateComposition(ec); } } else { // let's start a new composition over the current selection // this is totally contrived, a real text service would have // some meaningful logic to trigger this // first, test where a keystroke would go in the document if we did an insert // we need a special interface to insert text at the selection if (_pContext->QueryInterface(IID_ITfInsertAtSelection, (void **)&pInsertAtSelection) != S_OK) { pInsertAtSelection = NULL; goto Exit; } if (pInsertAtSelection->InsertTextAtSelection(ec, TF_IAS_QUERYONLY, NULL, 0, &pRangeInsert) != S_OK) goto Exit; // start the composition if (pContextComposition->StartComposition(ec, pRangeInsert, _pMark, &pComposition) != S_OK) { pComposition = NULL; } pRangeInsert->Release(); // _pComposition may be NULL even if StartComposition return S_OK, this mean the application // rejected the composition if (pComposition != NULL) { _pMark->_SetComposition(pComposition); // underline the composition text to give the user some feedback UI _pMark->_SetCompositionDisplayAttributes(ec); } } // if we make it here, we've succeeded hr = S_OK; Exit: SafeRelease(pInsertAtSelection); pContextComposition->Release(); return hr; } //+--------------------------------------------------------------------------- // // OnCompositionTerminated // // Callback for ITfCompositionSink. The system calls this method whenever // someone other than this service ends a composition. //---------------------------------------------------------------------------- STDAPI CMarkTextService::OnCompositionTerminated(TfEditCookie ecWrite, ITfComposition *pComposition) { // we already have the composition cached, so we can ignore pComposition... // all this service wants to do is clear the display property _ClearCompositionDisplayAttributes(ecWrite); // releae our cached composition SafeReleaseClear(_pComposition); return S_OK; } //+--------------------------------------------------------------------------- // // _ClearCompositionDisplayAttributes // //---------------------------------------------------------------------------- void CMarkTextService::_ClearCompositionDisplayAttributes(TfEditCookie ec) { ITfRange *pRangeComposition; ITfContext *pContext; ITfProperty *pDisplayAttributeProperty; // we need a range and the context it lives in if (_pComposition->GetRange(&pRangeComposition) != S_OK) return; if (pRangeComposition->GetContext(&pContext) != S_OK) { pContext = NULL; goto Exit; } // get our the display attribute property if (pContext->GetProperty(GUID_PROP_ATTRIBUTE, &pDisplayAttributeProperty) != S_OK) goto Exit; // clear the value over the range pDisplayAttributeProperty->Clear(ec, pRangeComposition); pDisplayAttributeProperty->Release(); Exit: pRangeComposition->Release(); SafeRelease(pContext); } //+--------------------------------------------------------------------------- // // _SetCompositionDisplayAttributes // //---------------------------------------------------------------------------- BOOL CMarkTextService::_SetCompositionDisplayAttributes(TfEditCookie ec) { ITfRange *pRangeComposition; ITfContext *pContext; ITfProperty *pDisplayAttributeProperty; VARIANT var; HRESULT hr; // we need a range and the context it lives in if (_pComposition->GetRange(&pRangeComposition) != S_OK) return FALSE; hr = E_FAIL; if (pRangeComposition->GetContext(&pContext) != S_OK) { pContext = NULL; goto Exit; } // get our the display attribute property if (pContext->GetProperty(GUID_PROP_ATTRIBUTE, &pDisplayAttributeProperty) != S_OK) goto Exit; // set the value over the range // the application will use this guid atom to lookup the acutal rendering information var.vt = VT_I4; // we're going to set a TfGuidAtom var.lVal = _gaDisplayAttribute; // our cached guid atom for c_guidMarkDisplayAttribute hr = pDisplayAttributeProperty->SetValue(ec, pRangeComposition, &var); pDisplayAttributeProperty->Release(); Exit: pRangeComposition->Release(); SafeRelease(pContext); return (hr == S_OK); } //+--------------------------------------------------------------------------- // // _InitDisplayAttributeGuidAtom // // Because it's expensive to map our display attribute GUID to a TSF // TfGuidAtom, we do it once when Activate is called. //---------------------------------------------------------------------------- BOOL CMarkTextService::_InitDisplayAttributeGuidAtom() { ITfCategoryMgr *pCategoryMgr; HRESULT hr; if (CoCreateInstance(CLSID_TF_CategoryMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr, (void**)&pCategoryMgr) != S_OK) { return FALSE; } hr = pCategoryMgr->RegisterGUID(c_guidMarkDisplayAttribute, &_gaDisplayAttribute); pCategoryMgr->Release(); return (hr == S_OK); }