Leaked source code of windows server 2003
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

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