You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
10 KiB
344 lines
10 KiB
//
|
|
// 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);
|
|
}
|