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