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.
2671 lines
72 KiB
2671 lines
72 KiB
//
|
|
// 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;
|
|
}
|