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.
1602 lines
40 KiB
1602 lines
40 KiB
//
|
|
// range.cpp
|
|
//
|
|
|
|
#include "private.h"
|
|
#include "range.h"
|
|
#include "ic.h"
|
|
#include "immxutil.h"
|
|
#include "rprop.h"
|
|
#include "tim.h"
|
|
#include "anchoref.h"
|
|
#include "compose.h"
|
|
|
|
/* b68832f0-34b9-11d3-a745-0050040ab407 */
|
|
const IID IID_PRIV_CRANGE = { 0xb68832f0,0x34b9, 0x11d3, {0xa7, 0x45, 0x00, 0x50, 0x04, 0x0a, 0xb4, 0x07} };
|
|
|
|
DBG_ID_INSTANCE(CRange);
|
|
|
|
MEMCACHE *CRange::_s_pMemCache = NULL;
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _InitClass
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
/* static */
|
|
void CRange::_InitClass()
|
|
{
|
|
_s_pMemCache = MemCache_New(32);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _UninitClass
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
/* static */
|
|
void CRange::_UninitClass()
|
|
{
|
|
if (_s_pMemCache == NULL)
|
|
return;
|
|
|
|
MemCache_Delete(_s_pMemCache);
|
|
_s_pMemCache = NULL;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _Init
|
|
//
|
|
// NB: If fSetDefaultGravity == TRUE, make certain paStart <= paEnd, or you
|
|
// will break something!
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL CRange::_Init(CInputContext *pic, AnchorOwnership ao, IAnchor *paStart, IAnchor *paEnd, RInit rinit)
|
|
{
|
|
TsGravity gStart;
|
|
TsGravity gEnd;
|
|
|
|
// can't check the anchors because we may be cloned from a range with crossed anchors
|
|
// can't do anything about crossed anchors until we know we have a doc lock
|
|
//Assert(CompareAnchors(paStart, paEnd) <= 0);
|
|
|
|
Assert(_paStart == NULL);
|
|
Assert(_paEnd == NULL);
|
|
Assert(_fDirty == FALSE);
|
|
Assert(_nextOnChangeRangeInIcsub == NULL);
|
|
|
|
if (ao == OWN_ANCHORS)
|
|
{
|
|
_paStart = paStart;
|
|
_paEnd = paEnd;
|
|
}
|
|
else
|
|
{
|
|
Assert(ao == COPY_ANCHORS);
|
|
if (paStart->Clone(&_paStart) != S_OK || _paStart == NULL)
|
|
goto ErrorExit;
|
|
if (paEnd->Clone(&_paEnd) != S_OK || _paEnd == NULL)
|
|
goto ErrorExit;
|
|
}
|
|
|
|
_pic = pic;
|
|
|
|
switch (rinit)
|
|
{
|
|
case RINIT_DEF_GRAVITY:
|
|
Assert(CompareAnchors(paStart, paEnd) <= 0); // Issue: this is only a safe assert for acp implementations
|
|
|
|
if (_SetGravity(TF_GRAVITY_BACKWARD, TF_GRAVITY_FORWARD, FALSE) != S_OK)
|
|
goto ErrorExit;
|
|
break;
|
|
|
|
case RINIT_GRAVITY:
|
|
if (_paStart->GetGravity(&gStart) != S_OK)
|
|
goto ErrorExit;
|
|
if (_paEnd->GetGravity(&gEnd) != S_OK)
|
|
goto ErrorExit;
|
|
|
|
_InitLastLockReleaseId(gStart, gEnd);
|
|
break;
|
|
|
|
default:
|
|
// caller must init _dwLastLockReleaseID!
|
|
break;
|
|
}
|
|
|
|
_pic->AddRef();
|
|
|
|
return TRUE;
|
|
|
|
ErrorExit:
|
|
Assert(0);
|
|
if (ao == COPY_ANCHORS)
|
|
{
|
|
SafeReleaseClear(_paStart);
|
|
SafeReleaseClear(_paEnd);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// dtor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
CRange::~CRange()
|
|
{
|
|
_paStart->Release();
|
|
_paEnd->Release();
|
|
|
|
_pic->Release();
|
|
|
|
Assert(_prgChangeSinks == NULL || _prgChangeSinks->Count() == 0); // all ITfRangeChangeSink's should have been unadvised
|
|
delete _prgChangeSinks;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// IUnknown
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
CAnchorRef *par;
|
|
|
|
if (&riid == &IID_PRIV_CRANGE ||
|
|
IsEqualIID(riid, IID_PRIV_CRANGE))
|
|
{
|
|
*ppvObj = SAFECAST(this, CRange *);
|
|
return S_OK; // No AddRef for IID_PRIV_CRANGE! this is a private IID....
|
|
}
|
|
|
|
*ppvObj = NULL;
|
|
|
|
if (IsEqualIID(riid, IID_ITfRange) ||
|
|
IsEqualIID(riid, IID_IUnknown))
|
|
{
|
|
*ppvObj = SAFECAST(this, ITfRangeAnchor *);
|
|
}
|
|
else if (IsEqualIID(riid, IID_ITfRangeACP))
|
|
{
|
|
if ((par = GetCAnchorRef_NA(_paStart)) != NULL) // just a test to see if we're wrapping
|
|
{
|
|
*ppvObj = SAFECAST(this, ITfRangeACP *);
|
|
}
|
|
}
|
|
else if (IsEqualIID(riid, IID_ITfRangeAnchor))
|
|
{
|
|
if ((par = GetCAnchorRef_NA(_paStart)) == NULL) // just a test to see if we're wrapping
|
|
{
|
|
*ppvObj = SAFECAST(this, ITfRangeAnchor *);
|
|
}
|
|
}
|
|
else if (IsEqualIID(riid, IID_ITfSource))
|
|
{
|
|
*ppvObj = SAFECAST(this, ITfSource *);
|
|
}
|
|
|
|
if (*ppvObj)
|
|
{
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
STDAPI_(ULONG) CRange::AddRef()
|
|
{
|
|
return ++_cRef;
|
|
}
|
|
|
|
STDAPI_(ULONG) CRange::Release()
|
|
{
|
|
long cr;
|
|
|
|
cr = --_cRef;
|
|
Assert(cr >= 0);
|
|
|
|
if (cr == 0)
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
return cr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _IsValidEditCookie
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL CRange::_IsValidEditCookie(TfEditCookie ec, DWORD dwFlags)
|
|
{
|
|
// any time someone is about to access the doc, we also need
|
|
// to verify the last app edit didn't cross this range's anchors
|
|
_QuickCheckCrossedAnchors();
|
|
|
|
return _pic->_IsValidEditCookie(ec, dwFlags);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _CheckCrossedAnchors
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void CRange::_CheckCrossedAnchors()
|
|
{
|
|
DWORD dw;
|
|
|
|
Assert(_dwLastLockReleaseID != IGNORE_LAST_LOCKRELEASED); // use _QuickCheckCrossedAnchors first!
|
|
|
|
#ifdef DEBUG
|
|
// we shold only make is this far if this range has TF_GRAVITY_FORWARD,
|
|
// TF_GRAVITY_BACKWARD otherwise we should never be able to get crossed
|
|
// anchors.
|
|
TsGravity gStart;
|
|
TsGravity gEnd;
|
|
|
|
_paStart->GetGravity(&gStart);
|
|
_paEnd->GetGravity(&gEnd);
|
|
|
|
Assert(gStart == TS_GR_FORWARD && gEnd == TS_GR_BACKWARD);
|
|
#endif // DEBUG
|
|
|
|
dw = _pic->_GetLastLockReleaseID();
|
|
Assert(dw != IGNORE_LAST_LOCKRELEASED);
|
|
|
|
if (_dwLastLockReleaseID == dw)
|
|
return;
|
|
|
|
_dwLastLockReleaseID = dw;
|
|
|
|
if (CompareAnchors(_paStart, _paEnd) > 0)
|
|
{
|
|
// for crossed anchors, we always move the start anchor to the end pos -- ie, don't move
|
|
_paStart->ShiftTo(_paEnd);
|
|
}
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// GetText
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::GetText(TfEditCookie ec, DWORD dwFlags, WCHAR *pch, ULONG cchMax, ULONG *pcch)
|
|
{
|
|
HRESULT hr;
|
|
BOOL fMove;
|
|
|
|
Perf_IncCounter(PERF_RGETTEXT_COUNT);
|
|
|
|
if (pcch == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*pcch = 0;
|
|
|
|
if (dwFlags & ~(TF_TF_MOVESTART | TF_TF_IGNOREEND))
|
|
return E_INVALIDARG;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
fMove = (dwFlags & TF_TF_MOVESTART);
|
|
|
|
hr = _pic->_ptsi->GetText(0, _paStart, (dwFlags & TF_TF_IGNOREEND) ? NULL : _paEnd, pch, cchMax, pcch, fMove);
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
// don't let the start advance past the end
|
|
if (fMove && CompareAnchors(_paStart, _paEnd) > 0)
|
|
{
|
|
_paEnd->ShiftTo(_paStart);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// SetText
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::SetText(TfEditCookie ec, DWORD dwFlags, const WCHAR *pchText, LONG cch)
|
|
{
|
|
CComposition *pComposition;
|
|
HRESULT hr;
|
|
BOOL fNewComposition;
|
|
|
|
Perf_IncCounter(PERF_RSETTEXT_COUNT);
|
|
|
|
if (pchText == NULL && cch != 0)
|
|
return E_INVALIDARG;
|
|
|
|
if ((dwFlags & ~TF_ST_CORRECTION) != 0)
|
|
return E_INVALIDARG;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READWRITE))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
hr = _PreEditCompositionCheck(ec, &pComposition, &fNewComposition);
|
|
|
|
if (hr != S_OK)
|
|
return hr;
|
|
|
|
if (cch < 0)
|
|
{
|
|
cch = wcslen(pchText);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
for (LONG i=0; i<cch; i++)
|
|
{
|
|
Assert(pchText[i] != TF_CHAR_EMBEDDED); // illegal to insert TF_CHAR_EMBEDDED!
|
|
Assert(pchText[i] != TS_CHAR_REGION); // illegal to insert TS_CHAR_REGION!
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// set the text
|
|
//
|
|
|
|
hr = _pic->_ptsi->SetText(dwFlags, _paStart, _paEnd, pchText ? pchText : L"", cch);
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
_pic->_DoPostTextEditNotifications(pComposition, ec, dwFlags, cch, NULL, NULL, this);
|
|
}
|
|
|
|
// terminate the default composition, if there is one
|
|
if (fNewComposition)
|
|
{
|
|
Assert(pComposition != NULL);
|
|
pComposition->EndComposition(ec);
|
|
pComposition->Release(); // don't need Release if !fNewComposition
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// GetEmbedded
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::GetEmbedded(TfEditCookie ec, REFGUID rguidService, REFIID riid, IUnknown **ppunk)
|
|
{
|
|
if (ppunk == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*ppunk = NULL;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
return _pic->_ptsi->GetEmbedded(0, _paStart, rguidService, riid, ppunk);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Clone
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::InsertEmbedded(TfEditCookie ec, DWORD dwFlags, IDataObject *pDataObject)
|
|
{
|
|
CComposition *pComposition;
|
|
BOOL fNewComposition;
|
|
HRESULT hr;
|
|
|
|
if ((dwFlags & ~TF_IE_CORRECTION) != 0)
|
|
return E_INVALIDARG;
|
|
|
|
if (pDataObject == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READWRITE))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
hr = _PreEditCompositionCheck(ec, &pComposition, &fNewComposition);
|
|
|
|
if (hr != S_OK)
|
|
return hr;
|
|
|
|
hr = _pic->_ptsi->InsertEmbedded(dwFlags, _paStart, _paEnd, pDataObject);
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
_pic->_DoPostTextEditNotifications(pComposition, ec, dwFlags, 1, NULL, NULL, this);
|
|
}
|
|
|
|
// terminate the default composition, if there is one
|
|
if (fNewComposition)
|
|
{
|
|
Assert(pComposition != NULL);
|
|
pComposition->EndComposition(ec);
|
|
pComposition->Release(); // don't need Release if !fNewComposition
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// GetFormattedText
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::GetFormattedText(TfEditCookie ec, IDataObject **ppDataObject)
|
|
{
|
|
if (ppDataObject == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*ppDataObject = NULL;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
return _pic->_ptsi->GetFormattedText(_paStart, _paEnd, ppDataObject);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Clone
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::Clone(ITfRange **ppClone)
|
|
{
|
|
if (ppClone == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
return (*ppClone = (ITfRangeAnchor *)_Clone()) ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// GetContext
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::GetContext(ITfContext **ppContext)
|
|
{
|
|
if (ppContext == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*ppContext = _pic;
|
|
if (*ppContext)
|
|
{
|
|
(*ppContext)->AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// ShiftStart
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::ShiftStart(TfEditCookie ec, LONG cchReq, LONG *pcch, const TF_HALTCOND *pHalt)
|
|
{
|
|
CRange *pRangeP;
|
|
IAnchor *paLimit;
|
|
IAnchor *paShift;
|
|
HRESULT hr;
|
|
|
|
Perf_IncCounter(PERF_SHIFTSTART_COUNT);
|
|
|
|
if (pcch == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*pcch = 0;
|
|
|
|
if (pHalt != NULL && (pHalt->dwFlags & ~TF_HF_OBJECT))
|
|
return E_INVALIDARG;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
paLimit = NULL;
|
|
|
|
if (pHalt != NULL && pHalt->pHaltRange != NULL)
|
|
{
|
|
if ((pRangeP = GetCRange_NA(pHalt->pHaltRange)) == NULL)
|
|
return E_FAIL;
|
|
|
|
paLimit = (pHalt->aHaltPos == TF_ANCHOR_START) ? pRangeP->_GetStart() : pRangeP->_GetEnd();
|
|
}
|
|
|
|
if (pHalt == NULL || pHalt->dwFlags == 0)
|
|
{
|
|
// caller doesn't care about special chars, so we can do it the easy way
|
|
hr = _paStart->Shift(0, cchReq, pcch, paLimit);
|
|
}
|
|
else
|
|
{
|
|
// caller wants us to halt for special chars, need to read text
|
|
if (_paStart->Clone(&paShift) != S_OK)
|
|
return E_FAIL;
|
|
|
|
hr = _ShiftConditional(paShift, paLimit, cchReq, pcch, pHalt);
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = _paStart->ShiftTo(paShift);
|
|
}
|
|
paShift->Release();
|
|
}
|
|
|
|
if (hr != S_OK)
|
|
return E_FAIL;
|
|
|
|
// don't let the start advance past the end
|
|
if (cchReq > 0 && CompareAnchors(_paStart, _paEnd) > 0)
|
|
{
|
|
_paEnd->ShiftTo(_paStart);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// ShiftEnd
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::ShiftEnd(TfEditCookie ec, LONG cchReq, LONG *pcch, const TF_HALTCOND *pHalt)
|
|
{
|
|
CRange *pRangeP;
|
|
IAnchor *paLimit;
|
|
IAnchor *paShift;
|
|
HRESULT hr;
|
|
|
|
Perf_IncCounter(PERF_SHIFTEND_COUNT);
|
|
|
|
if (pcch == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*pcch = 0;
|
|
|
|
if (pHalt != NULL && (pHalt->dwFlags & ~TF_HF_OBJECT))
|
|
return E_INVALIDARG;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
paLimit = NULL;
|
|
|
|
if (pHalt != NULL && pHalt->pHaltRange != NULL)
|
|
{
|
|
if ((pRangeP = GetCRange_NA(pHalt->pHaltRange)) == NULL)
|
|
return E_FAIL;
|
|
|
|
paLimit = (pHalt->aHaltPos == TF_ANCHOR_START) ? pRangeP->_GetStart() : pRangeP->_GetEnd();
|
|
}
|
|
|
|
if (pHalt == NULL || pHalt->dwFlags == 0)
|
|
{
|
|
// caller doesn't care about special chars, so we can do it the easy way
|
|
hr = _paEnd->Shift(0, cchReq, pcch, paLimit);
|
|
}
|
|
else
|
|
{
|
|
// caller wants us to halt for special chars, need to read text
|
|
if (_paEnd->Clone(&paShift) != S_OK)
|
|
return E_FAIL;
|
|
|
|
hr = _ShiftConditional(paShift, paLimit, cchReq, pcch, pHalt);
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = _paEnd->ShiftTo(paShift);
|
|
}
|
|
paShift->Release();
|
|
}
|
|
|
|
if (hr != S_OK)
|
|
return E_FAIL;
|
|
|
|
// don't let the start advance past the end
|
|
if (cchReq < 0 && CompareAnchors(_paStart, _paEnd) > 0)
|
|
{
|
|
_paStart->ShiftTo(_paEnd);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _ShiftConditional
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CRange::_ShiftConditional(IAnchor *paStart, IAnchor *paLimit, LONG cchReq, LONG *pcch, const TF_HALTCOND *pHalt)
|
|
{
|
|
HRESULT hr;
|
|
ITextStoreAnchor *ptsi;
|
|
LONG cchRead;
|
|
LONG cch;
|
|
LONG i;
|
|
LONG iStop;
|
|
LONG delta;
|
|
BOOL fHaltObj;
|
|
WCHAR ach[64];
|
|
|
|
Assert(*pcch == 0);
|
|
Assert(pHalt && pHalt->dwFlags);
|
|
|
|
hr = S_OK;
|
|
ptsi = _pic->_ptsi;
|
|
fHaltObj = pHalt->dwFlags & TF_HF_OBJECT;
|
|
delta = (cchReq > 0) ? +1 : -1;
|
|
|
|
while (cchReq != 0)
|
|
{
|
|
if (cchReq > 0)
|
|
{
|
|
cch = (LONG)min(cchReq, ARRAYSIZE(ach));
|
|
}
|
|
else
|
|
{
|
|
// going backwards is tricky!
|
|
cch = max(cchReq, -(LONG)ARRAYSIZE(ach));
|
|
hr = paStart->Shift(0, cch, &cchRead, paLimit);
|
|
|
|
if (hr != S_OK)
|
|
break;
|
|
|
|
if (cchRead == 0)
|
|
break; // at top of doc or hit paLimit
|
|
|
|
cch = -cchRead; // must read text forward
|
|
}
|
|
|
|
Perf_IncCounter(PERF_SHIFTCOND_GETTEXT);
|
|
|
|
hr = ptsi->GetText(0, paStart, paLimit, ach, cch, (ULONG *)&cchRead, (cchReq > 0));
|
|
|
|
if (hr != S_OK)
|
|
break;
|
|
|
|
if (cchRead == 0)
|
|
break; // end of doc
|
|
|
|
if (fHaltObj)
|
|
{
|
|
// scan for special chars
|
|
if (cchReq > 0)
|
|
{
|
|
// scan left-to-right
|
|
i = 0;
|
|
iStop = cchRead;
|
|
}
|
|
else
|
|
{
|
|
// scan right-to-left
|
|
i = cchRead - 1;
|
|
iStop = -1;
|
|
}
|
|
|
|
for (; i != iStop; i += delta)
|
|
{
|
|
if (ach[i] == TS_CHAR_EMBEDDED)
|
|
{
|
|
if (cchReq > 0)
|
|
{
|
|
hr = paStart->Shift(0, i - cchRead, &cch, NULL);
|
|
cchReq = cchRead = i;
|
|
}
|
|
else
|
|
{
|
|
hr = paStart->Shift(0, i + 1, &cch, NULL);
|
|
cchRead -= i + 1;
|
|
cchReq = -cchRead;
|
|
}
|
|
goto ExitLoop;
|
|
}
|
|
}
|
|
}
|
|
|
|
ExitLoop:
|
|
if (cchReq < 0)
|
|
{
|
|
cchRead = -cchRead;
|
|
}
|
|
cchReq -= cchRead;
|
|
*pcch += cchRead;
|
|
}
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
*pcch = 0;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// ShiftStartToRange
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::ShiftStartToRange(TfEditCookie ec, ITfRange *pRange, TfAnchor aPos)
|
|
{
|
|
CRange *pRangeP;
|
|
HRESULT hr;
|
|
|
|
if (pRange == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
if ((pRangeP = GetCRange_NA(pRange)) == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
if (!VerifySameContext(this, pRangeP))
|
|
return E_INVALIDARG;
|
|
|
|
pRangeP->_QuickCheckCrossedAnchors();
|
|
|
|
hr = _paStart->ShiftTo((aPos == TF_ANCHOR_START) ? pRangeP->_GetStart() : pRangeP->_GetEnd());
|
|
|
|
// don't let the start advance past the end
|
|
if (CompareAnchors(_paStart, _paEnd) > 0)
|
|
{
|
|
_paEnd->ShiftTo(_paStart);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// ShiftEndToRange
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::ShiftEndToRange(TfEditCookie ec, ITfRange *pRange, TfAnchor aPos)
|
|
{
|
|
CRange *pRangeP;
|
|
HRESULT hr;
|
|
|
|
if (pRange == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
if ((pRangeP = GetCRange_NA(pRange)) == NULL)
|
|
return E_FAIL;
|
|
|
|
if (!VerifySameContext(this, pRangeP))
|
|
return E_INVALIDARG;
|
|
|
|
pRangeP->_QuickCheckCrossedAnchors();
|
|
|
|
hr = _paEnd->ShiftTo((aPos == TF_ANCHOR_START) ? pRangeP->_GetStart() : pRangeP->_GetEnd());
|
|
|
|
// don't let the end advance past the start
|
|
if (CompareAnchors(_paStart, _paEnd) > 0)
|
|
{
|
|
_paStart->ShiftTo(_paEnd);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// ShiftStartRegion
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::ShiftStartRegion(TfEditCookie ec, TfShiftDir dir, BOOL *pfNoRegion)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (pfNoRegion == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*pfNoRegion = TRUE;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
hr = _paStart->ShiftRegion(0, (TsShiftDir)dir, pfNoRegion);
|
|
|
|
if (hr == S_OK && dir == TF_SD_FORWARD && !*pfNoRegion)
|
|
{
|
|
// don't let the start advance past the end
|
|
if (CompareAnchors(_paStart, _paEnd) > 0)
|
|
{
|
|
_paEnd->ShiftTo(_paStart);
|
|
}
|
|
}
|
|
else if (hr == E_NOTIMPL)
|
|
{
|
|
// app doesn't support regions, so we can still succeed
|
|
// it's just that there's no region to shift over
|
|
*pfNoRegion = TRUE; // be paranoid, the app could be wacky
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// ShiftEndRegion
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::ShiftEndRegion(TfEditCookie ec, TfShiftDir dir, BOOL *pfNoRegion)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (pfNoRegion == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*pfNoRegion = TRUE;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
hr = _paEnd->ShiftRegion(0, (TsShiftDir)dir, pfNoRegion);
|
|
|
|
if (hr == S_OK && dir == TF_SD_BACKWARD && !*pfNoRegion)
|
|
{
|
|
// don't let the end advance past the start
|
|
if (CompareAnchors(_paStart, _paEnd) > 0)
|
|
{
|
|
_paStart->ShiftTo(_paEnd);
|
|
}
|
|
}
|
|
else if (hr == E_NOTIMPL)
|
|
{
|
|
// app doesn't support regions, so we can still succeed
|
|
// it's just that there's no region to shift over
|
|
*pfNoRegion = TRUE; // be paranoid, the app could be wacky
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _SnapToRegion
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#if 0
|
|
|
|
HRESULT CRange::_SnapToRegion(DWORD dwFlags)
|
|
{
|
|
ITfRange *range;
|
|
TF_HALTCOND hc;
|
|
LONG cch;
|
|
HRESULT hr;
|
|
|
|
if (Clone(&range) != S_OK)
|
|
return E_OUTOFMEMORY;
|
|
|
|
hc.pHaltRange = (ITfRangeAnchor *)this;
|
|
hc.dwFlags = 0;
|
|
|
|
if (dwFlags & TF_GS_SNAPREGION_START)
|
|
{
|
|
if ((hr = range->Collapse(BACKDOOR_EDIT_COOKIE, TF_ANCHOR_START)) != S_OK)
|
|
goto Exit;
|
|
|
|
hc.aHaltPos = TF_ANCHOR_END;
|
|
|
|
do
|
|
{
|
|
if ((hr = range->ShiftEnd(BACKDOOR_EDIT_COOKIE, LONG_MAX, &cch, &hc)) != S_OK)
|
|
goto Exit;
|
|
}
|
|
while (cch >= LONG_MAX); // just in case this is a _really_ huge doc
|
|
|
|
hr = ShiftEndToRange(BACKDOOR_EDIT_COOKIE, range, TF_ANCHOR_END);
|
|
}
|
|
else
|
|
{
|
|
Assert(dwFlags & TF_GS_SNAPREGION_END);
|
|
|
|
if ((hr = range->Collapse(BACKDOOR_EDIT_COOKIE, TF_ANCHOR_END)) != S_OK)
|
|
goto Exit;
|
|
|
|
hc.aHaltPos = TF_ANCHOR_START;
|
|
|
|
do
|
|
{
|
|
if ((hr = range->ShiftStart(BACKDOOR_EDIT_COOKIE, LONG_MIN, &cch, &hc)) != S_OK)
|
|
goto Exit;
|
|
}
|
|
while (cch <= LONG_MIN); // just in case this is a _really_ huge doc
|
|
|
|
hr = ShiftStartToRange(BACKDOOR_EDIT_COOKIE, range, TF_ANCHOR_START);
|
|
}
|
|
|
|
Exit:
|
|
if (hr != S_OK)
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
range->Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
#endif // 0
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// IsEmpty
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::IsEmpty(TfEditCookie ec, BOOL *pfEmpty)
|
|
{
|
|
return IsEqualStart(ec, (ITfRangeAnchor *)this, TF_ANCHOR_END, pfEmpty);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Collapse
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::Collapse(TfEditCookie ec, TfAnchor aPos)
|
|
{
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
return (aPos == TF_ANCHOR_START) ? _paEnd->ShiftTo(_paStart) : _paStart->ShiftTo(_paEnd);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// IsEqualStart
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::IsEqualStart(TfEditCookie ec, ITfRange *pWith, TfAnchor aPos, BOOL *pfEqual)
|
|
{
|
|
return _IsEqualX(ec, TF_ANCHOR_START, pWith, aPos, pfEqual);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// IsEqualEnd
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::IsEqualEnd(TfEditCookie ec, ITfRange *pWith, TfAnchor aPos, BOOL *pfEqual)
|
|
{
|
|
return _IsEqualX(ec, TF_ANCHOR_END, pWith, aPos, pfEqual);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _IsEqualX
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CRange::_IsEqualX(TfEditCookie ec, TfAnchor aPosThisRange, ITfRange *pWith, TfAnchor aPos, BOOL *pfEqual)
|
|
{
|
|
LONG lComp;
|
|
HRESULT hr;
|
|
|
|
if (pfEqual == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*pfEqual = FALSE;
|
|
|
|
// perf: we could check TS_SS_NOHIDDENTEXT for better perf
|
|
hr = _CompareX(ec, aPosThisRange, pWith, aPos, &lComp);
|
|
|
|
if (hr != S_OK)
|
|
return hr;
|
|
|
|
*pfEqual = (lComp == 0);
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CompareStart
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::CompareStart(TfEditCookie ec, ITfRange *pWith, TfAnchor aPos, LONG *plResult)
|
|
{
|
|
return _CompareX(ec, TF_ANCHOR_START, pWith, aPos, plResult);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CompareEnd
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::CompareEnd(TfEditCookie ec, ITfRange *pWith, TfAnchor aPos, LONG *plResult)
|
|
{
|
|
return _CompareX(ec, TF_ANCHOR_END, pWith, aPos, plResult);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _CompareX
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CRange::_CompareX(TfEditCookie ec, TfAnchor aPosThisRange, ITfRange *pWith, TfAnchor aPos, LONG *plResult)
|
|
{
|
|
CRange *pRangeP;
|
|
IAnchor *paThis;
|
|
IAnchor *paWith;
|
|
IAnchor *paTest;
|
|
LONG lComp;
|
|
LONG cch;
|
|
BOOL fEqual;
|
|
HRESULT hr;
|
|
|
|
if (plResult == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*plResult = 0;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
if (pWith == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
if ((pRangeP = GetCRange_NA(pWith)) == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
if (!VerifySameContext(this, pRangeP))
|
|
return E_INVALIDARG;
|
|
|
|
pRangeP->_QuickCheckCrossedAnchors();
|
|
|
|
paWith = (aPos == TF_ANCHOR_START) ? pRangeP->_GetStart() : pRangeP->_GetEnd();
|
|
paThis = (aPosThisRange == TF_ANCHOR_START) ? _paStart : _paEnd;
|
|
|
|
if (paThis->Compare(paWith, &lComp) != S_OK)
|
|
return E_FAIL;
|
|
|
|
if (lComp == 0) // exact match
|
|
{
|
|
Assert(*plResult == 0);
|
|
return S_OK;
|
|
}
|
|
|
|
// we need to account for hidden text, so we actually have to do a shift
|
|
// perf: we could check TS_SS_NOHIDDENTEXT for better perf
|
|
|
|
if (paThis->Shift(TS_SHIFT_COUNT_ONLY, (lComp < 0) ? 1 : -1, &cch, paWith) != S_OK)
|
|
return E_FAIL;
|
|
|
|
if (cch == 0)
|
|
{
|
|
// nothing but hidden text between the two anchors?
|
|
// one special case: we might have hit a region boundary
|
|
if (paThis->Clone(&paTest) != S_OK || paTest == NULL)
|
|
return E_FAIL;
|
|
|
|
hr = E_FAIL;
|
|
|
|
// if we're not at paWith after the shift, we must have hit a region
|
|
if (paTest->Shift(0, (lComp < 0) ? 1 : -1, &cch, paWith) != S_OK)
|
|
goto ReleaseTest;
|
|
|
|
Assert(cch == 0);
|
|
|
|
if (paTest->IsEqual(paWith, &fEqual) != S_OK)
|
|
goto ReleaseTest;
|
|
|
|
hr = S_OK;
|
|
|
|
ReleaseTest:
|
|
paTest->Release();
|
|
|
|
if (hr != S_OK)
|
|
return E_FAIL;
|
|
|
|
if (fEqual)
|
|
{
|
|
Assert(*plResult == 0);
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
*plResult = lComp;
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// GetGravity
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::AdjustForInsert(TfEditCookie ec, ULONG cchInsert, BOOL *pfInsertOk)
|
|
{
|
|
TfGravity gStart;
|
|
TfGravity gEnd;
|
|
IAnchor *paStartResult;
|
|
IAnchor *paEndResult;
|
|
HRESULT hr;
|
|
|
|
if (pfInsertOk == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*pfInsertOk = FALSE;
|
|
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
hr = _pic->_ptsi->QueryInsert(_paStart, _paEnd, cchInsert, &paStartResult, &paEndResult);
|
|
|
|
if (hr == E_NOTIMPL)
|
|
{
|
|
// ok, just allow the request
|
|
goto Exit;
|
|
}
|
|
else if (hr != S_OK)
|
|
{
|
|
Assert(*pfInsertOk == FALSE);
|
|
return E_FAIL;
|
|
}
|
|
else if (paStartResult == NULL || paEndResult == NULL)
|
|
{
|
|
Assert(paEndResult == NULL);
|
|
// NULL out params means no insert possible
|
|
Assert(*pfInsertOk == FALSE);
|
|
return S_OK;
|
|
}
|
|
|
|
// all set, just swap anchors and make sure gravity doesn't change
|
|
GetGravity(&gStart, &gEnd);
|
|
|
|
_paStart->Release();
|
|
_paEnd->Release();
|
|
_paStart = paStartResult;
|
|
_paEnd = paEndResult;
|
|
|
|
_SetGravity(gStart, gEnd, TRUE);
|
|
|
|
Exit:
|
|
*pfInsertOk = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// GetGravity
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::GetGravity(TfGravity *pgStart, TfGravity *pgEnd)
|
|
{
|
|
TsGravity gStart;
|
|
TsGravity gEnd;
|
|
|
|
if (pgStart == NULL || pgEnd == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
_paStart->GetGravity(&gStart);
|
|
_paEnd->GetGravity(&gEnd);
|
|
|
|
*pgStart = (gStart == TS_GR_BACKWARD) ? TF_GRAVITY_BACKWARD : TF_GRAVITY_FORWARD;
|
|
*pgEnd = (gEnd == TS_GR_BACKWARD) ? TF_GRAVITY_BACKWARD : TF_GRAVITY_FORWARD;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// SetGravity
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::SetGravity(TfEditCookie ec, TfGravity gStart, TfGravity gEnd)
|
|
{
|
|
if (!_IsValidEditCookie(ec, TF_ES_READ))
|
|
{
|
|
Assert(0);
|
|
return TF_E_NOLOCK;
|
|
}
|
|
|
|
return _SetGravity(gStart, gEnd, TRUE);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _SetGravity
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CRange::_SetGravity(TfGravity gStart, TfGravity gEnd, BOOL fCheckCrossedAnchors)
|
|
{
|
|
if (fCheckCrossedAnchors)
|
|
{
|
|
// make sure we're not crossed in case we're switching away from inward gravity
|
|
_QuickCheckCrossedAnchors();
|
|
}
|
|
|
|
if (_paStart->SetGravity((TsGravity)gStart) != S_OK)
|
|
return E_FAIL;
|
|
if (_paEnd->SetGravity((TsGravity)gEnd) != S_OK)
|
|
return E_FAIL;
|
|
|
|
_InitLastLockReleaseId((TsGravity)gStart, (TsGravity)gEnd);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// AdviseSink
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::AdviseSink(REFIID riid, IUnknown *punk, DWORD *pdwCookie)
|
|
{
|
|
const IID *rgiid = &IID_ITfRangeChangeSink;
|
|
HRESULT hr;
|
|
|
|
if (_prgChangeSinks == NULL)
|
|
{
|
|
// we delay allocate our sink container
|
|
if ((_prgChangeSinks = new CStructArray<GENERICSINK>) == NULL)
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
hr = GenericAdviseSink(riid, punk, &rgiid, _prgChangeSinks, 1, pdwCookie);
|
|
|
|
if (hr == S_OK && _prgChangeSinks->Count() == 1)
|
|
{
|
|
// add this range to the list of ranges with sinks in the icsub
|
|
_nextOnChangeRangeInIcsub = _pic->_pOnChangeRanges;
|
|
_pic->_pOnChangeRanges = this;
|
|
|
|
// start tracking anchor collapses
|
|
//_paStart->TrackCollapse(TRUE);
|
|
//_paEnd->TrackCollapse(TRUE);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// UnadviseSink
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::UnadviseSink(DWORD dwCookie)
|
|
{
|
|
CRange *pRange;
|
|
CRange **ppRange;
|
|
HRESULT hr;
|
|
|
|
if (_prgChangeSinks == NULL)
|
|
return CONNECT_E_NOCONNECTION;
|
|
|
|
hr = GenericUnadviseSink(_prgChangeSinks, 1, dwCookie);
|
|
|
|
if (hr == S_OK && _prgChangeSinks->Count() == 0)
|
|
{
|
|
// remove this range from the list of ranges in its icsub
|
|
ppRange = &_pic->_pOnChangeRanges;
|
|
while (pRange = *ppRange)
|
|
{
|
|
if (pRange == this)
|
|
{
|
|
*ppRange = pRange->_nextOnChangeRangeInIcsub;
|
|
break;
|
|
}
|
|
ppRange = &pRange->_nextOnChangeRangeInIcsub;
|
|
}
|
|
|
|
// stop tracking anchor collapses
|
|
//_paStart->TrackCollapse(FALSE);
|
|
//_paEnd->TrackCollapse(FALSE);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// GetExtent
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::GetExtent(LONG *pacpAnchor, LONG *pcch)
|
|
{
|
|
CAnchorRef *par;
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (pacpAnchor == NULL || pcch == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*pacpAnchor = 0;
|
|
*pcch = 0;
|
|
|
|
// make the validation call anyways because we do other stuff in there
|
|
_IsValidEditCookie(BACKDOOR_EDIT_COOKIE, TF_ES_READ);
|
|
|
|
if ((par = GetCAnchorRef_NA(_paStart)) != NULL)
|
|
{
|
|
// we have a wrapped ACP impl, this is easy
|
|
|
|
*pacpAnchor = par->_GetACP();
|
|
|
|
if ((par = GetCAnchorRef_NA(_paEnd)) == NULL)
|
|
goto ErrorExit;
|
|
|
|
*pcch = par->_GetACP() - *pacpAnchor;
|
|
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
Assert(0); // who's doing this?
|
|
// we fail if someone tries to do GetExtentACP on a
|
|
// non-acp text store. Users of this method should
|
|
// be aware of whether or not they are using an acp
|
|
// store.
|
|
}
|
|
|
|
return hr;
|
|
|
|
ErrorExit:
|
|
*pacpAnchor = 0;
|
|
*pcch = 0;
|
|
return E_FAIL;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// GetExtent
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::GetExtent(IAnchor **ppaStart, IAnchor **ppaEnd)
|
|
{
|
|
if (ppaStart == NULL || ppaEnd == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*ppaStart = NULL;
|
|
*ppaEnd = NULL;
|
|
|
|
// make the validation call anyways because we do other stuff in there
|
|
_IsValidEditCookie(BACKDOOR_EDIT_COOKIE, TF_ES_READ);
|
|
|
|
if (_paStart->Clone(ppaStart) != S_OK)
|
|
return E_FAIL;
|
|
|
|
if (_paEnd->Clone(ppaEnd) != S_OK)
|
|
{
|
|
SafeReleaseClear(*ppaStart);
|
|
return E_FAIL;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// SetExtent
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::SetExtent(LONG acpAnchor, LONG cch)
|
|
{
|
|
CAnchorRef *par;
|
|
IAnchor *paStart;
|
|
IAnchor *paEnd;
|
|
|
|
// make the validation call anyways because we do other stuff in there
|
|
_IsValidEditCookie(BACKDOOR_EDIT_COOKIE, TF_ES_READ);
|
|
|
|
if (acpAnchor < 0 || cch < 0)
|
|
return E_INVALIDARG;
|
|
|
|
paStart = paEnd = NULL;
|
|
|
|
if ((par = GetCAnchorRef_NA(_paStart)) != NULL)
|
|
{
|
|
// we have a wrapped ACP impl, this is easy
|
|
|
|
// need to work with Clones to handle failure gracefully
|
|
if (FAILED(_paStart->Clone(&paStart)))
|
|
goto ErrorExit;
|
|
|
|
if ((par = GetCAnchorRef_NA(paStart)) == NULL)
|
|
goto ErrorExit;
|
|
|
|
if (!par->_SetACP(acpAnchor))
|
|
goto ErrorExit;
|
|
|
|
if (FAILED(_paEnd->Clone(&paEnd)))
|
|
goto ErrorExit;
|
|
|
|
if ((par = GetCAnchorRef_NA(paEnd)) == NULL)
|
|
goto ErrorExit;
|
|
|
|
if (!par->_SetACP(acpAnchor + cch))
|
|
goto ErrorExit;
|
|
}
|
|
else
|
|
{
|
|
Assert(0); // who's doing this?
|
|
// we fail if someone tries to do SetExtentACP on a
|
|
// non-acp text store. Users of this method should
|
|
// be aware of whether or not they are using an acp
|
|
// store.
|
|
goto ErrorExit;
|
|
}
|
|
|
|
SafeRelease(_paStart);
|
|
SafeRelease(_paEnd);
|
|
_paStart = paStart;
|
|
_paEnd = paEnd;
|
|
|
|
return S_OK;
|
|
|
|
ErrorExit:
|
|
SafeRelease(paStart);
|
|
SafeRelease(paEnd);
|
|
return E_FAIL;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// SetExtent
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CRange::SetExtent(IAnchor *paStart, IAnchor *paEnd)
|
|
{
|
|
IAnchor *paStartClone;
|
|
IAnchor *paEndClone;
|
|
|
|
// make the validation call anyways because we do other stuff in there
|
|
_IsValidEditCookie(BACKDOOR_EDIT_COOKIE, TF_ES_READ);
|
|
|
|
if (paStart == NULL || paEnd == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
if (CompareAnchors(paStart, paEnd) > 0)
|
|
return E_INVALIDARG;
|
|
|
|
if (paStart->Clone(&paStartClone) != S_OK)
|
|
return E_FAIL;
|
|
|
|
if (paEnd->Clone(&paEndClone) != S_OK)
|
|
{
|
|
paStartClone->Release();
|
|
return E_FAIL;
|
|
}
|
|
|
|
SafeRelease(_paStart);
|
|
SafeRelease(_paEnd);
|
|
|
|
_paStart = paStartClone;
|
|
_paEnd = paEndClone;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// _PreEditCompositionCheck
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT CRange::_PreEditCompositionCheck(TfEditCookie ec, CComposition **ppComposition, BOOL *pfNewComposition)
|
|
{
|
|
IRC irc;
|
|
|
|
// any active compositions?
|
|
*pfNewComposition = FALSE;
|
|
irc = CComposition::_IsRangeCovered(_pic, _pic->_GetClientInEditSession(ec), _paStart, _paEnd, ppComposition);
|
|
|
|
if (irc == IRC_COVERED)
|
|
{
|
|
// this range is within an owned composition
|
|
Assert(*ppComposition != NULL);
|
|
return S_OK;
|
|
}
|
|
else if (irc == IRC_OUTSIDE)
|
|
{
|
|
// the caller owns compositions, but this range isn't wholly within them
|
|
return TF_E_RANGE_NOT_COVERED;
|
|
}
|
|
else
|
|
{
|
|
Assert(irc == IRC_NO_OWNEDCOMPOSITIONS);
|
|
}
|
|
|
|
// not covered, need to create a default composition
|
|
if (_pic->_StartComposition(ec, _paStart, _paEnd, NULL, ppComposition) != S_OK)
|
|
return E_FAIL;
|
|
|
|
if (*ppComposition != NULL)
|
|
{
|
|
*pfNewComposition = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
return TF_E_COMPOSITION_REJECTED;
|
|
}
|