|
|
//
// rprop.cpp
//
#include "private.h"
#include "rprop.h"
#include "rngsink.h"
#include "immxutil.h"
#include "varutil.h"
#include "ic.h"
#include "tim.h"
#include "enumprop.h"
#include "tfprop.h"
#include "range.h"
#include "anchoref.h"
/* ccaefd20-38a6-11d3-a745-0050040ab407 */ const IID IID_PRIV_CPROPERTY = { 0xccaefd20, 0x38a6, 0x11d3, {0xa7, 0x45, 0x00, 0x50, 0x04, 0x0a, 0xb4, 0x07} };
//
// By using this fake CLSID, the StaticProperty pretends
// to be an TFE for persistent data.
//
/* b6a4bc60-0749-11d3-8def-00105a2799b5 */ static const CLSID CLSID_IME_StaticProperty = { 0xb6a4bc60, 0x0749, 0x11d3, {0x8d, 0xef, 0x00, 0x10, 0x5a, 0x27, 0x99, 0xb5} };
DBG_ID_INSTANCE(CProperty);
inline void CheckCrossedAnchors(PROPERTYLIST *pProp) { if (CompareAnchors(pProp->_paStart, pProp->_paEnd) > 0) { // for crossed anchors, we always move the start anchor to the end pos -- ie, don't move
pProp->_paStart->ShiftTo(pProp->_paEnd); } }
//+---------------------------------------------------------------------------
//
// IsEqualPropertyValue
//
//----------------------------------------------------------------------------
BOOL IsEqualPropertyValue(ITfPropertyStore *pStore1, ITfPropertyStore *pStore2) { BOOL fEqual; VARIANT varValue1; VARIANT varValue2;
if (pStore1->GetData(&varValue1) != S_OK) return FALSE;
if (pStore2->GetData(&varValue2) != S_OK) { VariantClear(&varValue1); return FALSE; }
if (varValue1.vt != varValue2.vt) { Assert(0); // shouldn't happen for property of same type
VariantClear(&varValue1); VariantClear(&varValue2); return FALSE; }
switch (varValue1.vt) { case VT_I4: fEqual = varValue1.lVal == varValue2.lVal; break;
case VT_UNKNOWN: fEqual = IdentityCompare(varValue1.punkVal, varValue2.punkVal); varValue1.punkVal->Release(); varValue2.punkVal->Release(); break;
case VT_BSTR: fEqual = (wcscmp(varValue1.bstrVal, varValue2.bstrVal) == 0); SysFreeString(varValue1.bstrVal); SysFreeString(varValue2.bstrVal); break;
case VT_EMPTY: fEqual = TRUE; break;
default: Assert(0); // invalid type
fEqual = FALSE; VariantClear(&varValue1); VariantClear(&varValue2); break; }
return fEqual; }
//////////////////////////////////////////////////////////////////////////////
//
// CProperty
//
//////////////////////////////////////////////////////////////////////////////
//+---------------------------------------------------------------------------
//
// ctor
//
//----------------------------------------------------------------------------
CProperty::CProperty(CInputContext *pic, REFGUID guidProp, TFPROPERTYSTYLE propStyle, DWORD dwAuthority, DWORD dwPropFlags) { Dbg_MemSetThisNameIDCounter(TEXT("CProperty"), PERF_PROP_COUNTER);
_dwAuthority = dwAuthority; _propStyle = propStyle; _pic = pic; // don't need to AddRef because we are contained in the ic
// CPropertySub, otoh, must AddRef the owner ic
MyRegisterGUID(guidProp, &_guidatom);
_dwPropFlags = dwPropFlags; _dwCookie = 0;
Assert(_pss == NULL);
#ifdef DEBUG
_dbg_guid = guidProp; #endif // DEBUG
}
//+---------------------------------------------------------------------------
//
// dtor
//
//----------------------------------------------------------------------------
CProperty::~CProperty() { int nCnt = GetPropNum(); int i;
for (i = 0; i < nCnt; i++) { PROPERTYLIST *pProp = GetPropList(i); Assert(pProp); _FreePropertyList(pProp); }
_rgProp.Clear();
if (_pss != NULL) { delete _pss; }
Assert(!GetPropNum()); }
//+---------------------------------------------------------------------------
//
// _FreePropertyList
//
//----------------------------------------------------------------------------
void CProperty::_FreePropertyList(PROPERTYLIST *pProp) { SafeRelease(pProp->_pPropStore);
if (pProp->_pPropLoad) { delete pProp->_pPropLoad; }
SafeRelease(pProp->_paStart); SafeRelease(pProp->_paEnd);
cicMemFree(pProp); }
//+---------------------------------------------------------------------------
//
// GetType
//
//----------------------------------------------------------------------------
HRESULT CProperty::GetType(GUID *pguid) { return MyGetGUID(_guidatom, pguid); }
//+---------------------------------------------------------------------------
//
// _FindComplex
//
// If piOut != NULL then it is set to the index where ich was found, or the
// index of the next lower ich if ich isn't in the array.
// If there is no element in the array with a lower ich, returns offset -1.
//
// If fTextUpdate == TRUE, the expectation is that this method is being called
// from _PropertyTextUpdate and we have to worry about empty or crossed spans.
//----------------------------------------------------------------------------
PROPERTYLIST *CProperty::_FindComplex(IAnchor *pa, LONG *piOut, BOOL fEnd, BOOL fTextUpdate) { PROPERTYLIST *pProp; PROPERTYLIST *pPropMatch; int iMin; int iMax; int iMid; LONG l;
pPropMatch = NULL; iMid = -1; iMin = 0; iMax = _rgProp.Count();
while (iMin < iMax) { iMid = (iMin + iMax) / 2; pProp = _rgProp.Get(iMid); Assert(pProp != NULL);
if (fTextUpdate) { // after an edit, the anchors may be crossed
CheckCrossedAnchors(pProp); }
l = CompareAnchors(pa, fEnd ? pProp->_paEnd : pProp->_paStart);
if (l < 0) { iMax = iMid; } else if (l > 0) { iMin = iMid + 1; } else // pa == paPropStart
{ pPropMatch = pProp; break; } }
if (fTextUpdate && pPropMatch != NULL && iMid != -1) { // we have to account for empty spans during a textupdate
pPropMatch = _FindUpdateTouchup(pa, &iMid, fEnd); }
if (piOut != NULL) { if (pPropMatch == NULL && iMid >= 0) { PROPERTYLIST *pPropTmp = _rgProp.Get(iMid); // couldn't find a match, return the next lowest ich
// this assert won't work because the previous property list might have crossed anchors (which is ok)
//Assert(iMid == 0 || CompareAnchors(fEnd ? GetPropList(iMid - 1)->_paEnd : GetPropList(iMid - 1)->_paStart, pa) < 0);
if (CompareAnchors(fEnd ? pProp->_paEnd : pPropTmp->_paStart, pa) > 0) { iMid--; } } *piOut = iMid; }
return pPropMatch; }
//+---------------------------------------------------------------------------
//
// _FindUpdateTouchup
//
//----------------------------------------------------------------------------
PROPERTYLIST *CProperty::_FindUpdateTouchup(IAnchor *pa, int *piMid, BOOL fEnd) { PROPERTYLIST *pPropertyList; int iTmp;
// we may have empty spans after a text update, because of a text delete.
// in this case, return the last empty span.
// We'll do a O(n) scan instead of anything tricky, because in this case
// we'll soon touch every empty span again just so we can delete it.
// if we testing vs. the span end, we want the first empty span, otherwise we want the last one
for (iTmp = fEnd ? *piMid-1 : *piMid+1; iTmp >= 0 && iTmp < _rgProp.Count(); iTmp += fEnd ? -1 : +1) { pPropertyList = _rgProp.Get(iTmp);
if (CompareAnchors(pPropertyList->_paStart, pPropertyList->_paEnd) < 0) // use Compare instead of IsEqual to handle crossed anchors
break;
*piMid = iTmp; }
// was the next/prev span truncated? we want it if it matches the original search criteria
if (fEnd) { if (iTmp >= 0 && IsEqualAnchor(pa, pPropertyList->_paEnd)) { *piMid = iTmp; } } else { if (iTmp < _rgProp.Count() && IsEqualAnchor(pa, pPropertyList->_paStart)) { *piMid = iTmp; } }
return _rgProp.Get(*piMid); }
//+---------------------------------------------------------------------------
//
// Set
//
//----------------------------------------------------------------------------
HRESULT CProperty::Set(IAnchor *paStart, IAnchor *paEnd, ITfPropertyStore *pPropStore) { BOOL bRet;
Assert(pPropStore != NULL);
bRet = _InsertPropList(paStart, paEnd, pPropStore, NULL);
if (bRet) PropertyUpdated(paStart, paEnd);
_Dbg_AssertProp();
return bRet ? S_OK : E_FAIL; }
//+---------------------------------------------------------------------------
//
// SetLoader
//
//----------------------------------------------------------------------------
HRESULT CProperty::SetLoader(IAnchor *paStart, IAnchor *paEnd, CPropertyLoad *pPropLoad) { BOOL bRet; bRet = _InsertPropList(paStart, paEnd, NULL, pPropLoad);
if (bRet) PropertyUpdated(paStart, paEnd);
return bRet ? S_OK : E_FAIL; }
//+---------------------------------------------------------------------------
//
// ForceLoad
//
//----------------------------------------------------------------------------
HRESULT CProperty::ForceLoad() { int nCnt = GetPropNum(); int i;
for (i = 0; i < nCnt; i++) { PROPERTYLIST *pProp = GetPropList(i); if (!pProp->_pPropStore) { HRESULT hr; if (FAILED(hr = LoadData(pProp))) return hr; } }
return S_OK; }
//+---------------------------------------------------------------------------
//
// Clear
//
// Removes property spans from paStart to paEnd.
//----------------------------------------------------------------------------
void CProperty::Clear(IAnchor *paStart, IAnchor *paEnd, DWORD dwFlags, BOOL fTextUpdate) { PROPERTYLIST *pPropertyList; LONG iEnd; LONG iStart; LONG iRunSrc; LONG iRunDst; LONG iBogus; LONG lResult; BOOL fStartMatchesSpanEnd; BOOL fEndMatchesSpanStart; BOOL fSkipNextOnTextUpdate;
if (_rgProp.Count() == 0) return; // no props
fEndMatchesSpanStart = (_FindComplex(paEnd, &iEnd, FALSE /* fFindEndEdge */, fTextUpdate) != NULL);
if (iEnd < 0) return; // no props covered -- delta preceeds all spans
fStartMatchesSpanEnd = (_FindComplex(paStart, &iStart, TRUE /* fFindEndEdge */, fTextUpdate) != NULL);
if (!fStartMatchesSpanEnd) { // we can skip this span, it's end edge is to the left of paStart
iStart++; }
if (iEnd < iStart) return; // no props covered -- delta is between two spans
//
// first span is special, since it may be partially covered
//
// just one span?
if (iStart == iEnd) { _ClearOneSpan(paStart, paEnd, iStart, fStartMatchesSpanEnd, fEndMatchesSpanStart, dwFlags, fTextUpdate); return; }
// first span may be truncated
pPropertyList = _rgProp.Get(iStart);
if (!_ClearFirstLastSpan(TRUE /* fFirst */, fStartMatchesSpanEnd, paStart, paEnd, pPropertyList, dwFlags, fTextUpdate, &fSkipNextOnTextUpdate)) { // we're not going clear the first span, so skip past it
iStart++; }
//
// handle all the totally covered spans
//
iBogus = iStart-1; // a sentinel
iRunSrc = iBogus; iRunDst = iBogus;
if (!fTextUpdate) { // we don't need a loop for non-text updates
// everything will be deleted, and we don't have
// to worry about crossed anchors or change histories
// we just need to some extra checking on the last span
if (iStart < iEnd) { iRunDst = iStart; } iStart = iEnd; }
for (; iStart <= iEnd; iStart++) { pPropertyList = _rgProp.Get(iStart);
if (iStart == iEnd) { // last span is special, since it may be partially covered
if (_ClearFirstLastSpan(FALSE /* fFirst */, fEndMatchesSpanStart, paStart, paEnd, pPropertyList, dwFlags, fTextUpdate, &fSkipNextOnTextUpdate)) { goto ClearSpan; } else { goto SaveSpan; } }
// make sure we handle any crossed anchors
lResult = CompareAnchors(pPropertyList->_paStart, pPropertyList->_paEnd);
if (lResult >= 0) { if (lResult > 0) { // for crossed anchors, we always move the start anchor to the end pos -- ie, don't move
pPropertyList->_paStart->ShiftTo(pPropertyList->_paEnd); } // don't do OnTextUpdated for empty spans!
fSkipNextOnTextUpdate = TRUE; }
// give the property owner a chance to ignore text updates
if (fSkipNextOnTextUpdate || !_OnTextUpdate(dwFlags, pPropertyList, paStart, paEnd)) { ClearSpan: // this span is going to die
fSkipNextOnTextUpdate = FALSE;
if (iRunDst == iBogus) { iRunDst = iStart; } else if (iRunSrc > iRunDst) { // time to move this run
_MovePropertySpans(iRunDst, iRunSrc, iStart - iRunSrc); // and update the pointers
iRunDst += iStart - iRunSrc; iRunSrc = iBogus; } } else { // make sure we clear the history for this span
pPropertyList->_paStart->ClearChangeHistory(); pPropertyList->_paEnd->ClearChangeHistory(); SaveSpan: // this span will live
if (iRunSrc == iBogus && iRunDst != iBogus) { iRunSrc = iStart; } } }
// handle the final run
if (iRunDst > iBogus) { // if iRunSrc == iBogus, then we want to delete every span we saw
if (iRunSrc == iBogus) { _MovePropertySpans(iRunDst, iStart, _rgProp.Count()-iStart); } else { _MovePropertySpans(iRunDst, iRunSrc, _rgProp.Count()-iRunSrc); } }
_Dbg_AssertProp(); }
//+---------------------------------------------------------------------------
//
// _ClearOneSpan
//
// Handle a clear that intersects just one span.
//----------------------------------------------------------------------------
void CProperty::_ClearOneSpan(IAnchor *paStart, IAnchor *paEnd, int iIndex, BOOL fStartMatchesSpanEnd, BOOL fEndMatchesSpanStart, DWORD dwFlags, BOOL fTextUpdate) { PROPERTYLIST *pPropertyList; ITfPropertyStore *pPropertyStore; IAnchor *paTmp; LONG lResult; DWORD dwStartHistory; DWORD dwEndHistory; LONG lStartDeltaToStartSpan; LONG lEndDeltaToEndSpan; HRESULT hr;
pPropertyList = _rgProp.Get(iIndex); lResult = 0;
// empty or crossed span?
if (fTextUpdate) { if ((lResult = CompareAnchors(pPropertyList->_paStart, pPropertyList->_paEnd)) >= 0) goto ClearSpan; // we shouldn't call OnTextUpdated for empty/crossed spans
} else { // we should never see an empty span outside a text update
Assert(!IsEqualAnchor(pPropertyList->_paStart, pPropertyList->_paEnd)); }
if (fTextUpdate) { // make sure we clear the history for this span in case it isn't cleared
_ClearChangeHistory(pPropertyList, &dwStartHistory, &dwEndHistory); }
// handle edge case first, if the clear range just touches an edge of the property span
if (fStartMatchesSpanEnd || fEndMatchesSpanStart) { // if this is not a text update, then the ranges don't intersect
if (!fTextUpdate) return;
// some of the text at either end of the span might have been deleted
if (fStartMatchesSpanEnd) { if (!(dwEndHistory & TS_CH_PRECEDING_DEL)) return;
if (_OnTextUpdate(dwFlags, pPropertyList, paStart, paEnd)) return;
goto ShrinkLeft; // we can avoid the CompareAnchors calls below
} else { Assert(fEndMatchesSpanStart);
if (!(dwStartHistory & TS_CH_FOLLOWING_DEL)) return;
if (_OnTextUpdate(dwFlags, pPropertyList, paStart, paEnd)) return;
goto ShrinkRight; // we can avoid the CompareAnchors calls below
} }
if (fTextUpdate && _OnTextUpdate(dwFlags, pPropertyList, paStart, paEnd)) { // property owner is ok with the clear
return; }
lStartDeltaToStartSpan = CompareAnchors(paStart, pPropertyList->_paStart); lEndDeltaToEndSpan = CompareAnchors(paEnd, pPropertyList->_paEnd);
if (lStartDeltaToStartSpan > 0) { if (lEndDeltaToEndSpan < 0) { //
// divide, we're clearing in the middle of the span
//
if (pPropertyList->_paEnd->Clone(&paTmp) != S_OK) goto ClearSpan; // give up
hr = _Divide(pPropertyList, paStart, paEnd, &pPropertyStore);
if (hr == S_OK) { _CreateNewProp(paEnd, paTmp, pPropertyStore, NULL); pPropertyStore->Release();
PropertyUpdated(paStart, paEnd); }
paTmp->Release();
if (hr != S_OK) goto ClearSpan; } else { //
// shrink to the left, we're clearing the right edge of this span
//
ShrinkLeft: if (pPropertyList->_paEnd->Clone(&paTmp) != S_OK) goto ClearSpan;
hr = _SetNewExtent(pPropertyList, pPropertyList->_paStart, paStart, FALSE);
PropertyUpdated(paStart, paTmp);
paTmp->Release();
if (hr != S_OK) goto ClearSpan; } } else if (lEndDeltaToEndSpan < 0) { //
// shrink to the right, we're clearing the left edge of this span
//
ShrinkRight: if (pPropertyList->_paStart->Clone(&paTmp) != S_OK) goto ClearSpan;
hr = _SetNewExtent(pPropertyList, paEnd, pPropertyList->_paEnd, FALSE);
PropertyUpdated(paTmp, paEnd);
paTmp->Release();
if (hr != S_OK) goto ClearSpan; } else { // we're wiping the whole span
ClearSpan: if (lResult <= 0) { PropertyUpdated(pPropertyList->_paStart, pPropertyList->_paEnd); } else { // we found a crossed span above, report as if empty
PropertyUpdated(pPropertyList->_paEnd, pPropertyList->_paEnd); } _FreePropertyList(pPropertyList); _rgProp.Remove(iIndex, 1); } }
//+---------------------------------------------------------------------------
//
// _OnTextUpdate
//
// Make a ITfPropertyStore::OnTextUpdate callback.
// Returns FALSE if the property should be freed.
//----------------------------------------------------------------------------
BOOL CProperty::_OnTextUpdate(DWORD dwFlags, PROPERTYLIST *pPropertyList, IAnchor *paStart, IAnchor *paEnd) { CRange *pRange; BOOL fRet; BOOL fAccept;
if (pPropertyList->_pPropStore == NULL) { // need to load data to make a change notification
if (LoadData(pPropertyList) != S_OK) return FALSE; Assert(pPropertyList->_pPropStore != NULL); }
// perf: can we cache the range for the notification?
if ((pRange = new CRange) == NULL) return FALSE; // out of memory, give up
fRet = FALSE;
if (!pRange->_InitWithDefaultGravity(_pic, COPY_ANCHORS, paStart, paEnd)) goto Exit;
if (pPropertyList->_pPropStore->OnTextUpdated(dwFlags, (ITfRangeAnchor *)pRange, &fAccept) != S_OK) goto Exit; fRet = fAccept;
Exit: pRange->Release(); return fRet; }
//+---------------------------------------------------------------------------
//
// _MovePropertySpans
//
// Shift PROPERTYLISTs from iSrc to iDst, and shrink the array if we move
// anything that touches the very end.
//----------------------------------------------------------------------------
void CProperty::_MovePropertySpans(int iDst, int iSrc, int iCount) { PROPERTYLIST *pPropertyList; PROPERTYLIST **pSrc; PROPERTYLIST **pDst; LONG i; LONG iHalt; int cb; BOOL fLastRun;
Assert(iCount >= 0); Assert(iDst < iSrc); Assert(iDst >= 0); Assert(iSrc + iCount <= _rgProp.Count());
fLastRun = (iSrc + iCount == _rgProp.Count());
if (!fLastRun) { // free all the spans that are going to be clobbered
iHalt = min(iSrc, iDst + iCount); } else { // on the last call, cleanup everything that never got clobbered
iHalt = iSrc; }
for (i=iDst; i<iHalt; i++) { pPropertyList = _rgProp.Get(i);
if (pPropertyList == NULL) continue; // already freed this guy
if (CompareAnchors(pPropertyList->_paStart, pPropertyList->_paEnd) <= 0) { PropertyUpdated(pPropertyList->_paStart, pPropertyList->_paEnd); } else { // crossed anchors
PropertyUpdated(pPropertyList->_paEnd, pPropertyList->_paEnd); } *_rgProp.GetPtr(i) = NULL; // NULL before the callbacks in _FreePropertyList
_FreePropertyList(pPropertyList); }
// shift the moving spans down
pSrc = _rgProp.GetPtr(iSrc); pDst = _rgProp.GetPtr(iDst);
memmove(pDst, pSrc, iCount*sizeof(PROPERTYLIST *));
// this method is called from Clear as we shift through an array of spans
// to remove. On the last call only, we want to re-size the array.
if (fLastRun) { // free any unused memory at the end of the array
_rgProp.Remove(iDst + iCount, _rgProp.Count() - (iDst + iCount)); } else { // mark vacated spans so we don't try to free them a second time
// nb: we don't do this on the last call, which can be a big win since
// in the case of delete there is only one single call
pDst = _rgProp.GetPtr(max(iSrc, iDst + iCount)); cb = sizeof(PROPERTYLIST *)*(iSrc+iCount - max(iSrc, iDst + iCount)); memset(pDst, 0, cb); } }
//+---------------------------------------------------------------------------
//
// _ClearFirstLastSpan
//
// Returns TRUE if the span should be cleared.
//----------------------------------------------------------------------------
BOOL CProperty::_ClearFirstLastSpan(BOOL fFirst, BOOL fMatchesSpanEdge, IAnchor *paStart, IAnchor *paEnd, PROPERTYLIST *pPropertyList, DWORD dwFlags, BOOL fTextUpdate, BOOL *pfSkipNextOnTextUpdate) { DWORD dwStartHistory; DWORD dwEndHistory; BOOL fCovered; LONG lResult;
*pfSkipNextOnTextUpdate = FALSE;
if (fTextUpdate) { lResult = CompareAnchors(pPropertyList->_paStart, pPropertyList->_paEnd);
if (lResult == 0) { // empty span, nix it
goto Exit; }
// make sure we clear the history for this span in case it isn't cleared
_ClearChangeHistory(pPropertyList, &dwStartHistory, &dwEndHistory); // make sure we handle any crossed anchors
if (lResult > 0) { // for crossed anchors, we always move the start anchor to the end pos -- ie, don't move
pPropertyList->_paStart->ShiftTo(pPropertyList->_paEnd); } }
// completely covered?
if (fFirst) { fCovered = (CompareAnchors(pPropertyList->_paStart, paStart) >= 0); } else { fCovered = (CompareAnchors(pPropertyList->_paEnd, paEnd) <= 0); }
if (fCovered) { // this span is covered, so we're going to clear it unless it's a text update
// and the store is cool with it
if (!fTextUpdate) return TRUE;
if (_OnTextUpdate(dwFlags, pPropertyList, paStart, paEnd)) return FALSE;
goto Exit; // return TRUE, and make sure we don't call OnTextUpdate again
}
// start of span matches end of clear range? (or vice-versa)
if (fMatchesSpanEdge) { // if no text was deleted, then there really is no overlap
if (!fTextUpdate) return FALSE;
// otherwise, we may have just deleted text at the edge of the property span
if (fFirst) { if (!(dwEndHistory & TS_CH_PRECEDING_DEL)) return FALSE; } else { if (!(dwStartHistory & TS_CH_FOLLOWING_DEL)) return FALSE; } }
// if we made it here we're going to clear some of the property span
if (fTextUpdate) { if (_OnTextUpdate(dwFlags, pPropertyList, paStart, paEnd)) { // property owner is ok with the text edit
return FALSE; } }
if (fFirst) { if (_SetNewExtent(pPropertyList, pPropertyList->_paStart, paStart, FALSE) == S_OK) return FALSE; } else { if (_SetNewExtent(pPropertyList, paEnd, pPropertyList->_paEnd, FALSE) == S_OK) return FALSE; }
Exit: // property owner is not ok with the shink, kill this span
*pfSkipNextOnTextUpdate = TRUE;
return TRUE; }
//+---------------------------------------------------------------------------
//
// CreateNewProp
//
//----------------------------------------------------------------------------
PROPERTYLIST *CProperty::_CreateNewProp(IAnchor *paStart, IAnchor *paEnd, ITfPropertyStore *pPropStore, CPropertyLoad *pPropLoad) { PROPERTYLIST *pProp; LONG iProp;
Assert(!IsEqualAnchor(paStart, paEnd));
if (Find(paStart, &iProp, FALSE)) { Assert(0); } iProp++;
pProp = (PROPERTYLIST *)cicMemAllocClear(sizeof(PROPERTYLIST));
if (pProp == NULL) return NULL;
Dbg_MemSetNameIDCounter(pProp, TEXT("PROPERTYLIST"), (DWORD)-1, PERF_PROPERTYLIST_COUNTER);
if (!_rgProp.Insert(iProp, 1)) { cicMemFree(pProp); return NULL; }
_rgProp.Set(iProp, pProp);
pProp->_pPropStore = pPropStore; pProp->_pPropLoad = pPropLoad;
if (pPropStore) pPropStore->AddRef();
Assert(pProp->_paStart == NULL); Assert(pProp->_paEnd == NULL); Assert(pProp->_pPropStore || pProp->_pPropLoad);
_SetNewExtent(pProp, paStart, paEnd, TRUE);
pProp->_paStart->SetGravity(TS_GR_FORWARD); pProp->_paEnd->SetGravity(TS_GR_BACKWARD); // End must be LEFT, too. Because we don't want to stratch this property.
if (GetPropStyle() == TFPROPSTYLE_STATICCOMPACT || GetPropStyle() == TFPROPSTYLE_CUSTOM_COMPACT) { _DefragAfterThis(iProp); }
return pProp; }
//+---------------------------------------------------------------------------
//
// _SetNewExtent
//
// return S_FALSE, if tip wants to free the property.
//
//----------------------------------------------------------------------------
HRESULT CProperty::_SetNewExtent(PROPERTYLIST *pProp, IAnchor *paStart, IAnchor *paEnd, BOOL fNew) { HRESULT hr; BOOL fFree; CRange *pRange = NULL;
Assert(!IsEqualAnchor(paStart, paEnd));
ShiftToOrClone(&pProp->_paStart, paStart); ShiftToOrClone(&pProp->_paEnd, paEnd);
// we don't load actual data or send a resize event for new data
if (fNew) return S_OK;
Assert(pProp);
if (!pProp->_pPropStore) { //
// need to load data to make a change notification.
//
// perf: we may skip and delete Loader if this property is not
// custom property.
//
if (FAILED(LoadData(pProp))) return E_FAIL; }
hr = E_FAIL;
if ((pRange = new CRange) != NULL) { if (pRange->_InitWithDefaultGravity(_pic, COPY_ANCHORS, pProp->_paStart, pProp->_paEnd)) { hr = pProp->_pPropStore->Shrink((ITfRangeAnchor *)pRange, &fFree);
if (hr != S_OK || fFree) { SafeReleaseClear(pProp->_pPropStore); // caller will free this property when it see S_FALSE return
hr = S_FALSE; } } pRange->Release(); }
return hr; }
//+---------------------------------------------------------------------------
//
// _Divide
//
// return S_FALSE, if tip wants to free the property.
//
//----------------------------------------------------------------------------
HRESULT CProperty::_Divide(PROPERTYLIST *pProp, IAnchor *paBreakPtStart, IAnchor *paBreakPtEnd, ITfPropertyStore **ppStore) { HRESULT hr = E_FAIL; CRange *pRangeThis = NULL; CRange *pRangeNew = NULL;
Assert(CompareAnchors(pProp->_paStart, paBreakPtStart) <= 0); Assert(CompareAnchors(paBreakPtStart, paBreakPtEnd) <= 0); Assert(CompareAnchors(paBreakPtEnd, pProp->_paEnd) <= 0);
if ((pRangeThis = new CRange) == NULL) goto Exit; if (!pRangeThis->_InitWithDefaultGravity(_pic, COPY_ANCHORS, pProp->_paStart, paBreakPtStart)) goto Exit; if ((pRangeNew = new CRange) == NULL) goto Exit; if (!pRangeNew->_InitWithDefaultGravity(_pic, COPY_ANCHORS, paBreakPtEnd, pProp->_paEnd)) goto Exit;
if (!pProp->_pPropStore) { //
// we need to load data to make a change notification.
//
// perf: we may skip and delete Loader if this property is not
// custom property.
//
if (FAILED(LoadData(pProp))) goto Exit; }
hr = pProp->_pPropStore->Divide((ITfRangeAnchor *)pRangeThis, (ITfRangeAnchor *)pRangeNew, ppStore);
if ((hr == S_OK) && *ppStore) { ShiftToOrClone(&pProp->_paEnd, paBreakPtStart); } else { *ppStore = NULL; hr = S_FALSE; }
Exit: SafeRelease(pRangeThis); SafeRelease(pRangeNew);
return hr; }
//+---------------------------------------------------------------------------
//
// DestroyProp
//
//----------------------------------------------------------------------------
void CProperty::_RemoveProp(LONG iIndex, PROPERTYLIST *pProp) { #ifdef DEBUG
LONG iProp;
Assert(Find(pProp->_paStart, &iProp, FALSE) == pProp); Assert(iProp == iIndex); #endif // DEBUG
_rgProp.Remove(iIndex, 1); _FreePropertyList(pProp); }
//+---------------------------------------------------------------------------
//
// _InsertPropList
//
//----------------------------------------------------------------------------
BOOL CProperty::_InsertPropList(IAnchor *paStart, IAnchor *paEnd, ITfPropertyStore *pPropStore, CPropertyLoad *pPropLoad) { PROPERTYLIST *pProp; IAnchor *paTmpEnd = NULL; LONG nCnt = GetPropNum(); LONG nCur;
Assert(!IsEqualAnchor(paStart, paEnd));
if (!nCnt) { //
// we create the first PropList.
//
_CreateNewProp(paStart, paEnd, pPropStore, pPropLoad); goto End; } nCur = 0; Find(paStart, &nCur, FALSE); if (nCur <= 0) nCur = 0;
pProp = QuickGetPropList(nCur);
while (nCur < nCnt) { Assert(pProp); SafeReleaseClear(paTmpEnd); pProp->_paEnd->Clone(&paTmpEnd);
if (CompareAnchors(paStart, paTmpEnd) >= 0) goto Next;
if (CompareAnchors(paEnd, pProp->_paStart) <= 0) { //
// we insert new PropList just before pProp.
//
if (!_AddIntoProp(nCur - 1, paStart, paEnd, pPropStore)) _CreateNewProp(paStart, paEnd, pPropStore, pPropLoad); goto End; }
if (CompareAnchors(paStart, pProp->_paStart) > 0) { //
// Now need to split pProp to insert new Prop.
//
Assert(pProp->_pPropStore);
if (CompareAnchors(paTmpEnd, paEnd) > 0) { ITfPropertyStore *pNewPropStore = NULL;
if (S_OK != _Divide(pProp, paStart, paEnd, &pNewPropStore)) { _RemoveProp(nCur, pProp); nCnt--; goto DoAgain; } else if (pNewPropStore) { _CreateNewProp(paEnd, paTmpEnd, pNewPropStore, pPropLoad); pNewPropStore->Release(); pProp = GetPropList(nCur); nCnt++; } } else { if (S_OK != _SetNewExtent(pProp, pProp->_paStart, paStart, FALSE)) { _RemoveProp(nCur, pProp); nCnt--; goto DoAgain; } }
//
// next time, new Prop will be inserted.
//
goto Next; }
Assert(CompareAnchors(paStart, pProp->_paStart) <= 0);
if (CompareAnchors(pProp->_paStart, paEnd) < 0) { if (CompareAnchors(paTmpEnd, paEnd) <= 0) { //
// pProp is completely overlapped by new Prop.
// so we delete this pProp.
//
_RemoveProp(nCur, pProp); nCnt--; } else { //
// A part of pProp is overlapped by new Prop.
//
if (S_OK != _SetNewExtent(pProp, paEnd, paTmpEnd, FALSE)) { _RemoveProp(nCur, pProp); nCnt--; } }
goto DoAgain; }
Assert(0); Next: nCur++; DoAgain: pProp = SafeGetPropList(nCur); }
if (!_AddIntoProp(nCur - 1, paStart, paEnd, pPropStore)) _CreateNewProp(paStart, paEnd, pPropStore, pPropLoad);
End: _Dbg_AssertProp(); SafeRelease(paTmpEnd); return TRUE; }
//+---------------------------------------------------------------------------
//
// Defrag
//
// paStart, paEnd == NULL will defrag everything.
//----------------------------------------------------------------------------
BOOL CProperty::Defrag(IAnchor *paStart, IAnchor *paEnd) { PROPERTYLIST *pProp; LONG nCnt = GetPropNum(); LONG nCur; BOOL fSamePropIndex; BOOL fDone = FALSE;
if (GetPropStyle() != TFPROPSTYLE_STATICCOMPACT && GetPropStyle() != TFPROPSTYLE_CUSTOM_COMPACT) { return fDone; }
if (!nCnt) return fDone; pProp = GetFirstPropList(); nCur = 0; if (paStart != NULL) { if (Find(paStart, &nCur, FALSE)) nCur--; if (nCur <= 0) nCur = 0; }
pProp = GetPropList(nCur);
while (nCur < nCnt - 1) // Issue: shouldn't this terminate at paEnd?
{ PROPERTYLIST *pPropNext = GetPropList(nCur + 1);
if (paEnd != NULL && CompareAnchors(pProp->_paStart, paEnd) > 0) break;
fSamePropIndex = FALSE;
if (CompareAnchors(pProp->_paEnd, pPropNext->_paStart) == 0) { if (!pProp->_pPropStore) { if (FAILED(LoadData(pProp))) return FALSE; } if (!pPropNext->_pPropStore) { if (FAILED(LoadData(pPropNext))) return FALSE; }
// compare the value of each property instance
if (IsEqualPropertyValue(pProp->_pPropStore, pPropNext->_pPropStore)) { //
// pPropNext is next to pProp and has same data.
// So we merge them.
// We should never fail this.
//
_SetNewExtent(pProp, pProp->_paStart, pPropNext->_paEnd, FALSE); Assert(pProp->_pPropStore); _RemoveProp(nCur+1, pPropNext); nCnt = GetPropNum(); pProp = GetPropList(nCur);
fDone = TRUE;
//
// Do same pProp again because _pNext was changed.
//
fSamePropIndex = TRUE; } } if (!fSamePropIndex) { nCur++; } pProp = GetPropList(nCur); }
_Dbg_AssertProp(); return fDone; }
//+---------------------------------------------------------------------------
//
// _AddIntoProp
//
//----------------------------------------------------------------------------
BOOL CProperty::_AddIntoProp(int nCur, IAnchor *paStart, IAnchor *paEnd, ITfPropertyStore *pPropStore) { PROPERTYLIST *pProp; BOOL bRet = FALSE;
Assert(!IsEqualAnchor(paStart, paEnd));
if (GetPropStyle() != TFPROPSTYLE_STATICCOMPACT && GetPropStyle() != TFPROPSTYLE_CUSTOM_COMPACT) { return FALSE; }
if (!pPropStore) return FALSE;
if (nCur < 0) return FALSE;
if (!(pProp = GetPropList(nCur))) return FALSE;
if (CompareAnchors(pProp->_paStart, paStart) <= 0 && // Issue: why do we need 2 compares? isn't CompareAnchors(pProp->_paEnd, paStart) >= 0 enough?
CompareAnchors(pProp->_paEnd, paStart) >= 0) { if (CompareAnchors(paEnd, pProp->_paEnd) > 0) { if (!pProp->_pPropStore) { if (FAILED(LoadData(pProp))) return FALSE; }
if (IsEqualPropertyValue(pProp->_pPropStore, pPropStore)) { HRESULT hr; hr = _SetNewExtent(pProp, pProp->_paStart, paEnd, FALSE);
//
// Our static property store should never fail.
//
Assert(hr == S_OK); Assert(pProp->_pPropStore); bRet = TRUE; } } }
if (bRet) _DefragAfterThis(nCur);
_Dbg_AssertProp(); return bRet; }
//+---------------------------------------------------------------------------
//
// DefragThis
//
//----------------------------------------------------------------------------
void CProperty::_DefragAfterThis(int nCur) {
nCur++; while (1) { IAnchor *paTmpStart = NULL; IAnchor *paTmpEnd = NULL; PROPERTYLIST *pProp; int nCnt = GetPropNum(); BOOL bRet;
if (nCur >= nCnt) goto Exit;
if (!(pProp = GetPropList(nCur))) goto Exit;
pProp->_paStart->Clone(&paTmpStart); pProp->_paEnd->Clone(&paTmpEnd);
bRet = Defrag(paTmpStart, paTmpEnd);
SafeRelease(paTmpStart); SafeRelease(paTmpEnd);
if (!bRet) goto Exit; } Exit: return; }
//+---------------------------------------------------------------------------
//
// FindPropertyListByPos
//
//----------------------------------------------------------------------------
PROPERTYLIST *CProperty::FindPropertyListByPos(IAnchor *paPos, BOOL fEnd) { PROPERTYLIST *pPropList = NULL; BOOL fFound = FALSE; LONG nCnt;
Find(paPos, &nCnt, fEnd); if (nCnt >= 0) pPropList = GetPropList(nCnt); if (pPropList) { if (!fEnd) { if (CompareAnchors(pPropList->_paStart, paPos) <= 0 && CompareAnchors(paPos, pPropList->_paEnd) < 0) { fFound = TRUE; } } else { if (CompareAnchors(pPropList->_paStart, paPos) < 0 && CompareAnchors(paPos, pPropList->_paEnd) <= 0) { fFound = TRUE; } } }
return fFound ? pPropList : NULL; }
//+---------------------------------------------------------------------------
//
// LoadData
//
//----------------------------------------------------------------------------
HRESULT CProperty::LoadData(PROPERTYLIST *pPropList) { HRESULT hr = E_FAIL; ITfPropertyStore *pStore; TF_PERSISTENT_PROPERTY_HEADER_ANCHOR *ph; IStream *pStream = NULL; CRange *pRange = NULL; Assert(!pPropList->_pPropStore); Assert(pPropList->_pPropLoad);
//
// Update ichAnchor and cch of TF_PERSISTENT_PROPERTY_HEADER_ACP.
// As text may be updated, the original value is
// obsolete.
//
ph = &pPropList->_pPropLoad->_hdr;
ShiftToOrClone(&ph->paStart, pPropList->_paStart); ShiftToOrClone(&ph->paEnd, pPropList->_paEnd);
if (FAILED(pPropList->_pPropLoad->_pLoader->LoadProperty(ph, &pStream))) goto Exit;
if ((pRange = new CRange) == NULL) { hr = E_OUTOFMEMORY; goto Exit; } if (!pRange->_InitWithDefaultGravity(_pic, COPY_ANCHORS, ph->paStart, ph->paEnd)) { hr = E_FAIL; goto Exit; }
if (FAILED(_GetPropStoreFromStream(ph, pStream, pRange, &pStore))) goto Exit;
pPropList->_pPropStore = pStore; delete pPropList->_pPropLoad; pPropList->_pPropLoad = NULL;
hr = S_OK;
Exit: SafeRelease(pStream); SafeRelease(pRange); return hr; }
//+---------------------------------------------------------------------------
//
// _Dbg_AssertProp
//
//----------------------------------------------------------------------------
#ifdef DEBUG
void CProperty::_Dbg_AssertProp() { LONG nCnt = GetPropNum(); LONG n; IAnchor *paEndLast = NULL;
for (n = 0; n < nCnt; n++) { PROPERTYLIST *pProp = GetPropList(n);
Assert(paEndLast == NULL || CompareAnchors(paEndLast, pProp->_paStart) <= 0); Assert(CompareAnchors(pProp->_paStart, pProp->_paEnd) < 0); Assert(pProp->_pPropStore || pProp->_pPropLoad);
paEndLast = pProp->_paEnd; }
} #endif
//+---------------------------------------------------------------------------
//
// PropertyUpdated
//
//----------------------------------------------------------------------------
void CProperty::PropertyUpdated(IAnchor *paStart, IAnchor *paEnd) { CSpanSet *pss; CProperty *pDisplayAttrProperty;
if (pss = _CreateSpanSet()) { pss->Add(0, paStart, paEnd, COPY_ANCHORS); }
if (_dwPropFlags & PROPF_MARKUP_COLLECTION) { // we also need to update the display attribute property
if (_pic->_GetProperty(GUID_PROP_ATTRIBUTE, &pDisplayAttrProperty) == S_OK) { Assert(!(pDisplayAttrProperty->_dwPropFlags & PROPF_MARKUP_COLLECTION)); // don't allow infinite recursion!
pDisplayAttrProperty->PropertyUpdated(paStart, paEnd); pDisplayAttrProperty->Release(); } } }
//+---------------------------------------------------------------------------
//
// GetPropStoreFromStream
//
//----------------------------------------------------------------------------
HRESULT CProperty::_GetPropStoreFromStream(const TF_PERSISTENT_PROPERTY_HEADER_ANCHOR *pHdr, IStream *pStream, CRange *pRange, ITfPropertyStore **ppStore) { ITfTextInputProcessor *pIME = NULL; CTip *ptip = NULL;
ITfCreatePropertyStore *pCreateStore = NULL; ITfPropertyStore *pPropStore = NULL; CRange *pRangeTmp = NULL; GUID guidProp; CThreadInputMgr *ptim = CThreadInputMgr::_GetThis(); HRESULT hr = E_FAIL; LARGE_INTEGER li; TfGuidAtom guidatom;
Assert(!IsEqualAnchor(pHdr->paStart, pHdr->paEnd));
GetCurrentPos(pStream, &li);
if (!ptim) goto Exit;
if (FAILED(GetType(&guidProp))) goto Exit;
if (!IsEqualGUID(guidProp, pHdr->guidType)) goto Exit;
//
// Try QI.
//
if (FAILED(MyRegisterGUID(pHdr->clsidTIP, &guidatom))) goto Exit;
if (ptim->_GetCTipfromGUIDATOM(guidatom, &ptip)) pIME = ptip->_pTip;
if (pIME && ptip->_fActivated) { if (FAILED(hr = pIME->QueryInterface(IID_ITfCreatePropertyStore, (void **)&pCreateStore))) goto Exit;
if ((pRangeTmp = new CRange) == NULL) { hr = E_OUTOFMEMORY; goto Exit; } if (!pRangeTmp->_InitWithDefaultGravity(_pic, COPY_ANCHORS, pHdr->paStart, pHdr->paEnd)) { hr = E_FAIL; goto Exit; } if (FAILED(hr = pCreateStore->CreatePropertyStore(guidProp, (ITfRangeAnchor *)pRangeTmp, pHdr->cb, pStream, &pPropStore))) goto Exit; } else { if (IsEqualCLSID(pHdr->clsidTIP, CLSID_IME_StaticProperty)) { //
// Unserialize Static properties.
//
CGeneralPropStore *pStore;
// GUID_PROP_READING is TFPROPSTYLE_CUSTOM ==> uses a general prop store
if (_propStyle == TFPROPSTYLE_CUSTOM) { // general prop stores are thrown away if their text is edited
pStore = new CGeneralPropStore; } else { // static prop stores are per char, and simply clone themselves in response to any edit
pStore = new CStaticPropStore; } if (!pStore) goto Exit;
if (!pStore->_Init(GetPropGuidAtom(), pHdr->cb, (TfPropertyType)pHdr->dwPrivate, pStream, _dwPropFlags)) { goto Exit; }
pPropStore = pStore; hr = S_OK; } else { //
// There is no TFE installed in this system. So we use
// PropStoreProxy to hold the data.
// Temporarily we use ITfIME_APP. But original TFE that owns this
// data is kept in CPropStoreProxy.
//
CPropStoreProxy *pStoreProxy = new CPropStoreProxy; if (!pStoreProxy) goto Exit;
if (!pStoreProxy->_Init(&pHdr->clsidTIP, GetPropGuidAtom(), pHdr->cb, pStream, _dwPropFlags)) { goto Exit; }
pPropStore = pStoreProxy; hr = S_OK; } }
Exit:
// make sure the stream seek ptr is in a consistent state -- don't count
// on any tip to do it right!
if (SUCCEEDED(hr)) { li.QuadPart += pHdr->cb; } pStream->Seek(li, STREAM_SEEK_SET, NULL);
*ppStore = pPropStore; SafeRelease(pRangeTmp); SafeRelease(pCreateStore);
return hr; }
//+---------------------------------------------------------------------------
//
// GetContext
//
//----------------------------------------------------------------------------
STDAPI CProperty::GetContext(ITfContext **ppContext) { if (ppContext == NULL) return E_INVALIDARG;
*ppContext = _pic; if (*ppContext) { (*ppContext)->AddRef(); return S_OK; }
return E_FAIL; }
//+---------------------------------------------------------------------------
//
// Clear
//
//----------------------------------------------------------------------------
STDAPI CProperty::Clear(TfEditCookie ec, ITfRange *pRange) { CRange *pCRange = NULL; IAnchor *paStart = NULL; IAnchor *paEnd = NULL; HRESULT hr;
if (!_IsValidEditCookie(ec, TF_ES_READ_PROPERTY_WRITE)) { Assert(0); return TF_E_NOLOCK; }
paStart = NULL; paEnd = NULL;
if (pRange != NULL) { pCRange = GetCRange_NA(pRange); if (!pCRange) return E_INVALIDARG;
if (!VerifySameContext(_pic, pCRange)) return E_INVALIDARG;
pCRange->_QuickCheckCrossedAnchors();
paStart = pCRange->_GetStart(); paEnd = pCRange->_GetEnd(); }
if ((hr = _CheckValidation(ec, pCRange)) != S_OK) return hr;
return _ClearInternal(ec, paStart, paEnd); }
//+---------------------------------------------------------------------------
//
// _CheckValidation
//
//----------------------------------------------------------------------------
HRESULT CProperty::_CheckValidation(TfEditCookie ec, CRange *range) { CProperty *prop; BOOL fExactEndMatch; LONG iStartEdge; LONG iEndEdge; LONG iSpan; PROPERTYLIST *pPropList; TfClientId tid; IAnchor *paStart;
//
// There is no validation. Return TRUE;
//
if (_dwAuthority == PROPA_NONE) return S_OK;
if (_dwAuthority & PROPA_READONLY) return TF_E_READONLY;
tid = _pic->_GetClientInEditSession(ec);
if (range == NULL) return (tid == _pic->_GetTIPOwner()) ? S_OK : TF_E_NOTOWNEDRANGE;
if (!(_dwAuthority & PROPA_FOCUSRANGE)) { Assert(_dwAuthority & PROPA_TEXTOWNER); return _CheckOwner(tid, range->_GetStart(), range->_GetEnd()); }
//
// If the validation is PROPA_FOCUSTEXTOWNER, we check the focus range
// first. If the range is not focus range, we allow tip to
// update the property.
//
if ((prop = _pic->_FindProperty(GUID_PROP_COMPOSING)) == NULL) return S_OK; // no focus spans, so must be valid
// for each focus span covered by the range, we need to make sure
// this tip is the owner
prop->Find(range->_GetStart(), &iStartEdge, FALSE); fExactEndMatch = (prop->Find(range->_GetEnd(), &iEndEdge, TRUE) != NULL);
for (iSpan = max(iStartEdge, 0); iSpan <= iEndEdge; iSpan++) { pPropList = prop->GetPropList(iSpan);
if (iSpan == iStartEdge) { // this span may not be covered, need to check
// only relivent case: are we entirely to the right of the span?
if (CompareAnchors(range->_GetStart(), pPropList->_paEnd) >= 0) continue;
paStart = range->_GetStart(); } else { paStart = pPropList->_paStart; }
if (_CheckOwner(tid, paStart, pPropList->_paEnd) == TF_E_NOTOWNEDRANGE) return TF_E_NOTOWNEDRANGE; } // might also need to check the next span, since we rounded down
if (!fExactEndMatch && prop->GetPropNum() > iEndEdge+1) { pPropList = prop->GetPropList(iEndEdge+1);
IAnchor *paMaxStart;
if (CompareAnchors(range->_GetStart(), pPropList->_paStart) >= 0) paMaxStart = range->_GetStart(); else paMaxStart = pPropList->_paStart;
if (CompareAnchors(range->_GetEnd(), pPropList->_paStart) > 0) { return _CheckOwner(tid, paMaxStart, range->_GetEnd()); } }
return S_OK; }
//+---------------------------------------------------------------------------
//
// _CheckOwner
//
//----------------------------------------------------------------------------
HRESULT CProperty::_CheckOwner(TfClientId tid, IAnchor *paStart, IAnchor *paEnd) { CProperty *prop; BOOL fExactEndMatch; LONG iStartEdge; LONG iEndEdge; LONG iSpan; PROPERTYLIST *pPropList; VARIANT var;
if ((prop = _pic->GetTextOwnerProperty()) == NULL) return S_OK; // no owned spans, so must be valid
// for each owner span covered by the range, we need to make sure
// this tip is the owner
prop->Find(paStart, &iStartEdge, FALSE); fExactEndMatch = (prop->Find(paEnd, &iEndEdge, TRUE) != NULL);
for (iSpan = max(iStartEdge, 0); iSpan <= iEndEdge; iSpan++) { pPropList = prop->QuickGetAndLoadPropList(iSpan);
if (pPropList == NULL) { // this probably means we couldn't unserialize the data
// just skip it
continue; }
if (iSpan == iStartEdge) { // this span may not be covered, need to check
// only relivent case: are we entirely to the right of the span?
if (CompareAnchors(paStart, pPropList->_paEnd) >= 0) continue; }
if (pPropList->_pPropStore->GetData(&var) == S_OK) { Assert(var.vt == VT_I4); // this is the text owner property!
if ((TfClientId)var.lVal != tid) { return TF_E_NOTOWNEDRANGE; } } } // might also need to check the next span, since we rounded down
if (!fExactEndMatch && prop->GetPropNum() > iEndEdge+1) { pPropList = prop->QuickGetAndLoadPropList(iEndEdge+1);
if (pPropList == NULL) { // this probably means we couldn't unserialize the data
goto Exit; }
if (CompareAnchors(paEnd, pPropList->_paStart) > 0) { if (pPropList->_pPropStore->GetData(&var) == S_OK) { Assert(var.vt == VT_I4); // this is the text owner property!
if ((TfClientId)var.lVal != tid) { return TF_E_NOTOWNEDRANGE; } } } }
Exit: return S_OK; }
//+---------------------------------------------------------------------------
//
// CheckTextOwner
//
//----------------------------------------------------------------------------
BOOL CProperty::_IsValidEditCookie(TfEditCookie ec, DWORD dwFlags) { return _pic->_IsValidEditCookie(ec, dwFlags); }
//+---------------------------------------------------------------------------
//
// SetValue
//
//----------------------------------------------------------------------------
STDAPI CProperty::SetValue(TfEditCookie ec, ITfRange *pRange, const VARIANT *pvarValue) { CRange *pCRange; HRESULT hr;
if (pRange == NULL) return E_INVALIDARG;
if (pvarValue == NULL) return E_INVALIDARG;
if (!IsValidCiceroVarType(pvarValue->vt)) return E_INVALIDARG;
if (!_IsValidEditCookie(ec, TF_ES_READ_PROPERTY_WRITE)) { Assert(0); return TF_E_NOLOCK; }
pCRange = GetCRange_NA(pRange); if (!pCRange) return E_INVALIDARG;
if (!VerifySameContext(_pic, pCRange)) return E_INVALIDARG;
pCRange->_QuickCheckCrossedAnchors();
if ((hr = _CheckValidation(ec, pCRange)) != S_OK) return hr;
if (IsEqualAnchor(pCRange->_GetStart(), pCRange->_GetEnd())) return E_INVALIDARG;
return _SetDataInternal(ec, pCRange->_GetStart(), pCRange->_GetEnd(), pvarValue); }
//+---------------------------------------------------------------------------
//
// GetValue
//
//----------------------------------------------------------------------------
STDAPI CProperty::GetValue(TfEditCookie ec, ITfRange *pRange, VARIANT *pvarValue) { CRange *pCRange;
if (pvarValue == NULL) return E_INVALIDARG;
QuickVariantInit(pvarValue);
if (pRange == NULL) return E_INVALIDARG;
if ((pCRange = GetCRange_NA(pRange)) == NULL) return E_INVALIDARG;
if (!VerifySameContext(_pic, pCRange)) return E_INVALIDARG;
if (!_IsValidEditCookie(ec, TF_ES_READ)) { Assert(0); return TF_E_NOLOCK; }
pCRange->_QuickCheckCrossedAnchors();
return _GetDataInternal(pCRange->_GetStart(), pCRange->_GetEnd(), pvarValue); }
//+---------------------------------------------------------------------------
//
// SetValueStore
//
//----------------------------------------------------------------------------
STDAPI CProperty::SetValueStore(TfEditCookie ec, ITfRange *pRange, ITfPropertyStore *pPropStore) { CRange *pCRange;
if (pRange == NULL || pPropStore == NULL) return E_INVALIDARG;
if (!_IsValidEditCookie(ec, TF_ES_READ_PROPERTY_WRITE)) { Assert(0); return TF_E_NOLOCK; }
pCRange = GetCRange_NA(pRange); if (!pCRange) return E_INVALIDARG;
if (!VerifySameContext(_pic, pCRange)) return E_INVALIDARG;
pCRange->_QuickCheckCrossedAnchors();
return _SetStoreInternal(ec, pCRange, pPropStore, FALSE); }
//+---------------------------------------------------------------------------
//
// FindRange
//
//----------------------------------------------------------------------------
STDAPI CProperty::FindRange(TfEditCookie ec, ITfRange *pRange, ITfRange **ppv, TfAnchor aPos) { CRange *pCRange; CRange *range; HRESULT hr;
if (!ppv) return E_INVALIDARG;
*ppv = NULL;
if (pRange == NULL) return E_INVALIDARG;
if ((range = GetCRange_NA(pRange)) == NULL) return E_INVALIDARG;
if (!VerifySameContext(_pic, range)) return E_INVALIDARG;
if (!_IsValidEditCookie(ec, TF_ES_READ)) { Assert(0); return TF_E_NOLOCK; }
range->_QuickCheckCrossedAnchors();
if (SUCCEEDED(hr = _InternalFindRange(range, &pCRange, aPos, FALSE))) { *ppv = (ITfRangeAnchor *)pCRange; } return hr; }
//+---------------------------------------------------------------------------
//
// FindRange
//
//----------------------------------------------------------------------------
HRESULT CProperty::_InternalFindRange(CRange *pRange, CRange **ppv, TfAnchor aPos, BOOL fEnd) { PROPERTYLIST *pPropList; CRange *pCRange;
//
// Issue: need to defrag for STATICCOMPACT property.
//
if (pRange) { pPropList = FindPropertyListByPos((aPos == TF_ANCHOR_START) ? pRange->_GetStart() : pRange->_GetEnd(), fEnd); } else { // if pRange is NULL, we returns the first or last property.
if (aPos == TF_ANCHOR_START) pPropList = GetFirstPropList(); else pPropList = GetLastPropList(); }
*ppv = NULL;
if (!pPropList) return S_FALSE;
if ((pCRange = new CRange) == NULL) return E_OUTOFMEMORY;
if (!pCRange->_InitWithDefaultGravity(_pic, COPY_ANCHORS, pPropList->_paStart, pPropList->_paEnd)) { pCRange->Release(); return E_FAIL; }
*ppv = pCRange; return S_OK; }
//+---------------------------------------------------------------------------
//
// EnumRanges
//
//----------------------------------------------------------------------------
STDAPI CProperty::EnumRanges(TfEditCookie ec, IEnumTfRanges **ppv, ITfRange *pTargetRange) { CRange *range; CEnumPropertyRanges *pEnum;
if (ppv == NULL) return E_INVALIDARG;
*ppv = NULL;
if (!_IsValidEditCookie(ec, TF_ES_READ)) { Assert(0); return TF_E_NOLOCK; }
range = NULL;
if (pTargetRange != NULL) { if ((range = GetCRange_NA(pTargetRange)) == NULL) return E_INVALIDARG;
if (!VerifySameContext(_pic, range)) return E_INVALIDARG;
range->_QuickCheckCrossedAnchors(); }
if ((pEnum = new CEnumPropertyRanges) == NULL) return E_OUTOFMEMORY;
if (!pEnum->_Init(_pic, range ? range->_GetStart() : NULL, range ? range->_GetEnd() : NULL, this)) { pEnum->Release(); return E_FAIL; }
*ppv = pEnum; return S_OK; }
//+---------------------------------------------------------------------------
//
// _Serialize
//
//----------------------------------------------------------------------------
HRESULT CProperty::_Serialize(CRange *pRange, TF_PERSISTENT_PROPERTY_HEADER_ANCHOR *pHdr, IStream *pStream) { HRESULT hr = E_FAIL; CLSID clsidTIP; LARGE_INTEGER li; GUID guidProp; PROPERTYLIST *pPropList;
memset(pHdr, 0, sizeof(*pHdr));
if (_dwAuthority & PROPA_WONT_SERIALZE) return S_FALSE;
// nb: this call ignores any property spans following the leftmost span covered by pRange
// callers are expected to call for each span (which is goofy, but that's the way it is)
pPropList = _FindPropListAndDivide(pRange->_GetStart(), pRange->_GetEnd());
if (pPropList == NULL) { //
// There is no actual property data.
//
hr = S_FALSE; goto Exit; }
//
// perf: we have to tell the application that the data is not
// unserialized yet. maybe we don't have to load it.
//
if (!pPropList->_pPropStore) { if (FAILED(THR(LoadData(pPropList)))) return E_FAIL; }
Assert(pPropList->_pPropStore);
if (FAILED(GetType(&guidProp))) goto Exit;
//
// If the request range does not match the PROPERTYLIST,
// we can not serialize STATIC and CUSTOM correctly.
//
// STATICCOMPACT property does not care about boundary, so
// let it serialize.
//
if (CompareAnchors(pRange->_GetStart(), pPropList->_paStart) != 0 || CompareAnchors(pRange->_GetEnd(), pPropList->_paEnd) != 0) { if (_propStyle != TFPROPSTYLE_STATICCOMPACT && _propStyle != TFPROPSTYLE_CUSTOM_COMPACT) { hr = S_FALSE; goto Exit; }
pRange->_GetStart()->Clone(&pHdr->paStart); pRange->_GetEnd()->Clone(&pHdr->paEnd); } else { pPropList->_paStart->Clone(&pHdr->paStart); pPropList->_paEnd->Clone(&pHdr->paEnd); }
hr = pPropList->_pPropStore->GetPropertyRangeCreator(&clsidTIP);
if (FAILED(hr)) { hr = E_FAIL; goto Exit; }
if (hr == S_OK) { if (IsEqualGUID(clsidTIP, GUID_NULL)) // NULL owner means "I don't want to be serialized"
{ hr = S_FALSE; goto Exit; }
//
// Check if clsid has ITfCreatePropertyStore interface.
//
CThreadInputMgr *ptim = CThreadInputMgr::_GetThis(); ITfTextInputProcessor *pIME; ITfCreatePropertyStore *pCreateStore; TfGuidAtom guidatom;
hr = E_FAIL;
if (FAILED(MyRegisterGUID(clsidTIP, &guidatom))) goto Exit;
if (!ptim->_GetITfIMEfromGUIDATOM(guidatom, &pIME)) goto Exit;
if (FAILED(pIME->QueryInterface(IID_ITfCreatePropertyStore, (void **)&pCreateStore))) { hr = S_FALSE; goto Exit; }
BOOL fSerializable = FALSE;
Assert(pRange != NULL);
if (FAILED(pCreateStore->IsStoreSerializable(guidProp, (ITfRangeAnchor *)pRange, pPropList->_pPropStore, &fSerializable))) { fSerializable = FALSE; }
pCreateStore->Release();
if (!fSerializable) { hr = S_FALSE; goto Exit; }
pHdr->clsidTIP = clsidTIP; } else if (hr == TF_S_PROPSTOREPROXY) { //
// the data is held by our PropertyStoreProxy.
// we don't have to check this.
//
pHdr->clsidTIP = clsidTIP; } else if (hr == TF_S_GENERALPROPSTORE) { //
// the data is held by our GeneralPropertyStore.
// we don't have to check this.
//
pHdr->clsidTIP = CLSID_IME_StaticProperty; } else { Assert(0); hr = E_FAIL; goto Exit; }
pHdr->guidType = guidProp;
if (FAILED(hr = THR(pPropList->_pPropStore->GetDataType(&pHdr->dwPrivate)))) { goto Exit; }
GetCurrentPos(pStream, &li);
hr = THR(pPropList->_pPropStore->Serialize(pStream, &pHdr->cb));
// make sure the stream seek ptr is in a consistent state -- don't count
// on any tip to do it right!
if (hr == S_OK) { li.QuadPart += pHdr->cb; } pStream->Seek(li, STREAM_SEEK_SET, NULL);
Exit: if (hr != S_OK) { SafeRelease(pHdr->paStart); SafeRelease(pHdr->paEnd); memset(pHdr, 0, sizeof(*pHdr)); }
_Dbg_AssertProp();
return hr; }
//+---------------------------------------------------------------------------
//
// Unserialize
//
//----------------------------------------------------------------------------
HRESULT CProperty::_Unserialize(const TF_PERSISTENT_PROPERTY_HEADER_ANCHOR *pHdr, IStream *pStream, ITfPersistentPropertyLoaderAnchor *pLoader) { HRESULT hr = E_FAIL; CRange *pRange;
if (pStream) { if (pRange = new CRange) { if (pRange->_InitWithDefaultGravity(_pic, COPY_ANCHORS, pHdr->paStart, pHdr->paEnd)) { ITfPropertyStore *pPropStore; if (SUCCEEDED(hr = _GetPropStoreFromStream(pHdr, pStream, pRange, &pPropStore))) { hr = _SetStoreInternal(BACKDOOR_EDIT_COOKIE, pRange, pPropStore, TRUE); pPropStore->Release(); } } pRange->Release(); } } else if (pLoader) { if (pRange = new CRange) { if (pRange->_InitWithDefaultGravity(_pic, COPY_ANCHORS, pHdr->paStart, pHdr->paEnd)) { CPropertyLoad *pPropLoad = new CPropertyLoad;
if (pPropLoad != NULL) { hr = E_FAIL; if (pPropLoad->_Init(pHdr, pLoader)) { hr = _SetPropertyLoaderInternal(BACKDOOR_EDIT_COOKIE, pRange, pPropLoad); }
if (FAILED(hr)) { delete pPropLoad; } } } pRange->Release(); } }
_Dbg_AssertProp();
return hr; } //+---------------------------------------------------------------------------
//
// _ClearChangeHistory
//
//----------------------------------------------------------------------------
void CProperty::_ClearChangeHistory(PROPERTYLIST *prop, DWORD *pdwStartHistory, DWORD *pdwEndHistory) { if (prop->_paStart->GetChangeHistory(pdwStartHistory) != S_OK) { *pdwStartHistory = 0; } if (prop->_paEnd->GetChangeHistory(pdwEndHistory) != S_OK) { *pdwEndHistory = 0; }
// need to clear the history so we don't deal with the after-effects
// of a SetText more than once
if (*pdwStartHistory != 0) { prop->_paStart->ClearChangeHistory(); } if (*pdwEndHistory != 0) { prop->_paEnd->ClearChangeHistory(); } }
//+---------------------------------------------------------------------------
//
// _ClearChangeHistory
//
//----------------------------------------------------------------------------
#ifdef DEBUG
void CProperty::_Dbg_AssertNoChangeHistory() { int i; PROPERTYLIST *prop; DWORD dwHistory;
// all the history bits should have been cleared immediately following the text change notification
for (i=0; i<_rgProp.Count(); i++) { prop = _rgProp.Get(i);
prop->_paStart->GetChangeHistory(&dwHistory); Assert(dwHistory == 0); prop->_paEnd->GetChangeHistory(&dwHistory); Assert(dwHistory == 0); } } #endif // DEBUG
//+---------------------------------------------------------------------------
//
// FindNextValue
//
//----------------------------------------------------------------------------
STDAPI CProperty::FindNextValue(TfEditCookie ec, ITfRange *pRangeQueryIn, TfAnchor tfAnchorQuery, DWORD dwFlags, BOOL *pfContained, ITfRange **ppRangeNextValue) { CRange *pRangeQuery; CRange *pRangeNextValue; IAnchor *paQuery; PROPERTYLIST *pPropertyList; LONG iIndex; BOOL fSearchForward; BOOL fContained; BOOL fExactMatch;
if (pfContained != NULL) { *pfContained = FALSE; } if (ppRangeNextValue != NULL) { *ppRangeNextValue = NULL; } if (pfContained == NULL || ppRangeNextValue == NULL) return E_INVALIDARG;
if (pRangeQueryIn == NULL) return E_INVALIDARG;
if (dwFlags & ~(TF_FNV_BACKWARD | TF_FNV_NO_CONTAINED)) return E_INVALIDARG;
if (!_IsValidEditCookie(ec, TF_ES_READ)) return TF_E_NOLOCK;
if ((pRangeQuery = GetCRange_NA(pRangeQueryIn)) == NULL) return E_INVALIDARG;
if (!VerifySameContext(_pic, pRangeQuery)) return E_INVALIDARG;
fSearchForward = !(dwFlags & TF_FNV_BACKWARD);
pRangeQuery->_QuickCheckCrossedAnchors();
paQuery = (tfAnchorQuery == TF_ANCHOR_START) ? pRangeQuery->_GetStart() : pRangeQuery->_GetEnd();
fExactMatch = (Find(paQuery, &iIndex, fSearchForward) != NULL);
if (fSearchForward) { if (++iIndex >= _rgProp.Count()) return S_OK; // no next value
} else { if (fExactMatch) { --iIndex; } if (iIndex < 0) return S_OK; // no prev value
}
pPropertyList = _rgProp.Get(iIndex); Assert(pPropertyList != NULL);
fContained = (CompareAnchors(pPropertyList->_paStart, paQuery) <= 0) && (CompareAnchors(pPropertyList->_paEnd, paQuery) >= 0);
if (fContained && (dwFlags & TF_FNV_NO_CONTAINED)) { // caller wants to skip any contained value span
if (fSearchForward) { if (++iIndex >= _rgProp.Count()) return S_OK; // no next value
} else { if (--iIndex == -1) return S_OK; // no prev value
}
pPropertyList = _rgProp.Get(iIndex); Assert(pPropertyList != NULL);
fContained = FALSE; }
if ((pRangeNextValue = new CRange) == NULL) return E_OUTOFMEMORY;
if (!pRangeNextValue->_InitWithDefaultGravity(_pic, COPY_ANCHORS, pPropertyList->_paStart, pPropertyList->_paEnd)) { pRangeNextValue->Release(); return E_FAIL; }
*pfContained = fContained; *ppRangeNextValue = (ITfRangeAnchor *)pRangeNextValue;
return S_OK; }
|