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.
1697 lines
45 KiB
1697 lines
45 KiB
//
|
|
// 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);
|
|
}
|
|
}
|