// // anchor.cpp // // CACPWrap // #include "private.h" #include "anchor.h" #include "globals.h" #include "normal.h" #include "anchoref.h" #include "txtcache.h" DBG_ID_INSTANCE(CAnchor); //+--------------------------------------------------------------------------- // // _NormalizeAnchor // //---------------------------------------------------------------------------- void CACPWrap::_NormalizeAnchor(CAnchor *pa) { int iNew; int iCurrent; LONG acpNew; CAnchor *paInto; AssertPrivate(!pa->_fNormalized); // perf: we shouldn't normalize more than once Perf_IncCounter(PERF_LAZY_NORM); acpNew = Normalize(_ptsi, pa->GetIch()); pa->_fNormalized = TRUE; if (acpNew == pa->GetIch()) return; _Find(pa->GetIch(), &iCurrent); paInto = _Find(acpNew, &iNew); _Update(pa, acpNew, iCurrent, paInto, iNew); if (paInto == NULL) { // the target anchor didn't get merged, pretend it did paInto = pa; } else { // the target anchor got merged // need to set the norm bit for the anchor it got merged into paInto->_fNormalized = TRUE; } // we may have just skipped over anchors to the right of this one // handle those guys now while ((pa = _rgAnchors.Get(iCurrent)) != paInto) { Assert(pa->GetIch() < acpNew); _Merge(paInto, pa); } _Dbg_AssertAnchors(); } //+--------------------------------------------------------------------------- // // _DragAnchors // //---------------------------------------------------------------------------- void CACPWrap::_DragAnchors(LONG acpFrom, LONG acpTo) { CAnchor *paFrom; CAnchor *paTo; int iFrom; int iTo; int i; Assert(acpFrom > acpTo); // this method only handles dragging to the left _Find(acpFrom, &iFrom); if (iFrom < 0) return; // nothing to drag if (!_Find(acpTo, &iTo)) { // if acpTo isn't occupied, drag to the next highest anchor iTo++; } if (iTo > iFrom) return; // nothing to drag, iTo and iFrom ref anchors to the left of acpTo // merge all the anchors into the left most paTo = _rgAnchors.Get(iTo); for (i=iFrom; i>iTo; i--) { paFrom = _rgAnchors.Get(i); Assert(paFrom->GetIch() > paTo->GetIch()); _Merge(paTo, paFrom); } // if the left most anchor isn't already positioned, do that now paTo->SetACP(acpTo); _Dbg_AssertAnchors(); } //+--------------------------------------------------------------------------- // // _Insert // //---------------------------------------------------------------------------- HRESULT CACPWrap::_Insert(CAnchorRef *par, LONG ich) { int i; CAnchor *pa; if ((pa = _Find(ich, &i)) == NULL) { // this ich isn't in the array, allocate a new anchor if ((pa = new CAnchor) == NULL) return E_OUTOFMEMORY; // and insert it into the array if (!_rgAnchors.Insert(i+1, 1)) { delete pa; return E_FAIL; } // update _lPendingDeltaIndex if (i+1 <= _lPendingDeltaIndex) { _lPendingDeltaIndex++; } else { // new anchor is covered by a pending delta, so account for it ich -= _lPendingDelta; } pa->_paw = this; pa->_ich = ich; _rgAnchors.Set(i+1, pa); } return _Insert(par, pa); } //+--------------------------------------------------------------------------- // // _Insert // //---------------------------------------------------------------------------- HRESULT CACPWrap::_Insert(CAnchorRef *par, CAnchor *pa) { par->_pa = pa; par->_prev = NULL; par->_next = pa->_parFirst; if (pa->_parFirst) { Assert(pa->_parFirst->_prev == NULL); pa->_parFirst->_prev = par; } pa->_parFirst = par; return S_OK; } //+--------------------------------------------------------------------------- // // Remove // //---------------------------------------------------------------------------- void CACPWrap::_Remove(CAnchorRef *parIn) { CAnchor *pa = parIn->_pa; if (parIn->_prev != NULL) { // make the previous guy point past parIn parIn->_prev->_next = parIn->_next; } else { // this par is at the head of the list pa->_parFirst = parIn->_next; } if (parIn->_next != NULL) { parIn->_next->_prev = parIn->_prev; } if (pa->_parFirst == NULL) { // no more refs, delete the anchor _Delete(pa); } } //+--------------------------------------------------------------------------- // // Remove // //---------------------------------------------------------------------------- void CACPWrap::_Delete(CAnchor *pa) { int i; if (_Find(pa->GetIch(), &i) == pa) { _rgAnchors.Remove(i, 1); // update _lPendingDeltaIndex if (i < _lPendingDeltaIndex) { _lPendingDeltaIndex--; } } delete pa; } //+--------------------------------------------------------------------------- // // Update // // Normalization is handled by the caller who needs to use Renormalize after // calling this method. //---------------------------------------------------------------------------- void CACPWrap::_Update(const TS_TEXTCHANGE *pdctc) { int iStart; int ichEndOrg; int iEndOrg; int iEndNew; CAnchorRef *par; CAnchorRef **ppar; CAnchor *paStart; CAnchor *paEnd; CAnchor *paInto; BOOL fExactEndMatch; int dSize; int iDeltaStart; int ichStart = pdctc->acpStart; int ichEndOld = pdctc->acpOldEnd; int ichEndNew = pdctc->acpNewEnd; // always invalidate our text cache CProcessTextCache::Invalidate(_ptsi); Assert(ichStart >= 0); Assert(ichEndOld >= ichStart); Assert(ichEndNew >= ichStart); if (_rgAnchors.Count() == 0) // any anchors? return; dSize = ichEndNew - ichEndOld; if (dSize == 0) return; paStart = _Find(ichStart, &iStart); if (paStart == NULL) { iStart++; // return value was anchor closest to but less than ichStart if (iStart >= _rgAnchors.Count()) { // there aren't any anchors >= iStart, so bail, nothing to do return; } paStart = _rgAnchors.Get(iStart); if (iStart == 0 && paStart->GetIch() > ichEndOld) { // the delta is to the left of all the ranges _AdjustIchs(0, dSize); _Dbg_AssertAnchors(); return; } } paEnd = _Find(ichEndOld, &iEndOrg); if (paEnd == NULL) { Assert(iEndOrg >= 0); // there should be at least one anchor <= ichEndOld // if we made it this far fExactEndMatch = FALSE; paEnd = _rgAnchors.Get(iEndOrg); } else { fExactEndMatch = TRUE; } if (dSize < 0) { // if dSize < 0, then gotta merge all the anchors between old, new pos if (paEnd->GetIch() > ichEndNew) // are there any anchors between old, new pos? { // track change history // drag the paEnd to its new position ichEndOrg = paEnd->GetIch(); paInto = _Find(ichEndNew, &iEndNew); _TrackDelHistory(iEndOrg, fExactEndMatch, iEndNew, paInto != NULL); iEndNew = _Update(paEnd, ichEndNew, iEndOrg, paInto, iEndNew); _Find(ichEndOrg, &iEndOrg); // might be a prev anchor if paEnd got deleted in Update! while (iEndOrg > iEndNew) { // don't try to cache pointers, _rgAnchors might get realloc'd // during the _Merge! _Merge(_rgAnchors.Get(iEndNew), _rgAnchors.Get(iEndOrg)); iEndOrg--; } iEndOrg = iEndNew; // for below } // iEndOrg was updated so let's start from next. iDeltaStart = iEndOrg + 1; } else // dSize > 0 { // iEndOrg will be < iStart when the delta fits entirely between two existing anchors Assert(iEndOrg >= iStart || (iEndOrg == iStart - 1)); iDeltaStart = (iEndOrg <= iStart) ? iEndOrg + 1 : iEndOrg; } // update all the following anchors _AdjustIchs(iDeltaStart, dSize); if (dSize > 0 && paStart == paEnd) { // Need to account for gravity in the case of an insert to a single anchor pos. // In practice, this means that we have to handle the anchor refs at the insertion // anchor individually -- some will want to shift left, others right. ppar = &paStart->_parFirst; while (par = *ppar) { if (par->_fForwardGravity) { // remove the ref from this anchor *ppar = par->_next; if (par->_next != NULL) { par->_next->_prev = par->_prev; } // shove the ref over, it needs to be moved. Insert adds to the head of the list // so this call is fast and in constant time. _Insert(par, paStart->GetIch() + dSize); } else { ppar = &par->_next; } } if (paStart->_parFirst == NULL) { // we cleaned this guy out! Assert(_rgAnchors.Get(iEndOrg) == paStart); _rgAnchors.Remove(iEndOrg, 1); delete paStart; // update _lPendingDeltaIndex if (iEndOrg < _lPendingDeltaIndex) { _lPendingDeltaIndex--; } } } _Dbg_AssertAnchors(); } //+--------------------------------------------------------------------------- // // _TrackDelHistory // //---------------------------------------------------------------------------- void CACPWrap::_TrackDelHistory(int iEndOrg, BOOL fExactEndOrgMatch, int iEndNew, BOOL fExactEndNewMatch) { CAnchorRef *par; int i; Assert(iEndOrg >= iEndNew); if (fExactEndOrgMatch) { // all the anchoref's at iEndOrg get a preceding del for (par = _rgAnchors.Get(iEndOrg)->_parFirst; par != NULL; par = par->_next) { par->_dwHistory |= TS_CH_PRECEDING_DEL; } // if iEndOrg == iEndNew on entry, that's everything if (iEndOrg == iEndNew) return; iEndOrg--; // exclude this anchor from loop below } if (fExactEndNewMatch) { // all the anchoref's at iEndNew get a following del for (par = _rgAnchors.Get(iEndNew)->_parFirst; par != NULL; par = par->_next) { par->_dwHistory |= TS_CH_FOLLOWING_DEL; } } // exclude leftmost anchor from loop below // either we just handled it in the loop above or !fExactEndNewMatch // in which case it lies to the left of the affected anchors iEndNew++; // the the anchoref's in between get both dels for (i=iEndNew; i<=iEndOrg; i++) { for (par = _rgAnchors.Get(i)->_parFirst; par != NULL; par = par->_next) { par->_dwHistory = (TS_CH_PRECEDING_DEL | TS_CH_FOLLOWING_DEL); } } } //+--------------------------------------------------------------------------- // // _Update // // piInto gets loaded with the index of the anchor after the update. If the // index changed, pa is now bogus and a new pointer should be grabbed using // the index. //---------------------------------------------------------------------------- int CACPWrap::_Update(CAnchor *pa, int ichNew, int iOrg, CAnchor *paInto, int iInto) { int i; Assert(pa->GetIch() != ichNew); // we are so hosed if this happens i = iInto; if (paInto != NULL) { // gotta do a merge _Merge(paInto, pa); } else { if (iInto != iOrg) { // move the entry in the array to the new position i = _rgAnchors.Move(iInto+1, iOrg); } // did we cross _lPendingDeltaIndex? if (i > _lPendingDeltaIndex) { // new position is in the pending range, adjust ich ichNew -= _lPendingDelta; } else if (iOrg >= _lPendingDeltaIndex && i <= _lPendingDeltaIndex) // shifting an anchor out of the pending range { // one less anchor in the pending range _lPendingDeltaIndex++; } // change the ich _rgAnchors.Get(i)->_ich = ichNew; } _Dbg_AssertAnchors(); return i; } //+--------------------------------------------------------------------------- // // Renormalize // //---------------------------------------------------------------------------- void CACPWrap::_Renormalize(int ichStart, int ichEnd) { CAnchor *pa; int iCurrent; int iEnd; BOOL fExactEndMatch; Perf_IncCounter(PERF_RENORMALIZE_COUNTER); if (_rgAnchors.Count() == 0) return; fExactEndMatch = (_Find(ichEnd, &iEnd) != NULL); if (iEnd < 0) return; if (ichStart == ichEnd) { if (!fExactEndMatch) return; // this can happen on deletions iCurrent = iEnd; } else if (_Find(ichStart, &iCurrent) == NULL) { iCurrent++; // we don't care about anchors < ichStart } Assert(iCurrent >= 0); for (;iCurrent <= iEnd; iCurrent++) { pa = _rgAnchors.Get(iCurrent); Assert(pa->GetIch() >= 0); Assert(pa->GetIch() >= ichStart); Assert(pa->GetIch() <= ichEnd); // we'll do the real work only when we have too... pa->_fNormalized = FALSE; } _Dbg_AssertAnchors(); } //+--------------------------------------------------------------------------- // // _Find // // 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. //---------------------------------------------------------------------------- CAnchor *CACPWrap::_Find(int ich, int *piOut) { CAnchor *paMatch; int iMin; int iMax; int iMid; LONG lPendingDeltaIch; iMin = 0; iMax = _rgAnchors.Count(); // adjust search for pending delta range // values aren't consistent across the range boundary if (_lPendingDelta != 0 && _lPendingDeltaIndex < _rgAnchors.Count()) { lPendingDeltaIch = _rgAnchors.Get(_lPendingDeltaIndex)->_ich; if (ich < lPendingDeltaIch + _lPendingDelta) { iMax = _lPendingDeltaIndex; } else if (ich > lPendingDeltaIch + _lPendingDelta) { iMin = _lPendingDeltaIndex+1; ich -= _lPendingDelta; // make the search below work } else { iMid = _lPendingDeltaIndex; paMatch = _rgAnchors.Get(_lPendingDeltaIndex); goto Exit; } } paMatch = _FindInnerLoop(ich, iMin, iMax, &iMid); Exit: if (piOut != NULL) { if (paMatch == NULL && iMid >= 0) { // couldn't find a match, return the next lowest ich Assert(iMid == 0 || iMid == _lPendingDeltaIndex || _rgAnchors.Get(iMid-1)->_ich < ich); if (_rgAnchors.Get(iMid)->_ich > ich) { iMid--; } } *piOut = iMid; } return paMatch; } //+--------------------------------------------------------------------------- // // _FindInnerLoop // //---------------------------------------------------------------------------- CAnchor *CACPWrap::_FindInnerLoop(LONG acp, int iMin, int iMax, int *piIndex) { CAnchor *pa; CAnchor *paMatch; int iMid; paMatch = NULL; iMid = iMin - 1; while (iMin < iMax) { iMid = (iMin + iMax) / 2; pa = _rgAnchors.Get(iMid); Assert(pa != NULL); if (acp < pa->_ich) { iMax = iMid; } else if (acp > pa->_ich) { iMin = iMid + 1; } else // acp == pa->_ich { paMatch = pa; break; } } *piIndex = iMid; return paMatch; } //+--------------------------------------------------------------------------- // // _Merge // //---------------------------------------------------------------------------- void CACPWrap::_Merge(CAnchor *paInto, CAnchor *paFrom) { CAnchorRef *par; Assert(paInto != paFrom); // very bad! Assert(paInto->_parFirst != NULL); // should never have an anchor w/o any refs if (par = paFrom->_parFirst) { // update the anchor for the paFrom refs while (TRUE) { par->_pa = paInto; if (par->_next == NULL) break; par = par->_next; } // now par is the last ref in paFrom // shove all the refs in paFrom into paInto if (par != NULL) { Assert(par->_next == NULL); par->_next = paInto->_parFirst; Assert(paInto->_parFirst->_prev == NULL); paInto->_parFirst->_prev = par; paInto->_parFirst = paFrom->_parFirst; } } // and free the parFrom _Delete(paFrom); } //+--------------------------------------------------------------------------- // // _Dbg_AssertAnchors // //---------------------------------------------------------------------------- #ifdef DEBUG void CACPWrap::_Dbg_AssertAnchors() { int i; int ichLast; CAnchor *pa; ichLast = -1; // assert that the anchor array has ascending ich's for (i=0; i<_rgAnchors.Count(); i++) { pa = _rgAnchors.Get(i); Assert(ichLast < pa->GetIch()); ichLast = pa->GetIch(); } } #endif // DEBUG //+--------------------------------------------------------------------------- // // _AdjustIchs // //---------------------------------------------------------------------------- void CACPWrap::_AdjustIchs(int iFirst, int dSize) { CAnchor **ppaFirst; CAnchor **ppaLast; LONG dSizeAdjust; int iLastAnchor; Assert(dSize != 0); iLastAnchor = _rgAnchors.Count()-1; if (iFirst > iLastAnchor) return; if (_lPendingDelta == 0 || // no delta pending _lPendingDeltaIndex > iLastAnchor) // old pending anchors got deleted before update { // no pending delta, start a new one _lPendingDeltaIndex = iFirst; _lPendingDelta = dSize; return; } if (max(iFirst, _lPendingDeltaIndex) - min(iFirst, _lPendingDeltaIndex) > iLastAnchor / 2) { // adjust points are far apart, update the tail of the range if (iFirst > _lPendingDeltaIndex) { ppaFirst = _rgAnchors.GetPtr(iFirst); dSizeAdjust = dSize; } else { ppaFirst = _rgAnchors.GetPtr(_lPendingDeltaIndex); _lPendingDeltaIndex = iFirst; dSizeAdjust = _lPendingDelta; _lPendingDelta = dSize; } ppaLast = _rgAnchors.GetPtr(iLastAnchor); } else { // adjust points are close, update the head of the range if (iFirst > _lPendingDeltaIndex) { ppaFirst = _rgAnchors.GetPtr(_lPendingDeltaIndex); _lPendingDeltaIndex = iFirst; ppaLast = _rgAnchors.GetPtr(iFirst - 1); dSizeAdjust = _lPendingDelta; } else { ppaFirst = _rgAnchors.GetPtr(iFirst); ppaLast = _rgAnchors.GetPtr(_lPendingDeltaIndex-1); dSizeAdjust = dSize; } _lPendingDelta += dSize; } // do the real work while (ppaFirst <= ppaLast) { (*ppaFirst++)->_ich += dSizeAdjust; } }