/* * @doc INTERNAL * * @module DISPML.CPP -- CDisplayML class | * * This is the Multi-line display engine. See disp.c for the base class * methods and dispsl.c for the single-line display engine. * * Owner: * RichEdit 1.0 code: David R. Fulmer * Christian Fortini (initial conversion to C++) * Murray Sargent * Rick Sailor (for most of RE 2.0) * * Copyright (c) 1995-2000, Microsoft Corporation. All rights reserved. */ #include "_common.h" #include "_dispml.h" #include "_edit.h" #include "_font.h" #include "_measure.h" #include "_render.h" #include "_select.h" #include "_dfreeze.h" /* #include "icecap.h" class CCapProfile { public: CCapProfile() { StartProfile(PROFILE_THREADLEVEL, PROFILE_CURRENTID); } ~CCapProfile() { StopProfile(PROFILE_THREADLEVEL, PROFILE_CURRENTID); } }; */ ASSERTDATA // // Invariant support // #define DEBUG_CLASSNAME CDisplayML #include "_invar.h" // Timer tick counts for background task #define cmsecBgndInterval 300 #define cmsecBgndBusy 100 // Lines ahead const LONG cExtraBeforeLazy = 60; // cursor. NB! 6144 is not a measured number; 4096 was the former number, // and it wasn't measured either; it just seemed like a good one. We bumped // the number to 6144 as a safe-fix to a problem which caused cursor-flashing // for the eBook reader. The eBook reader's idle process pumps up to // 5120 characters into RichEdit between ReCalc attempts. However, each // recalc can still work on more that 5120 characters; if the // insertion started at the middle of a line, then recalc starts // at the beginning of the line, picking up a few extra characters. #define NUMCHARFORWAITCURSOR 6144 #ifndef DEBUG #define CheckView() #define CheckLineArray() #endif // =========================== CDisplayML ===================================================== #ifdef DEBUG /* * CDisplayML::Invariant * * @mfunc Make sure the display is in a valid state * * @rdesc TRUE if the tests succeeded, FALSE otherwise */ BOOL CDisplayML::Invariant(void) const { CDisplay::Invariant(); return TRUE; } #endif // DEBUG /* * CDisplayML::CalcScrollHeight() * * @mfunc * Calculate the maximum Y scroll position. * * @rdesc * Maximum possible scrolling position * * @devnote * This routine exists because plain text controls do not have * the auto-EOP and so the scroll height is different than * the height of the control if the text ends in an EOP type * character. */ LONG CDisplayML::CalcScrollHeight(LONG dvp) const { // The max scroll height for plain text controls is calculated // differently because they don't have an automatic EOP character. if(!_ped->IsRich() && Count()) { // If last character is an EOP, bump scroll height CLine *pli = Elem(Count() - 1); // Get last line in array if(pli->_cchEOP) dvp += pli->GetHeight(); } return dvp; } /* * CDisplayML::GetMaxVpScroll() * * @mfunc * Calculate the maximum Y scroll position. * * @rdesc * Maximum possible scrolling position * * @devnote * This routine exists because we may have to come back and modify this * calculation for 1.0 compatibility. If we do, this routine only needs * to be changed in one place rather than the three at which it is used. * */ inline LONG CDisplayML::GetMaxVpScroll() const { // The following code is turn off because we don't want to support // 1.0 mode unless someone complained about it. #if 0 if (_ped->Get10Mode()) { // Ensure last line is always visible // (use dy as temp to calculate max scroll) vpScroll = Elem(max(0, Count() - 1))->_dvp; if(vpScroll > _dvpView) vpScroll = _dvpView; vpScroll = _dvp - vpScroll; } #endif //0 return CalcScrollHeight(_dvp); } /* * CDisplayML::ConvertScrollToVPos(vPos) * * @mfunc * Calculate the real scroll position from the scroll position * * @rdesc * Y position from scroll * * @devnote * This routine exists because the thumb position messages * are limited to 16-bits so we extrapolate when the V position * gets greater than that. */ LONG CDisplayML::ConvertScrollToVPos( LONG vPos) //@parm Scroll position { // Get maximum scroll range LONG vpRange = GetMaxVpScroll(); // Has maximum scroll range exceeded 16-bits? if(vpRange >= _UI16_MAX) { // Yes - Extrapolate to "real" vPos vPos = MulDiv(vPos, vpRange, _UI16_MAX); } return vPos; } /* * CDisplayML::ConvertVPosToScrollPos() * * @mfunc * Calculate the scroll position from the V position in the document. * * @rdesc * Scroll position from V position * * @devnote * This routine exists because the thumb position messages * are limited to 16-bits so we extrapolate when the V position * gets greater than that. * */ inline LONG CDisplayML::ConvertVPosToScrollPos( LONG vPos) //@parm V position in document { // Get maximum scroll range LONG vRange = GetMaxVpScroll(); // Has maximum scroll range exceeded 16-bits? if(vRange >= _UI16_MAX) { // Yes - Extrapolate to "real" vPos vPos = MulDiv(vPos, _UI16_MAX, vRange); } return vPos; } CDisplayML::CDisplayML (CTxtEdit* ped) : CDisplay (ped), _pddTarget(NULL) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CDisplayML"); Assert(!_dulTarget && !_dvlTarget); _fMultiLine = TRUE; } CDisplayML::~CDisplayML() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::~CDisplayML"); delete _pddTarget; } /* * CDisplayML::Init() * * @mfunc * Init this display for the screen * * @rdesc * TRUE iff initialization succeeded */ BOOL CDisplayML::Init() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::Init"); // Initialize our base class if(!CDisplay::Init()) return FALSE; AssertSz(_ped, "CDisplayML::Init(): _ped not initialized in display"); // Verify allocation zeroed memory out Assert(!_vpCalcMax && !_dupLineMax && !_dvp && !_cpMin); Assert(!_fBgndRecalc && !_fVScrollEnabled && !_fUScrollEnabled); // The printer view is not main, therefore we do this to make // sure scroll bars are not created for print views. DWORD dwScrollBars = _ped->TxGetScrollBars(); if(IsMain() && (dwScrollBars & ES_DISABLENOSCROLL)) { if(dwScrollBars & WS_VSCROLL) { // This causes wlm to assert on the mac. something about // scrollbar being disabled _ped->TxSetScrollRange (SB_VERT, 0, 1, TRUE); _ped->TxEnableScrollBar(SB_VERT, ESB_DISABLE_BOTH); } // Set horizontal scroll range and pos if(dwScrollBars & WS_HSCROLL) { _ped->TxSetScrollRange (SB_HORZ, 0, 1, TRUE); _ped->TxEnableScrollBar(SB_HORZ, ESB_DISABLE_BOTH); } } SetWordWrap(_ped->TxGetWordWrap()); _cpFirstVisible = _cpMin; Assert(!_upScroll && !_vpScroll && !_iliFirstVisible && !_cpFirstVisible && !_dvpFirstVisible); _TEST_INVARIANT_ return TRUE; } //================================ Device drivers =================================== /* * CDisplayML::SetMainTargetDC(hdc, dulTarget) * * @mfunc * Sets a target device for this display and updates view * * @devnote * Target device can't be a metafile (can't get char width out of a * metafile) * * @rdesc * TRUE if success */ BOOL CDisplayML::SetMainTargetDC ( HDC hdc, //@parm Target DC, NULL for same as rendering device LONG dulTarget) //@parm Max line width (not used for screen) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::SetMainTargetDC"); if(SetTargetDC(hdc)) { // This is here because this is what RE 1.0 did. SetWordWrap(hdc || !dulTarget); // If dulTarget is greater than zero, then the caller is // trying to set the maximum width of the window (for measuring, // line breaking, etc.) However,in order to make our measuring // algorithms more reasonable, we force the max size to be // *at least* as wide as the width of a character. // Note that dulTarget = 0 means use the view rect width _dulTarget = (dulTarget <= 0) ? 0 : max(DXtoLX(GetDupSystemFont()), dulTarget); // Need to do a full recalc. If it fails, it fails, the lines are // left in a reasonable state. No need to call WaitForRecalc() // because UpdateView() starts at position zero and we're always // calc'd up to there CDisplay::UpdateView(); // Caret/selection has most likely moved CTxtSelection *psel = _ped->GetSelNC(); if(psel) psel->UpdateCaret(FALSE); return TRUE; } return FALSE; } // Useful for both main and printing devices. jonmat 6/08/1995 BOOL CDisplayML::SetTargetDC( HDC hdc, LONG dxpInch, LONG dypInch) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::SetTargetDC"); CDevDesc *pddTarget = NULL; // Don't allow metafiles to be set as the target device if(hdc && GetDeviceCaps(hdc, TECHNOLOGY) == DT_METAFILE) return FALSE; if(hdc) { // Allocate device first to see if we can. We don't want to change // our state if this is going to fail. pddTarget = new CDevDesc(_ped); if(!pddTarget) return FALSE; // We couldn't so we are done } // Remove any cached information for the old target device if(_pddTarget) { delete _pddTarget; _pddTarget = NULL; } if(hdc) { _pddTarget = pddTarget; // Update device because we have one _pddTarget->SetDC(hdc, dxpInch, dypInch); } return TRUE; } //================================= Line recalc ============================== /* * CDisplayML::RecalcScrollBars() * * @mfunc * Recalculate the scroll bars if the view has changed. * * * @devnote There is a possibility of recursion here, so we * need to protect ourselves. * * To visualize this, consider two types of characters, 'a' characters * which are small in height and 'A' 's which are really tall, but the same * width as an 'a'. So if I have * * a a A * A * * I'll get a calced size that's basically 2 * heightof(A). * With a scrollbar, this could wordwrap to * * a a * A A * * which is of calced size heightof(A) + heightof(a); this is * obviously less than the height in the first case. */ void CDisplayML::RecalcScrollBars() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcScrollBars"); if(_fViewChanged) { _fViewChanged = FALSE; UpdateScrollBar(SB_VERT, TRUE); UpdateScrollBar(SB_HORZ, TRUE); } } /* * CDisplayML::RecalcLines(rtp, fWait) * * @mfunc * Recalc all line breaks. * This method does a lazy calc after the last visible line * except for a bottomless control * * @rdesc * TRUE if success */ BOOL CDisplayML::RecalcLines ( CRchTxtPtr &rtp, //@parm Where change happened BOOL fWait) //@parm Recalc lines down to _cpWait/_vpWait; then be lazy { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcLines"); LONG cliWait = cExtraBeforeLazy; // Extra lines before being lazy BOOL fDone = TRUE; BOOL fFirstInPara = TRUE; CLine * pliNew = NULL; LONG dupLineMax; LONG dvp = 0; LONG cchText = _ped->GetTextLength(); BOOL fWaitingForFirstVisible = TRUE; LONG dvpView = _dvpView; LONG dvpScrollOld = GetMaxVpScroll(); LONG dvpScrollNew; DeleteSubLayouts(0, -1); Remove(0, -1); // Remove all old lines from *this _vpCalcMax = 0; // Set both maxes to start of text _cpCalcMax = 0; // Don't stop at bottom of view if we're bottomless and active. if(!_ped->TxGetAutoSize() && IsActive()) { // Be lazy - don't bother going past visible portion _cpWait = -1; _vpWait = -1; fWait = TRUE; } CMeasurer me(this, rtp); me.SetCp(0); me.SetNumber(0); // The following loop generates new lines while(me.GetCp() < cchText) { // Add one new line pliNew = Add(1, NULL); if (!pliNew) { _ped->GetCallMgr()->SetOutOfMemory(); TRACEWARNSZ("Out of memory Recalc'ing lines"); goto err; } // Stuff text into new line UINT uiFlags = MEASURE_BREAKATWORD | (fFirstInPara ? MEASURE_FIRSTINPARA : 0); Tracef(TRCSEVINFO, "Measuring new line from cp = %d", me.GetCp()); if(!Measure(me, pliNew, Count() - 1, uiFlags)) { Assert(FALSE); goto err; } fFirstInPara = pliNew->_fHasEOP; dvp += pliNew->GetHeight(); _cpCalcMax = me.GetCp(); AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax"); if(fWait) { // Do we want to do a background recalc? - the answer is yes if // three things are true: (1) We have recalc'd beyond the old first // visible character, (2) We have recalc'd beyond the visible // portion of the screen and (3) we have gone beyond the next // cExtraBeforeLazy lines to make page down go faster. if(fWaitingForFirstVisible) { if(me.GetCp() > _cpFirstVisible) { _vpWait = dvp + dvpView; fWaitingForFirstVisible = FALSE; } } else if(dvp > _vpWait && cliWait-- <= 0 && me._rgpobjWrap.Count() == 0) { fDone = FALSE; break; } } } //Create 1 line for empty controls if(!Count()) CreateEmptyLine(); Paginate(0); _vpCalcMax = dvp; _fRecalcDone = fDone; _fNeedRecalc = FALSE; dvpScrollNew = CalcScrollHeight(dvp); if(fDone && (dvp != _dvp || dvpScrollNew != dvpScrollOld) || dvpScrollNew > dvpScrollOld) { _fViewChanged = TRUE; } _dvp = dvp; dupLineMax = CalcDisplayDup(); if(fDone && dupLineMax != _dupLineMax || dupLineMax > _dupLineMax) { _dupLineMax = dupLineMax; _fViewChanged = TRUE; } Tracef(TRCSEVINFO, "CDisplayML::RecalcLine() - Done. Recalced down to line #%d", Count()); if(!fDone) // if not done, do rest in background fDone = StartBackgroundRecalc(); if(fDone) { _vpWait = -1; _cpWait = -1; CheckLineArray(); _fLineRecalcErr = FALSE; } #if defined(DEBUG) && !defined(NOFULLDEBUG) if( 1 ) { _TEST_INVARIANT_ } //Array memory allocation tracking { void **pv = (void**)((char*)this + sizeof(CDisplay) + sizeof(void*)); PvSet(*pv); } #endif return TRUE; err: TRACEERRORSZ("CDisplayML::RecalcLines() failed"); if(!_fLineRecalcErr) { _cpCalcMax = me.GetCp(); AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax"); _vpCalcMax = dvp; _fLineRecalcErr = TRUE; _ped->GetCallMgr()->SetOutOfMemory(); _fLineRecalcErr = FALSE; // fix up CArray & bail } return FALSE; } /* * CDisplayML::RecalcLines(rtp, cchOld, cchNew, fBackground, fWait, pled) * * @mfunc * Recompute line breaks after text modification * * @rdesc * TRUE if success * * @devnote * Most people call this the trickiest piece of code in RichEdit... */ BOOL CDisplayML::RecalcLines ( CRchTxtPtr &rtp, //@parm Where change happened LONG cchOld, //@parm Count of chars deleted LONG cchNew, //@parm Count of chars added BOOL fBackground, //@parm This method called as background process BOOL fWait, //@parm Recalc lines down to _cpWait/_vpWait; then be lazy CLed *pled) //@parm Returns edit impact on lines (can be NULL) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcLines"); LONG cchEdit; LONG cchSkip; LONG cliBackedUp = 0; LONG cliWait = cExtraBeforeLazy; BOOL fDone = TRUE; BOOL fFirstInPara = TRUE; LONG ili; CLed led; LONG lT = 0; // long Temporary LONG iliMain; CLine * pliMain; CLine * pliNew; CLinePtr rpOld(this); LONG dupLineMax; LONG dvp; LONG dvpPrev = 0; LONG cchText = _ped->GetTextLength(); UINT uiFlags; BOOL fReplaceResult; LONG dvpExtraLine = 0; LONG dvpScrollOld = GetMaxVpScroll(); LONG dvpScrollNew; WORD wNumber = 0; CLineArray rgliNew; DWORD dwBgndTickMax = fBackground ? GetTickCount() + cmsecBgndBusy : 0; if(!pled) pled = &led; #if defined(DEBUG) || defined(_RELEASE_ASSERTS_) LONG cp = rtp.GetCp(); if(cp > _cpCalcMax) Tracef(TRCSEVERR, "rtp %ld, _cpCalcMax %ld", cp, _cpCalcMax); AssertSz(cp <= _cpCalcMax, "CDisplayML::RecalcLines Caller didn't setup RecalcLines()"); AssertSz(!(fWait && fBackground), "CDisplayML::RecalcLines wait and background both true"); AssertSz(!(fWait && (-1 == _cpWait) && (-1 == _vpWait)), "CDisplayML::RecalcLines background recalc parms invalid"); #endif // We will not use background recalc if this is already a background recalc, // or if the control is not active or if this is an auto sized control. if(!IsActive() || _ped->TxGetAutoSize()) fWait = FALSE; // Init line pointer on old CLayout and backup to start of line rpOld.SetCp(rtp.GetCp(), FALSE); cchSkip = rpOld.GetIch(); rpOld.Move(-cchSkip); // Point rp at 1st char in line ili = rpOld; // Save line # at change for if(!Elem(ili)->IsNestedLayout()) // numbering { if(ili && (IsInOutlineView() || // Back up if not first number rtp.GetPF()->IsListNumbered())) // in list or if in OutlineView { // (Outline symbol may change) ili--; } // Back up at least one line in case we can now fit more on it // If on a line border, e.g., just inserted an EOP, backup 2; else 1 lT = !cchSkip + 1; while(rpOld > 0 && ((lT-- && (!rpOld[-1]._cchEOP || ili < rpOld)) || (rpOld[-1]._cObjectWrapLeft || rpOld[-1]._cObjectWrapRight))) { cliBackedUp++; rpOld--; cchSkip += rpOld->_cch; } } // Init measurer at rtp CMeasurer me(this, rtp); me.Move(-cchSkip); // Point at start of text to measure cchEdit = cchNew + cchSkip; // Number of chars affected by edit me.SetNumber(rpOld.GetNumber()); // Initialize list number // Determine whether we're on first line of paragraph if(rpOld > 0) { fFirstInPara = rpOld[-1]._fHasEOP; me.SetIhyphPrev(rpOld[-1]._ihyph); } dvp = VposFromLine(this, rpOld); // Update first-affected and pre-edit-match lines in pled pled->_iliFirst = rpOld; pled->_cpFirst = pled->_cpMatchOld = me.GetCp(); pled->_vpFirst = pled->_vpMatchOld = dvp; AssertSz(pled->_vpFirst >= 0, "CDisplayML::RecalcLines _vpFirst < 0"); Tracef(TRCSEVINFO, "Start recalcing from line #%d, cp=%d", pled->_iliFirst, pled->_cpFirst); // In case of error, set both maxes to where we are now _vpCalcMax = dvp; _cpCalcMax = me.GetCp(); AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax"); // If we are past the requested area to recalc and background recalc is // allowed, then just go directly to background recalc. If there is no // height, we just go ahead and calculate some lines anyway. This // prevents any weird background recalcs from occuring when it is // unnecessary to go into background recalc. if(fWait && _vpWait > 0 && dvp > _vpWait && me.GetCp() > _cpWait) { _dvp = dvp; DeleteSubLayouts((LONG)rpOld, -1); rpOld.Remove(-1); // Remove all old lines from here on StartBackgroundRecalc(); // Start up the background recalc pled->SetMax(this); return TRUE; } pliMain = NULL; iliMain = rpOld.GetLineIndex(); if (iliMain) { iliMain--; pliMain = rpOld.GetLine() - 1; } pliNew = NULL; // The following loop generates new lines for each line we backed // up over and for lines directly affected by edit while(cchEdit > 0) { pliNew = rgliNew.Add(1, NULL); // Add one new line if (!pliNew) { TRACEWARNSZ("CDisplayML::RecalcLines unable to alloc additional CLine in CLineArray"); goto errspace; } uiFlags = MEASURE_BREAKATWORD | (fFirstInPara ? MEASURE_FIRSTINPARA : 0); // Stuff text into new line Tracef(TRCSEVINFO, "Measuring new line from cp = %d", me.GetCp()); dvpExtraLine = 0; if(!Measure(me, pliNew, rgliNew.Count() - 1, uiFlags, 0, iliMain, pliMain, &dvpExtraLine)) { Assert(FALSE); goto err; } Assert(pliNew->_cch); fFirstInPara = pliNew->_fHasEOP; dvpPrev = dvp; dvp += pliNew->GetHeight(); cchEdit -= pliNew->_cch; AssertSz(cchEdit + me.GetCp() <= cchText, "CDisplayML::RecalcLines: want to measure beyond EOD"); // Calculate on what line the edit started. We do this because // we want to render the first edited line off screen so if // the line is being edited via the keyboard we don't clip // any characters. if(cchSkip > 0) { // Check whether we backed up and the line we are examining // changed at all. Even if it didn't change in outline view // have to redraw in case outline symbol changes if (cliBackedUp && cchSkip >= pliNew->_cch && pliNew->IsEqual(*(CLine *)(rpOld.GetLine())) && !IsInOutlineView() && !pliNew->_cObjectWrapLeft && !pliNew->_cObjectWrapRight) { // Perfect match, this line was not the first edited. Tracef(TRCSEVINFO, "New line matched old line #%d", (LONG)rpOld); cchSkip -= rpOld->_cch; // Update first affected line and match in pled pled->_iliFirst++; pled->_cpFirst += rpOld->_cch; pled->_cpMatchOld += rpOld->_cch; pled->_vpFirst += rpOld->GetHeight(); AssertSz(pled->_vpFirst >= 0, "CDisplayML::RecalcLines _vpFirst < 0"); pled->_vpMatchOld += rpOld->GetHeight(); cliBackedUp--; rgliNew.Clear(AF_KEEPMEM); // Discard new line if(!(rpOld++)) // Next line cchSkip = 0; } else // No match in the line, so cchSkip = 0; // this line is the first to } // be edited if(fBackground && GetTickCount() >= dwBgndTickMax) { fDone = FALSE; // took too long, stop for now goto no_match; } if (fWait && dvp > _vpWait && me.GetCp() > _cpWait && cliWait-- <= 0) { // Not really done, just past region we're waiting for // so let background recalc take it from here fDone = FALSE; goto no_match; } } // while(cchEdit > 0) { } Tracef(TRCSEVINFO, "Done recalcing edited text. Created %d new lines", rgliNew.Count()); // Edit lines have been exhausted. Continue breaking lines, // but try to match new & old breaks wNumber = me._wNumber; while(me.GetCp() < cchText) { // We are trying for a match so assume that there // is a match after all BOOL frpOldValid = TRUE; // Look for match in old line break CArray lT = me.GetCp() - cchNew + cchOld; while (rpOld.IsValid() && pled->_cpMatchOld < lT) { pled->_vpMatchOld += rpOld->GetHeight(); pled->_cpMatchOld += rpOld->_cch; if(!rpOld.NextRun()) { // No more line array entries so we can give up on // trying to match for good. frpOldValid = FALSE; break; } } // If perfect match, stop. if (frpOldValid && rpOld.IsValid() && pled->_cpMatchOld == lT && rpOld->_cch && me._wNumber == rpOld->_bNumber) { Tracef(TRCSEVINFO, "Found match with old line #%d", rpOld.GetLineIndex()); // Update fliFirstInPara flag in 1st old line that matches. Note // that if the new array doesn't have any lines, we have to look // into the line array preceding the current change. rpOld->_fFirstInPara = TRUE; if(rgliNew.Count() > 0) { if(!(rgliNew.Elem(rgliNew.Count() - 1)->_fHasEOP)) rpOld->_fFirstInPara = FALSE; } else if(rpOld >= pled->_iliFirst && pled->_iliFirst) { if(!(rpOld[pled->_iliFirst - rpOld - 1]._fHasEOP)) rpOld->_fFirstInPara = FALSE; } pled->_iliMatchOld = rpOld; // Replace old lines by new ones lT = rpOld - pled->_iliFirst; rpOld = pled->_iliFirst; DeleteSubLayouts(pled->_iliFirst, lT); if(!rpOld.Replace (lT, &rgliNew)) { TRACEWARNSZ("CDisplayML::RecalcLines unable to alloc additional CLines in rpOld"); goto errspace; } frpOldValid = rpOld.ChgRun(rgliNew.Count()); rgliNew.Clear(AF_KEEPMEM); // Clear aux array // Remember information about match after editing Assert((cp = rpOld.CalculateCp()) == me.GetCp()); pled->_vpMatchOld += dvpExtraLine; pled->_vpMatchNew = dvp + dvpExtraLine; pled->_vpMatchNewTop = dvpPrev; pled->_iliMatchNew = rpOld; pled->_cpMatchNew = me.GetCp(); // Compute height and cp after all matches _cpCalcMax = me.GetCp(); AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax"); if(frpOldValid && rpOld.IsValid()) { do { dvp += rpOld->GetHeight(); _cpCalcMax += rpOld->_cch; } while( rpOld.NextRun() ); #ifdef DEBUG CTxtPtr tp(_ped, _cpCalcMax); AssertSz(!IN_RANGE(STARTFIELD, tp.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax"); #endif } // Make sure _cpCalcMax is sane after the above update AssertSz(_cpCalcMax <= cchText, "CDisplayML::RecalcLines match extends beyond EOF"); // We stop calculating here.Note that if _cpCalcMax < size // of text, this means a background recalc is in progress. // We will let that background recalc get the arrays // fully in sync. AssertSz(_cpCalcMax == cchText || _fBgndRecalc, "CDisplayML::Match less but no background recalc"); if(_cpCalcMax != cchText) { // This is going to be finished by the background recalc // so set the done flag appropriately. fDone = FALSE; } goto match; } // Add a new line pliNew = rgliNew.Add(1, NULL); if(!pliNew) { TRACEWARNSZ("CDisplayML::RecalcLines unable to alloc additional CLine in CLineArray"); goto errspace; } Tracef(TRCSEVINFO, "Measuring new line from cp = %d", me.GetCp()); // Stuff some text into new line wNumber = me._wNumber; if(!Measure(me, pliNew, rgliNew.Count() - 1, MEASURE_BREAKATWORD | (fFirstInPara ? MEASURE_FIRSTINPARA : 0), 0, iliMain, pliMain)) { Assert(FALSE); goto err; } fFirstInPara = pliNew->_fHasEOP; dvp += pliNew->GetHeight(); if(fBackground && GetTickCount() >= (DWORD)dwBgndTickMax) { fDone = FALSE; // Took too long, stop for now break; } if(fWait && dvp > _vpWait && me.GetCp() > _cpWait && cliWait-- <= 0 && me._rgpobjWrap.Count() == 0) { // Not really done, just past region we're fDone = FALSE; // waiting for so let background recalc break; // take it from here } } // while(me < cchText) ... no_match: // Didn't find match: whole line array from _iliFirst needs to be changed pled->_iliMatchOld = Count(); pled->_cpMatchOld = cchText; pled->_vpMatchNew = dvp; pled->_vpMatchNewTop = dvpPrev; pled->_vpMatchOld = _dvp; _cpCalcMax = me.GetCp(); AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax"); // Replace old lines by new ones rpOld = pled->_iliFirst; // We store the result from the replace because although it can fail the // fields used for first visible must be set to something sensible whether // the replace fails or not. Further, the setting up of the first visible // fields must happen after the Replace because the lines could have // changed in length which in turns means that the first visible position // has failed. DeleteSubLayouts(rpOld, -1); fReplaceResult = rpOld.Replace(-1, &rgliNew); // _iliMatchNew & _cpMatchNew are used for first visible constants so we // need to set them to something reasonable. In particular the rendering // logic expects _cpMatchNew to be set to the first character of the first // visible line. rpOld is used because it is convenient. // Note we can't use RpBindToCp at this point because the first visible // information is screwed up because we may have changed the line that // the first visible cp is on. rpOld.BindToCp(me.GetCp(), cchText); pled->_iliMatchNew = rpOld.GetLineIndex(); pled->_cpMatchNew = me.GetCp() - rpOld.GetIch(); if (!fReplaceResult) { TRACEERRORSZ("CDisplayML::RecalcLines rpOld.Replace() failed"); goto errspace; } // Adjust first affected line if this line is gone // after replacing by new lines if(pled->_iliFirst >= Count() && Count() > 0) { Assert(pled->_iliFirst == Count()); pled->_iliFirst = Count() - 1; pliNew = Elem(pled->_iliFirst); pled->_vpFirst -= pliNew->GetHeight(); AssertSz(pled->_vpFirst >= 0, "CDisplayML::RecalcLines _vpFirst < 0"); pled->_cpFirst -= pliNew->_cch; } #ifdef DEBUG if (_ped->GetTextLength()) Assert(Count()); #endif //Create 1 line for empty controls if(!Count()) CreateEmptyLine(); match: _fRecalcDone = fDone; _fNeedRecalc = FALSE; _vpCalcMax = dvp; Tracef(TRCSEVINFO, "CDisplayML::RecalcLine(rtp, ...) - Done. Recalced down to line #%d", Count() - 1); // Clear wait fields since we want caller's to set them up. _vpWait = -1; _cpWait = -1; if(fDone && fBackground) { TRACEINFOSZ("Background line recalc done"); _ped->TxKillTimer(RETID_BGND_RECALC); _fBgndRecalc = FALSE; _fRecalcDone = TRUE; } // Determine display height and update scrollbar dvpScrollNew = CalcScrollHeight(dvp); if (_fViewChanged || fDone && (dvp != _dvp || dvpScrollNew != dvpScrollOld) || dvpScrollNew > dvpScrollOld) { //!NOTE: // UpdateScrollBar can cause a resize of the window by hiding or showing // scrollbars. As a consequence of resizing the lines may get recalculated // therefore updating _dvp to a new value, something != to dvp. _dvp = dvp; UpdateScrollBar(SB_VERT, TRUE); } else _dvp = dvp; // Guarantee heights agree // Determine display width and update scrollbar dupLineMax = CalcDisplayDup(); if(_fViewChanged || (fDone && dupLineMax != _dupLineMax) || dupLineMax > _dupLineMax) { _dupLineMax = dupLineMax; UpdateScrollBar(SB_HORZ, TRUE); } _fViewChanged = FALSE; // If not done, do the rest in background if(!fDone && !fBackground) fDone = StartBackgroundRecalc(); if(fDone) { CheckLineArray(); _fLineRecalcErr = FALSE; } #ifdef DEBUG if( 1 ) { _TEST_INVARIANT_ } #endif // DEBUG Paginate(pled->_iliFirst); return TRUE; errspace: _ped->GetCallMgr()->SetOutOfMemory(); _fNeedRecalc = TRUE; _cpCalcMax = _vpCalcMax = 0; _fLineRecalcErr = TRUE; err: if(!_fLineRecalcErr) { _cpCalcMax = me.GetCp(); AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax"); _vpCalcMax = dvp; } TRACEERRORSZ("CDisplayML::RecalcLines() failed"); if(!_fLineRecalcErr) { _fLineRecalcErr = TRUE; _ped->GetCallMgr()->SetOutOfMemory(); _fLineRecalcErr = FALSE; // fix up CArray & bail } pled->SetMax(this); return FALSE; } /* * CDisplayML::CalcDisplayDup() * * @mfunc * Calculates width of this display by walking line CArray and * returning widest line. Used for horizontal scrollbar routines. * * @rdesc * Widest line width in display */ LONG CDisplayML::CalcDisplayDup() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CalcDisplayDup"); LONG dupLineMax = 0; if (_ped->fInOurHost() && (_ped->GetHost())->TxGetHorzExtent(&dupLineMax) == S_OK) { return dupLineMax; } LONG ili = Count(); CLine *pli; if(ili) { LONG dupLine; pli = Elem(0); for(dupLineMax = 0; ili--; pli++) { dupLine = pli->_upStart + pli->_dup; dupLineMax = max(dupLineMax, dupLine); } } return dupLineMax; } /* * CDisplayML::StartBackgroundRecalc() * * @mfunc * Starts background line recalc (at _cpCalcMax position) * * @rdesc * TRUE if done with background recalc */ BOOL CDisplayML::StartBackgroundRecalc() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::StartBackgroundRecalc"); if(_fBgndRecalc) return FALSE; // Already in background recalc AssertSz(_cpCalcMax <= _ped->GetTextLength(), "_cpCalcMax > text length"); if(_cpCalcMax == _ped->GetTextLength()) return TRUE; // Enough chars are recalc'd if(!_ped->TxSetTimer(RETID_BGND_RECALC, cmsecBgndInterval)) { // Could not instantiate a timer so wait for recalculation WaitForRecalc(_ped->GetTextLength(), -1); return TRUE; } _fRecalcDone = FALSE; _fBgndRecalc = TRUE; return FALSE; } /* * CDisplayML::StepBackgroundRecalc() * * @mfunc * Steps background line recalc (at _cpCalcMax position) * Called by timer proc and also when going inactive. */ void CDisplayML::StepBackgroundRecalc() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::StepBackgroundRecalc"); _TEST_INVARIANT_ if(!_fBgndRecalc) // Not in background recalc, return; // so don't do anything LONG cch = _ped->GetTextLength() - _cpCalcMax; // Don't try recalc when processing OOM or had an error doing recalc or // if we are asserting. #ifdef DEBUG if(_fInBkgndRecalc || _fLineRecalcErr) { if(_fInBkgndRecalc) TRACEINFOSZ("avoiding reentrant background recalc"); else TRACEINFOSZ("OOM: not stepping recalc"); return; } #else if(_fInBkgndRecalc || _fLineRecalcErr) return; #endif _fInBkgndRecalc = TRUE; if(!IsActive()) { // Background recalc is over if we are no longer active because // we can no longer get the information we need for recalculating. // But, if we are half recalc'd we need to set ourselves up to // recalc again when we go active. InvalidateRecalc(); cch = 0; } // Background recalc is over if no more chars or no longer active if(cch <= 0) { TRACEINFOSZ("Background line recalc done"); _ped->TxKillTimer(RETID_BGND_RECALC); _fBgndRecalc = FALSE; _fRecalcDone = TRUE; _fInBkgndRecalc = FALSE; CheckLineArray(); return; } CRchTxtPtr rtp(_ped, _cpCalcMax); AssertSz(!IN_RANGE(STARTFIELD, rtp.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax"); RecalcLines(rtp, cch, cch, TRUE, FALSE, NULL); _fInBkgndRecalc = FALSE; } /* * CDisplayML::WaitForRecalc(cpMax, vpMax) * * @mfunc * Ensures that lines are recalced until a specific character * position or vPos. * * @rdesc * success */ BOOL CDisplayML::WaitForRecalc( LONG cpMax, //@parm Position recalc up to (-1 to ignore) LONG vpMax) //@parm vPos to recalc up to (-1 to ignore) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::WaitForRecalc"); _TEST_INVARIANT_ if(IsFrozen()) return TRUE; BOOL fReturn = TRUE; LONG cch; if((vpMax < 0 || vpMax >= _vpCalcMax) && (cpMax < 0 || cpMax >= _cpCalcMax)) { cch = _ped->GetTextLength() - _cpCalcMax; if(cch > 0 || Count() == 0) { HCURSOR hcur = NULL; _cpWait = cpMax; _vpWait = vpMax; if(cch > NUMCHARFORWAITCURSOR) hcur = _ped->TxSetCursor(LoadCursor(0, IDC_WAIT)); TRACEINFOSZ("Lazy recalc"); CRchTxtPtr rtp(_ped, _cpCalcMax); if(!_cpCalcMax || _fNeedRecalc) { fReturn = RecalcLines(rtp, TRUE); RebindFirstVisible(); if(!fReturn) InitVars(); } else fReturn = RecalcLines(rtp, cch, cch, FALSE, TRUE, NULL); if(hcur) _ped->TxSetCursor(hcur); } else if(!cch) { // If there was nothing else to calc, make sure that we think // recalc is done. #ifdef DEBUG if( !_fRecalcDone ) { TRACEWARNSZ("For some reason we didn't think background " "recalc was done, but it was!!"); } #endif // DEBUG _fRecalcDone = TRUE; } } // If view rect changed, make sure to update scrollbars RecalcScrollBars(); return fReturn; } /* * CDisplayML::WaitForRecalcIli(ili) * * @mfunc * Wait until line array is recalculated up to line

* * @rdesc * Returns TRUE if lines were recalc'd up to ili */ //REVIEW (keithcu) This recalcs up to the end! I'm not certain how great //our background recalc, etc. stuff is. It seems not to work all that well for //the complexity it adds to our codebase. I think we should either throw it //away or redo it. BOOL CDisplayML::WaitForRecalcIli ( LONG ili) //@parm Line index to recalculate line array up to { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::WaitForRecalcIli"); LONG cchGuess; while(!_fRecalcDone && ili >= Count()) { // just go ahead and recalc everything. cchGuess = _ped->GetTextLength(); if(IsFrozen() || !WaitForRecalc(cchGuess, -1)) return FALSE; } return ili < Count(); } /* * CDisplayML::WaitForRecalcView() * * @mfunc * Ensure visible lines are completly recalced * * @rdesc TRUE iff successful */ BOOL CDisplayML::WaitForRecalcView() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::WaitForRecalcView"); return WaitForRecalc(-1, _vpScroll + _dvpView); } /* * CDisplayML::InitLinePtr ( CLinePtr & plp ) * * @mfunc * Initialize a CLinePtr properly */ void CDisplayML::InitLinePtr ( CLinePtr & plp ) //@parm Ptr to line to initialize { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::InitLinePtr"); plp.Init( *this ); } /* * CDisplayML::GetLineText(ili, pchBuff, cchMost) * * @mfunc * Copy given line of this display into a character buffer * * @rdesc * number of character copied */ LONG CDisplayML::GetLineText( LONG ili, //@parm Line to get text of TCHAR *pchBuff, //@parm Buffer to stuff text into LONG cchMost) //@parm Length of buffer { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetLineText"); _TEST_INVARIANT_ CTxtPtr tp (_ped, 0); if(ili >= 0 && (ili < Count() || WaitForRecalcIli(ili))) { cchMost = min(cchMost, Elem(ili)->_cch); if(cchMost > 0) { tp.SetCp(CpFromLine(ili, NULL)); return tp.GetText(cchMost, pchBuff); } } *pchBuff = TEXT('\0'); return 0; } /* * CDisplayML::LineCount * * @mfunc returns the number of lines in this control. Note that for plain * text mode, we will add on an extra line of the last character is * a CR. This is for compatibility with MLE * * @rdesc LONG */ LONG CDisplayML::LineCount() const { LONG cLine = Count(); if (!_ped->IsRich() && (!cLine || // If plain text with no lines Elem(cLine - 1)->_cchEOP)) // or last line ending with a CR, { // then inc line count cLine++; } return cLine; } // ================================ Line info retrieval ==================================== /* * CDisplayML::CpFromLine(ili, pdvp) * * @mfunc * Computes cp at start of given line * (and top of line position relative to this display) * * @rdesc * cp of given line */ LONG CDisplayML::CpFromLine ( LONG ili, //@parm Line we're interested in (if 0 means caret line) LONG *pdvp) //@parm Returns top of line relative to display // (NULL if don't want that info) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CpFromLine"); _TEST_INVARIANT_ LONG cli; LONG vp = _vpScroll + _dvpFirstVisible; LONG cp = _cpFirstVisible; CLine *pli; LONG iStart = _iliFirstVisible; cli = ili - _iliFirstVisible; if(cli < 0 && -cli >= ili) { // Closer to first line than to first visible line, // so start at the first line cli = ili; vp = 0; cp = 0; iStart = 0; } else if( cli <= 0 ) { CheckView(); for(ili = _iliFirstVisible-1; cli < 0; cli++, ili--) { pli = Elem(ili); vp -= pli->GetHeight(); cp -= pli->_cch; } goto end; } for(ili = iStart; cli > 0; cli--, ili++) { pli = Elem(ili); if(!IsMain() || !WaitForRecalcIli(ili)) break; vp += pli->GetHeight(); cp += pli->_cch; } end: if(pdvp) *pdvp = vp; return cp; } /* * CDisplayML::LineFromCp(cp, fAtEnd) * * @mfunc * Computes line containing given cp. * * @rdesc * index of line found, -1 if no line at that cp. */ LONG CDisplayML::LineFromCp( LONG cp, //@parm cp to look for BOOL fAtEnd) //@parm If true, return previous line for ambiguous cp { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::LineFromCp"); _TEST_INVARIANT_ CLinePtr rp(this); if(!WaitForRecalc(cp, -1) || !rp.SetCp(cp, fAtEnd)) return -1; return (LONG)rp; } /* * CDisplayML::CpFromPoint(pt, prcClient, prtp, prp, fAllowEOL, phit, * pdispdim, pcpActual) * @mfunc * Determine cp at given point * * @devnote * --- Use when in-place active only --- * * @rdesc * Computed cp, -1 if failed */ LONG CDisplayML::CpFromPoint( POINTUV pt, //@parm Point to compute cp at (client coords) const RECTUV *prcClient,//@parm Client rectangle (can be NULL if active). CRchTxtPtr * const prtp,//@parm Returns text pointer at cp (may be NULL) CLinePtr * const prp, //@parm Returns line pointer at cp (may be NULL) BOOL fAllowEOL, //@parm Click at EOL returns cp after CRLF HITTEST * phit, //@parm Out parm for hit-test value CDispDim * pdispdim, //@parm Out parm for display dimensions LONG *pcpActual, //@parm Out cp that pt is above CLine * pliParent) //@parm Parent pli for table row displays { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CpFromPoint"); CMeasurer me(this); return CLayout::CpFromPoint(me, pt, prcClient, prtp, prp, fAllowEOL, phit, pdispdim, pcpActual); } /* * CDisplayML::PointFromTp(rtp, prcClient, fAtEnd, pt, prp, taMode, pdispdim) * * @mfunc * Determine coordinates at given tp * * @devnote * --- Use when in-place active only --- * * @rdesc * line index at cp, -1 if error */ LONG CDisplayML::PointFromTp( const CRchTxtPtr &rtp, //@parm Text ptr to get coordinates at const RECTUV *prcClient,//@parm Client rectangle (can be NULL if active). BOOL fAtEnd, //@parm Return end of prev line for ambiguous cp POINTUV & pt, //@parm Returns point at cp in client coords CLinePtr * const prp, //@parm Returns line pointer at tp (may be null) UINT taMode, //@parm Text Align mode: top, baseline, bottom CDispDim * pdispdim) //@parm Out parm for display dimensions { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::PointFromTp"); CMeasurer me(this, rtp); return CLayout::PointFromTp(me, rtp, prcClient, fAtEnd, pt, prp, taMode, pdispdim); } /* * Render(rcView, rcRender) * * @mfunc * Renders text. */ void CDisplayML::Render( const RECTUV &rcView, //@parm View RECT const RECTUV &rcRender) //@parm RECT to render (must be container in client rect) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::Render"); _TEST_INVARIANT_ LONG cp; LONG ili; LONG lCount = Count(); CTxtSelection *psel = _ped->GetSelNC(); POINTUV pt; LONG vpLine; if(psel) psel->ClearCchPending(); // Calculate line and cp to start display at if(IsInPageView()) { cp = _cpFirstVisible; ili = _iliFirstVisible; vpLine = _vpScroll; } else ili = LineFromVpos(this, rcRender.top + _vpScroll - rcView.top, &vpLine, &cp); CLine *pli = Elem(ili); CLine *pliFirst = pli; LONG dvpBottom = BottomOfRender(rcView, rcRender); LONG vpLi = pli->GetHeight(); // Calculate point where text will start being displayed pt.u = rcView.left - _upScroll; pt.v = rcView.top - _vpScroll + vpLine; // Create and prepare renderer CRenderer re(this); if(!re.StartRender(rcView, rcRender)) return; // Init renderer at start of first line to render re.SetCurPoint(pt); POINTUV ptFirst = pt; LONG cpFirst = cp = re.SetCp(cp); vpLi = pt.v; // Render each line in update rectangle for (;; pli++, ili++) { BOOL fLastLine = ili == lCount - 1 || re.GetCurPoint().v + pli->GetHeight() >= dvpBottom || IsInPageView() && ili + 1 < lCount && (pli + 1)->_fFirstOnPage; //Support khyphChangeAfter if (ili > 0) re.SetIhyphPrev((pli - 1)->_ihyph); //Don't draw the line if it doesn't intersect the rendering area, //but draw at least 1 line so that we erase the control if (pt.v + pli->GetHeight() < rcRender.top && !fLastLine) { pt.v += pli->GetHeight(); re.SetCurPoint(pt); re.Move(pli->_cch); } else if (!CLayout::Render(re, pli, &rcView, fLastLine, ili, lCount)) break; if (fLastLine) break; #ifdef DEBUG cp += pli->_cch; vpLi += pli->GetHeight(); // Rich controls with password characters stop at EOPs, // so re.GetCp() may be less than cp. AssertSz(_ped->IsRich() && _ped->fUsePassword() || re.GetCp() == cp, "cp out of sync with line table"); #endif pt = re.GetCurPoint(); AssertSz(pt.v == vpLi, "CDisplayML::RenderView() - y out of sync with line table"); } re.EndRender(pliFirst, pli, cpFirst, ptFirst); } //=================================== View Updating =================================== /* * CDisplayML::RecalcView(fUpdateScrollBars) * * @mfunc * Recalc all lines breaks and update first visible line * * @rdesc * TRUE if success */ BOOL CDisplayML::RecalcView( BOOL fUpdateScrollBars, RECTUV* prc) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcView"); BOOL fRet = TRUE; LONG dvpOld = _dvp; LONG vpScrollHeightOld = GetMaxVpScroll(); LONG dupOld = _dupLineMax; LONG vpScrollHeightNew; // Full recalc lines CRchTxtPtr rtp(_ped, 0); if(!RecalcLines(rtp, FALSE)) { // We're in deep crap now, the recalc failed. Let's try to get out // of this with our head still mostly attached InitVars(); fRet = FALSE; goto Done; } // Force _upScroll = 0 if x scroll range is smaller than the view width if(_dupLineMax <= _dupView) _upScroll = 0; vpScrollHeightNew = GetMaxVpScroll(); RebindFirstVisible(vpScrollHeightNew <= _dvpView); CheckView(); // We only need to resize if the size needed to display the object has // changed. if (dvpOld != _dvp || vpScrollHeightOld != vpScrollHeightNew || dupOld != _dupLineMax) { if(FAILED(RequestResize())) _ped->GetCallMgr()->SetOutOfMemory(); else if (prc && _ped->_fInOurHost)/*bug fix# 5830, forms3 relies on old behavior*/ _ped->TxGetClientRect(prc); } Done: // Now update scrollbars if(fUpdateScrollBars) RecalcScrollBars(); return fRet; } /* * CDisplayML::UpdateView(&rtp, cchOld, cchNew) * * @mfunc * Recalc lines and update the visible part of the display * (the "view") on the screen. * * @devnote * --- Use when in-place active only --- * * @rdesc * TRUE if success */ BOOL CDisplayML::UpdateView( CRchTxtPtr &rtp, //@parm Text ptr where change happened LONG cchOld, //@parm Count of chars deleted LONG cchNew) //@parm Count of chars inserted { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::UpdateView"); BOOL fReturn = TRUE; BOOL fRecalcVisible = TRUE; RECTUV rcClient; RECTUV rcView; CLed led; CTxtSelection *psel = _ped->GetSelNC(); LONG cpStartOfUpdate = rtp.GetCp(); BOOL fNeedViewChange = FALSE; LONG dvpOld = _dvp; LONG vpScrollHeightOld = GetMaxVpScroll(); LONG dupOld = _dupLineMax; LONG vpScrollOld = _vpScroll; LONG cpNewFirstVisible; if(_fNoUpdateView) return fReturn; AssertSz(_ped->_fInPlaceActive, "CDisplayML::UpdateView called when inactive"); if(rtp.GetCp() > _cpCalcMax || _fNeedRecalc) { // We haven't even calc'ed this far, so don't bother with updating // here. Background recalc will eventually catch up to us. if(!rtp.GetCp()) { // Changes started at start of doc _cpCalcMax = 0; // so previous calc'd state is _vpCalcMax = 0; // completely invalid } return TRUE; } AssertSz(rtp.GetCp() <= _cpCalcMax, "CDisplayML::UpdateView: rtp > _cpCalcMax"); _ped->TxGetClientRect(&rcClient); GetViewRect(rcView, &rcClient); if(psel && !psel->PuttingChar()) psel->ClearCchPending(); DeferUpdateScrollBar(); // In general, background recalc should not start until both the scroll // position is beyond the visible view and the cp is beyond the first visible // character. However, for the recalc we will only wait on the height. // Later calls to WaitForRecalc will wait on cpFirstVisible if that is // necessary. _vpWait = _vpScroll + _dvpView; _cpWait = -1; if(!RecalcLines(rtp, cchOld, cchNew, FALSE, TRUE, &led)) { // We're in deep crap now, the recalc failed. Let's try to get // out of this with our head still mostly attached InitVars(); fRecalcVisible = TRUE; fReturn = FALSE; _ped->TxInvalidate(); fNeedViewChange = TRUE; goto Exit; } if(_dupLineMax <= _dupView) { // x scroll range is smaller than the view width, force x scrolling position = 0 // we have to redraw all when this means scrolling back to home. // Problem lines are lines with trailing spaces crossing _dupView. UpdateCaret forces redraw // only when such lines are growing, misses shrinking. if (_upScroll != 0) _ped->TxInvalidate(); //REVIEW: find a smaller rectangle? _upScroll = 0; } if(led._vpFirst >= _vpScroll + _dvpView) { // Update is after view: don't do anything fRecalcVisible = FALSE; AssertNr(VerifyFirstVisible()); goto finish; } else if(led._vpMatchNew <= _vpScroll + _dvpFirstVisible && led._vpMatchOld <= _vpScroll + _dvpFirstVisible && _vpScroll < _dvp) { if (_dvp != 0) { // Update is entirely before view: just update scroll position // but don't touch the screen _vpScroll += led._vpMatchNew - led._vpMatchOld; _iliFirstVisible += led._iliMatchNew - led._iliMatchOld; _iliFirstVisible = max(_iliFirstVisible, 0); _cpFirstVisible += led._cpMatchNew - led._cpMatchOld; _cpFirstVisible = min(_ped->GetTextLength(), _cpFirstVisible); _cpFirstVisible = max(0, _cpFirstVisible); fRecalcVisible = FALSE; Sync_yScroll(); } else { // Odd outline case. Height of control can be recalc'd to zero due // when outline mode collapses all lines to 0. Example of how to // do this is tell outline to collapse to heading 1 and there is none. _vpScroll = 0; _iliFirstVisible = 0; _cpFirstVisible = 0; _sPage = 0; } AssertNr(VerifyFirstVisible()); } else { // Update overlaps visible view RECTUV rc = rcClient; // Do we need to resync the first visible? Note that this if check // is mostly an optmization; we could decide to _always_ recompute // this _iliFirstVisible if we wanted to unless rtp is inside a table, // in which case _cpFirstVisible won't change and the following may // mess up _dvpFirstVisible. const CParaFormat *pPF = rtp.GetPF(); if((!pPF->_bTableLevel || rtp._rpTX.IsAtTRD(0)) && (cpStartOfUpdate <= _cpFirstVisible || led._iliMatchOld <= _iliFirstVisible || led._iliMatchNew <= _iliFirstVisible || led._iliFirst <= _iliFirstVisible )) { // Edit overlaps the first visible. We try to maintain // approximately the same place in the file visible. cpNewFirstVisible = _cpFirstVisible; if(_iliFirstVisible - 1 == led._iliFirst) { // Edit occurred on line before visible view. Most likely // this means that the first character got pulled back to // the previous line so we want that line to be visible. cpNewFirstVisible = led._cpFirst; } // Change first visible entries because CLinePtr::SetCp() and // VposFromLine() use them, but they're not valid _dvpFirstVisible = 0; _cpFirstVisible = 0; _iliFirstVisible = 0; _vpScroll = 0; // With certain formatting changes, it's possible for // cpNewFirstVisible to be less that what's been calculated so far // in RecalcLines above. Wait for things to catch up. WaitForRecalc(cpNewFirstVisible, -1); Set_yScroll(cpNewFirstVisible); } AssertNr(VerifyFirstVisible()); // Is there a match in the display area? - this can only happen if the // old match is on the screen and the new match will be on the screen if (led._vpMatchOld < vpScrollOld + _dvpView && led._vpMatchNew < _vpScroll + _dvpView) { // We have a match inside visible view // Scroll the part that is below the old y pos of the match // or invalidate if the new y of the match is now below the view rc.top = rcView.top + (led._vpMatchOld - vpScrollOld); if(rc.top < rc.bottom) { // Calculate difference between new and old screen positions const INT dvp = (led._vpMatchNew - _vpScroll) - (led._vpMatchOld - vpScrollOld); if(dvp) { if(!IsTransparent() && _ped->GetBackgroundType() == -1) { LONG dxp, dyp; GetDxpDypFromDupDvp(0, dvp, GetTflow(), dxp, dyp); RECTUV rcClip = {rcClient.left, rcView.top, rcClient.right, rcView.bottom }; RECT rcxyClip, rcxy; RectFromRectuv(rcxyClip, rcClip); RectFromRectuv(rcxy, rc); _ped->TxScrollWindowEx(dxp, dyp, &rcxy, &rcxyClip); fNeedViewChange = TRUE; if(dvp < 0) { rc.top = rc.bottom + dvp; _ped->TxInvalidateRect(&rc); fNeedViewChange = TRUE; } } else { // Just invalidate cuz we don't scroll in transparent // mode RECTUV rcInvalidate = rc; rcInvalidate.top += dvp; _ped->TxInvalidateRect(&rcInvalidate); fNeedViewChange = TRUE; } } } else { rc.top = rcView.top + led._vpMatchNew - _vpScroll; _ped->TxInvalidateRect(&rc); fNeedViewChange = TRUE; } // Since we found that the new match falls on the screen, we can // safely set the bottom to the new match since this is the most // that can have changed. rc.bottom = rcView.top + max(led._vpMatchNew, led._vpMatchOld) - _vpScroll; } rc.top = rcView.top + led._vpFirst - _vpScroll; // Set first line edited to be rendered using off-screen bitmap if (led._iliFirst < Count() && !IsTransparent() && !Elem(led._iliFirst)->_fUseOffscreenDC) Elem(led._iliFirst)->_fOffscreenOnce = Elem(led._iliFirst)->_fUseOffscreenDC = TRUE; // Invalidate part of update that is above match (if any) _ped->TxInvalidateRect (&rc); fNeedViewChange = TRUE; } finish: if(fRecalcVisible) { fReturn = WaitForRecalcView(); if(!fReturn) return FALSE; } if(fNeedViewChange) _ped->GetHost()->TxViewChange(FALSE); CheckView(); // We only need to resize if size needed to display object has changed if (dvpOld != _dvp || vpScrollHeightOld != GetMaxVpScroll() || dupOld != _dupLineMax) { if(FAILED(RequestResize())) _ped->GetCallMgr()->SetOutOfMemory(); } if(DoDeferredUpdateScrollBar()) { if(FAILED(RequestResize())) _ped->GetCallMgr()->SetOutOfMemory(); DoDeferredUpdateScrollBar(); } Exit: return fReturn; } /* * CDisplayML::RecalcLine(cp) * * @mfunc * Show line */ void CDisplayML::RecalcLine( LONG cp) //@parm cp line to recalc { CNotifyMgr *pnm = GetPed()->GetNotifyMgr(); if(pnm) pnm->NotifyPostReplaceRange(NULL, cp, 0, 0, cp, cp); } void CDisplayML::InitVars() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::InitVars"); _vpScroll = _upScroll = 0; _iliFirstVisible = 0; _cpFirstVisible = _cpMin = 0; _dvpFirstVisible = 0; _sPage = 0; } /* * CDisplayML::GetCliVisible(pcpMostVisible) * * @mfunc * Get count of visible lines and update _cpMostVisible for PageDown() * * @rdesc * count of visible lines */ LONG CDisplayML::GetCliVisible( LONG* pcpMostVisible, //@parm Returns cpMostVisible BOOL fLastCharOfLastVisible) const //@parm Want cp of last visible char { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetCliVisible"); LONG cli = 0; // Initialize count LONG ili = _iliFirstVisible; // Start with 1st visible line LONG dvp = _dvpFirstVisible; LONG cp; const CLine *pli = Elem(ili); for(cp = _cpFirstVisible; dvp < _dvpView && ili < Count(); cli++, ili++, pli++) { dvp += pli->GetHeight(); //Note: I removed the support to give the last visible non-white character. //Does anyone want that? It never worked in LS displays. if (fLastCharOfLastVisible && dvp > _dvpView) break; if(IsInPageView() && cli && pli->_fFirstOnPage) break; cp += pli->_cch; } if(pcpMostVisible) *pcpMostVisible = cp; return cli; } //================================== Inversion (selection) ============================ /* * CDisplayML::InvertRange(cp, cch) * * @mfunc * Invert a given range on screen (for selection) * * @devnote * --- Use when in-place active only --- * * @rdesc * TRUE if success */ BOOL CDisplayML::InvertRange ( LONG cp, //@parm Active end of range to invert LONG cch, //@parm Signed length of range SELDISPLAYACTION selAction) //@parm Describes what we are doing to the selection { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::InvertRange"); LONG cpMost; RECTUV rc, rcClient, rcView; CLinePtr rp(this); CRchTxtPtr rtp(_ped); LONG y; LONG cpActive = _ped->GetSel()->GetCp(); AssertSz(_ped->_fInPlaceActive, "CDisplayML::InvertRange() called when not in-place active"); if(cch < 0) // Define cpMost, set cp = cpMin, { // and cch = |cch| cpMost = cp - cch; cch = -cch; } else { cpMost = cp; cp -= cch; } #ifndef NOLINESERVICES if (g_pols) g_pols->DestroyLine(this); #endif // If an object is being inverted, and nothing else is being inverted, // delegate to the ObjectMgr. If fIgnoreObj is TRUE we highlight normally if (cch == 1 && _ped->GetObjectCount() && (selAction == selSetNormal || selAction == selSetHiLite)) { CObjectMgr* pobjmgr = _ped->GetObjectMgr(); rtp.SetCp(cp); if(rtp.GetChar() == WCH_EMBEDDING) { if(pobjmgr) pobjmgr->HandleSingleSelect(_ped, cp, selAction == selSetHiLite); return TRUE; } } // If display is frozen, just update recalc region and move on. if(_padc) { AssertSz(cp >= 0, "CDisplayML::InvertRange: range (cp) goes below" "zero!!" ); // Make sure these values are bounded. if(cp > _ped->GetTextLength()) // Don't bother updating region; return TRUE; // it's out of bounds if(cp + cch > _ped->GetTextLength()) cch -= cp + cch - _ped->GetTextLength(); _padc->UpdateRecalcRegion(cp, cch, cch); return TRUE; } if(!WaitForRecalcView()) // Ensure all visible lines are return FALSE; // recalc'd _ped->TxGetClientRect(&rcClient); GetViewRect(rcView, &rcClient); // Compute first line to invert and where to start on it if(cp >= _cpFirstVisible) { POINTUV pt; rtp.SetCp(cp); if(PointFromTp(rtp, NULL, FALSE, pt, NULL, TA_TOP) < 0) return FALSE; //We don't use the rp returned from PointFromTp because //we need the outermost rp for best results. In the future //we could consider writing code which doesn't invalidate so much. rp.SetCp(cp, FALSE, 0); rc.top = pt.v; } else { cp = _cpFirstVisible; rp = _iliFirstVisible; rc.top = rcView.top + _dvpFirstVisible; } // Loop on all lines of range while (cp < cpMost && rc.top < rcView.bottom && rp.IsValid()) { // Calculate rc.bottom first because rc.top takes into account // the dy of the first visible on the first loop. y = rc.top; y += rp->GetHeight(); rc.bottom = min(y, rcView.bottom); rc.top = max(rc.top, rcView.top); //If we are inverting the active end of the selection, draw it offscreen //to minimize flicker. if (IN_RANGE(cp - rp.GetIch(), cpActive, cp - rp.GetIch() + rp->_cch) && !IsTransparent() && !rp->_fUseOffscreenDC) { rp->_fOffscreenOnce = rp->_fUseOffscreenDC = TRUE; } cp += rp->_cch - rp.GetIch(); rc.left = rcClient.left; rc.right = rcClient.right; _ped->TxInvalidateRect(&rc); rc.top = rc.bottom; if(!rp.NextRun()) break; } _ped->TxUpdateWindow(); // Make sure window gets repainted return TRUE; } //=================================== Scrolling ============================= /* * CDisplay::VScroll(wCode, vPos) * * @mfunc * Scroll the view vertically in response to a scrollbar event * * @devnote * --- Use when in-place active only --- * * @rdesc * LRESULT formatted for WM_VSCROLL message */ LRESULT CDisplayML::VScroll( WORD wCode, //@parm Scrollbar event code LONG vPos) //@parm Thumb position (vPos 0 for EM_SCROLL behavior) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::VScroll"); LONG cliVisible; LONG dy = 0; BOOL fTracking = FALSE; LONG i; const LONG iliSave = _iliFirstVisible; CLine * pli = NULL; INT dvpSys = GetDvpSystemFont(); LONG vpScroll = _vpScroll; AssertSz(_ped->_fInPlaceActive, "CDisplay::VScroll() called when not in-place"); if(vPos) { // Convert this from 16-bit to 32-bit if necessary. vPos = ConvertScrollToVPos(vPos); } vPos = min(vPos, _dvp); if(IsInPageView()) { BOOL fForward; BOOL fFoundNewPage = FALSE; LONG ili = _iliFirstVisible; LONG nLine = Count(); CLine *pli = Elem(_iliFirstVisible); AssertSz(Elem(_iliFirstVisible)->_fFirstOnPage, "CDisplayML::VScroll: _iliFirstVisible not top of page"); if(wCode <= SB_PAGEDOWN) { fForward = (wCode & 1); ili += fForward; while(ili && ili < nLine) { if(fForward > 0) { vpScroll += pli->GetHeight(); if(ili == nLine - 1) break; pli++; ili++; } else { pli--; ili--; vpScroll -= pli->GetHeight(); } if(pli->_fFirstOnPage) { fFoundNewPage = TRUE; break; } } } else if(wCode == SB_THUMBTRACK || wCode == SB_THUMBPOSITION) { if (vPos + _dvpView >= _dvp) // At last page? vPos = _dvp; if(vPos > vpScroll) { LONG iliFirst = ili; LONG vpScrollPage = vpScroll; if(ili < nLine) { while(vpScroll < vPos) { vpScroll += pli->GetHeight(); // Advance to vPos if(ili == nLine - 1) break; pli++; ili++; if(pli->_fFirstOnPage) { fFoundNewPage = TRUE; vpScrollPage = vpScroll; iliFirst = ili; } } } vpScroll = vpScrollPage; // Move to top of page ili = iliFirst; } else if(vPos < vpScroll) { // Go back to vPos if(!ili) { vpScroll = 0; fFoundNewPage = TRUE; } while(vpScroll > vPos && ili) { pli--; ili--; vpScroll -= pli->GetHeight(); if(pli->_fFirstOnPage) fFoundNewPage = TRUE; } while(!pli->_fFirstOnPage && ili) { pli--; ili--; vpScroll -= pli->GetHeight(); } } AssertSz(Elem(ili)->_fFirstOnPage, "CDisplayML::VScroll: ili not top of page"); } if(!fFoundNewPage) // Nothing to scroll, early exit return MAKELRESULT(0, TRUE); } else { switch(wCode) { case SB_BOTTOM: if(vPos < 0) return FALSE; WaitForRecalc(_ped->GetTextLength(), -1); vpScroll = _dvp; break; case SB_LINEDOWN: cliVisible = GetCliVisible(); if(_iliFirstVisible + cliVisible < Count() && 0 == _dvpFirstVisible) { i = _iliFirstVisible + cliVisible; pli = Elem(i); if(IsInOutlineView()) { // Scan for uncollapsed line for(; pli->_fCollapsed && i < Count(); pli++, i++); } if(i < Count()) dy = pli->GetHeight(); } else if(cliVisible > 1) { pli = Elem(_iliFirstVisible); dy = _dvpFirstVisible; // TODO: scan until find uncollapsed line dy += pli->GetHeight(); } else dy = _dvp - _vpScroll; if(dy >= _dvpView) dy = dvpSys; // Nothing to scroll, early exit if ( !dy ) return MAKELRESULT(0, TRUE); vpScroll += dy; break; case SB_LINEUP: if(_iliFirstVisible > 0) { pli = Elem(_iliFirstVisible - 1); // TODO: scan until find uncollapsed line dy = pli->GetHeight(); } else if(vpScroll > 0) dy = min(vpScroll, dvpSys); if(dy > _dvpView) dy = dvpSys; vpScroll -= dy; break; case SB_PAGEDOWN: cliVisible = GetCliVisible(); vpScroll += _dvpView; if(vpScroll < _dvp && cliVisible > 0) { // TODO: Scan until find uncollapsed line dy = Elem(_iliFirstVisible + cliVisible - 1)->GetHeight(); if(dy >= _dvpView) dy = dvpSys; else if(dy > _dvpView - dy) { // Go at least a line if line is very big dy = _dvpView - dy; } vpScroll -= dy; } break; case SB_PAGEUP: cliVisible = GetCliVisible(); vpScroll -= _dvpView; if (vpScroll < 0) { // Scroll position can't be negative and we don't // need to back up to be sure we display a full line. vpScroll = 0; } else if(cliVisible > 0) { // TODO: Scan until find uncollapsed line dy = Elem(_iliFirstVisible)->GetHeight(); if(dy >= _dvpView) dy = dvpSys; else if(dy > _dvpView - dy) { // Go at least a line if line is very big dy = _dvpView - dy; } vpScroll += dy; } break; case SB_THUMBTRACK: case SB_THUMBPOSITION: if(vPos < 0) return FALSE; vpScroll = vPos; fTracking = TRUE; break; case SB_TOP: if(vPos < 0) return FALSE; vpScroll = 0; break; case SB_ENDSCROLL: UpdateScrollBar(SB_VERT); return MAKELRESULT(0, TRUE); default: return FALSE; } } BOOL fFractional = wCode != SB_PAGEDOWN && wCode != SB_PAGEUP; LONG vpLimit = _dvp; if(!IsInPageView() && fFractional) vpLimit = max(_dvp - _dvpView, 0); vpScroll = min(vpScroll, vpLimit); ScrollView(_upScroll, vpScroll, fTracking, fFractional); // Force position update if we just finished a track if(wCode == SB_THUMBPOSITION) UpdateScrollBar(SB_VERT); // Return how many lines we scrolled return MAKELRESULT((WORD) (_iliFirstVisible - iliSave), TRUE); } /* * CDisplay::LineScroll(cli, cch) * * @mfunc * Scroll view vertically in response to a scrollbar event */ void CDisplayML::LineScroll( LONG cli, //@parm Count of lines to scroll vertically LONG cch) //@parm Count of characters to scroll horizontally { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::LineScroll"); //Make sure the line to scroll to is valid if (cli + _iliFirstVisible >= Count()) { // change line count enough to display the last line cli = Count() - _iliFirstVisible; } // Get the absolute vpScroll position by adding the difference of the line // we want to go to and the current _vpScroll position LONG dvpScroll = CalcVLineScrollDelta(cli, FALSE); if(dvpScroll < 0 || _dvp - (_vpScroll + dvpScroll) > _dvpView - dvpScroll) ScrollView(_upScroll, _vpScroll + dvpScroll, FALSE, FALSE); } /* * CDisplayML::FractionalScrollView (vDelta) * * @mfunc * Allow view to be scrolled by fractional lines. */ void CDisplayML::FractionalScrollView ( LONG vDelta ) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::FractionalScrollView"); if ( vDelta) ScrollView(_upScroll, min(vDelta + _vpScroll, max(_dvp - _dvpView, 0)), FALSE, TRUE); } /* * CDisplayML::ScrollToLineStart(iDirection) * * @mfunc * If the view is scrolled so that only a partial line is at the * top, then scroll the view so that the entire view is at the top. */ void CDisplayML::ScrollToLineStart( LONG iDirection) //@parm the direction in which to scroll (negative // means down the screen { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::ScrollToLineStart"); // This code originally lined things up on a line. However, it doesn't work // very well with big objects especially at the end of the document. I am // leaving the call here in case we discover problems later. (a-rsail). #if 0 // If _dvpFirstVisible is zero, then we're aligned on a line, so // nothing more to do. if(_dvpFirstVisible) { LONG vpScroll = _vpScroll + _dvpFirstVisible; if(iDirection <= 0) { vpScroll += Elem(_iliFirstVisible)->_dvp; } ScrollView(_upScroll, vpScroll, FALSE, TRUE); } #endif // 0 } /* * CDisplayML::CalcVLineScrollDelta (cli, fFractionalFirst) * * @mfunc * Given a count of lines, positive or negative, calc the number * of vertical units necessary to scroll the view to the start of * the current line + the given count of lines. */ LONG CDisplayML::CalcVLineScrollDelta ( LONG cli, BOOL fFractionalFirst ) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CalcVLineScrollDelta"); LONG vpScroll = 0; if(fFractionalFirst && _dvpFirstVisible) // Scroll partial for 1st. { Assert(_dvpFirstVisible <= 0); // get jonmat if(cli < 0) { cli++; vpScroll = _dvpFirstVisible; } else { cli--; vpScroll = Elem(_iliFirstVisible)->GetHeight() + _dvpFirstVisible; } } if(cli > 0) { // Scrolling down cli = min(cli, Count() - _iliFirstVisible - 1); if (!fFractionalFirst && (0 == cli)) { // If we are scrolling down and on the last line but we haven't scrolled to // the very bottom, then do so now. AssertSz(0 == vpScroll, "CDisplayML::CalcVLineScrollDelta last line & scroll"); vpScroll = _dvp - _vpScroll; // Limit scroll length to approximately 3 lines. vpScroll = min(vpScroll, 3 * GetDvpSystemFont()); } } else if(cli < 0) { // Scrolling up cli = max(cli, -_iliFirstVisible); // At the top. if (!fFractionalFirst && (0 == cli)) { // Make sure that we scroll back so first visible is 0. vpScroll = _dvpFirstVisible; // Limit scroll length to approximately 3 lines. vpScroll = max(vpScroll, -3 * GetDvpSystemFont()); } } if(cli) vpScroll += VposFromLine(this, _iliFirstVisible + cli) - VposFromLine(this, _iliFirstVisible); return vpScroll; } /* * CDisplayML::ScrollView(upScroll, vpScroll, fTracking, fFractionalScroll) * * @mfunc * Scroll view to new x and y position * * @devnote * This method tries to adjust the y scroll pos before * scrolling to display complete line at top. x scroll * pos is adjusted to avoid scrolling all text off the * view rectangle. * * Must be able to handle vpScroll pdp->dvp and vpScroll 0 * * @rdesc * TRUE if actual scrolling occurred, * FALSE if no change */ BOOL CDisplayML::ScrollView ( LONG upScroll, //@parm New x scroll position LONG vpScroll, //@parm New y scroll position BOOL fTracking, //@parm TRUE indicates we are tracking scrollbar thumb BOOL fFractionalScroll) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::ScrollView"); BOOL fTryAgain = TRUE; LONG dupMax; LONG dup = 0; LONG dvp = 0; RECTUV rcClient, rcClip; CTxtSelection *psel = _ped->GetSelNC(); COleObject *pipo; BOOL fRestoreCaret = FALSE; LONG iliFirstVisible = _iliFirstVisible; AssertSz(_ped->_fInPlaceActive, "CDisplayML::ScrollView() called when not in-place"); //For scrolling purposes, we clip to rcView's top and bottom, but rcClient's left and right _ped->TxGetClientRect(&rcClient); GetViewRect(rcClip, &rcClient); rcClip.left = rcClient.left; rcClip.right = rcClient.right; if(upScroll == -1) upScroll = _upScroll; if(vpScroll == -1) vpScroll = _vpScroll; // Determine vertical scrolling pos while(1) { BOOL fNothingBig = TRUE; LONG vFirst; LONG dvFirst; LONG cpFirst; LONG iliFirst; LONG vpHeight; LONG iliT; vpScroll = min(vpScroll, GetMaxVpScroll()); vpScroll = max(0, vpScroll); dvp = 0; // Ensure all visible lines are recalced if(!WaitForRecalcView()) return FALSE; // Compute new first visible line iliFirst = LineFromVpos(this, vpScroll, &vFirst, &cpFirst); if(IsInPageView()) { //REVIEW (keithcu) EBOOKS bug 424. Does this need to be here, or //should the logic be somewhere else? Also, would it be better to round //rather than to always round down? CLine *pli = Elem(iliFirst); for(; !pli->_fFirstOnPage && iliFirst; iliFirst--) { pli--; // Back up to previous line vFirst -= pli->GetHeight(); vpScroll -= pli->GetHeight(); cpFirst -= pli->_cch; } } if( cpFirst < 0 ) { // FUTURE (alexgo) this is pretty bogus, we should try to do // better in the next rel. TRACEERRORSZ("Display calc hosed, trying again"); InitVars(); _fNeedRecalc = TRUE; return FALSE; } if(iliFirst < 0) { // No line at _vpScroll, use last line instead iliFirst = max(0, Count() - 1); cpFirst = _ped->GetTextLength() - Elem(iliFirst)->_cch; vpScroll = _dvp - Elem(iliFirst)->GetHeight(); vFirst = _vpScroll; } if(IsInPageView()) { AssertSz(Elem(iliFirst)->_fFirstOnPage, "CDisplayML::ScrollView: _iliFirstVisible not top of page"); if(vpScroll > vFirst) // Tried to scroll beyond start vpScroll = vFirst; // of last line goto scrollit; } dvFirst = vFirst - vpScroll; // Figure whether there is a big line // (more that a third of the view rect) for(iliT = iliFirst, vpHeight = dvFirst; vpHeight < _dvpView && iliT < Count(); iliT++) { const CLine *pli = Elem(iliT); if(pli->GetHeight() >= _dvpView / 3) fNothingBig = FALSE; vpHeight += pli->GetHeight(); } // If no big line and first pass, try to adjust // scrolling pos to show complete line at top if(!fFractionalScroll && fTryAgain && fNothingBig && dvFirst != 0) { fTryAgain = FALSE; // prevent any infinite loop Assert(dvFirst < 0); Tracef(TRCSEVINFO, "adjusting scroll for partial line at %d", dvFirst); // partial line visible at top, try to get a complete line showing vpScroll += dvFirst; LONG dvpLine = Elem(iliFirst)->GetHeight(); // Adjust the height of the scroll by the height of the first // visible line if we are scrolling down or if we are using the // thumb (tracking) and we are on the last page of the view. if ((fTracking && vpScroll + _dvpView + dvpLine > _dvp) || (!fTracking && _vpScroll <= vpScroll)) { // Scrolling down so move down a little more vpScroll += dvpLine; } } else { dvp = 0; if(vpScroll != _vpScroll) { _dvpFirstVisible = dvFirst; scrollit: _iliFirstVisible = iliFirst; _cpFirstVisible = cpFirst; dvp = _vpScroll - vpScroll; _vpScroll = vpScroll; AssertSz(_vpScroll >= 0, "CDisplayML::ScrollView _vpScroll < 0"); AssertNr(VerifyFirstVisible()); if(!WaitForRecalcView()) return FALSE; } break; } } CheckView(); // Determine horizontal scrolling pos. dupMax = _dupLineMax; // REVIEW (Victork) Restricting the range of the scroll is not really needed and could even be bad (bug 6104) upScroll = min(upScroll, dupMax); upScroll = max(0, upScroll); dup = _upScroll - upScroll; if(dup) _upScroll = upScroll; // Now perform the actual scrolling if(IsMain() && (dvp || dup)) { // Scroll only if scrolling < view dimensions and we are in-place if(IsActive() && !IsTransparent() && dvp < _dvpView && dup < _dupView && !IsInPageView()) { // FUTURE: (ricksa/alexgo): we may be able to get rid of // some of these ShowCaret calls; they look bogus. if (psel && psel->IsCaretShown()) { _ped->TxShowCaret(FALSE); fRestoreCaret = TRUE; } LONG dxp, dyp; GetDxpDypFromDupDvp(dup, dvp, GetTflow(), dxp, dyp); RECT rcxyClip; RectFromRectuv(rcxyClip, rcClip); _ped->TxScrollWindowEx(dxp, dyp, NULL, &rcxyClip); if(fRestoreCaret) _ped->TxShowCaret(FALSE); } else _ped->TxInvalidateRect(&rcClip); if(psel) psel->UpdateCaret(FALSE); if(!fTracking && dvp) { UpdateScrollBar(SB_VERT); _ped->SendScrollEvent(EN_VSCROLL); } if(!fTracking && dup) { UpdateScrollBar(SB_HORZ); _ped->SendScrollEvent(EN_HSCROLL); } // FUTURE: since we're now repositioning in place active // objects every time we draw, this call seems to be // superfluous (AndreiB) // Tell object subsystem to reposition any in place objects if(_ped->GetObjectCount()) { pipo = _ped->GetObjectMgr()->GetInPlaceActiveObject(); if(pipo) pipo->OnReposition(); } } bool fNotifyPageChange(false); if(IsInPageView() && iliFirstVisible != _iliFirstVisible) { CalculatePage(iliFirstVisible); fNotifyPageChange = true; } // Update the View after state has been updated if(IsMain() && (dvp || dup)) _ped->TxUpdateWindow(); if(fNotifyPageChange) GetPed()->TxNotify(EN_PAGECHANGE, NULL); return dvp || dup; } /* * CDisplayML::GetScrollRange(nBar) * * @mfunc * Returns the max part of a scrollbar range for scrollbar

* * @rdesc * LONG max part of scrollbar range */ LONG CDisplayML::GetScrollRange( INT nBar) const //@parm Scroll bar to interrogate (SB_VERT or SB_HORZ) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetScrollRange"); Assert( IsMain() ); LONG lRange = 0; if(nBar == SB_VERT && _fVScrollEnabled) { if(_ped->TxGetScrollBars() & WS_VSCROLL) lRange = GetMaxVpScroll(); } else if((_ped->TxGetScrollBars() & WS_HSCROLL) && _fUScrollEnabled) { // Scroll range is maximum width. lRange = max(0, _dupLineMax + _ped->GetCaretWidth()); } // Since thumb messages are limited to 16-bit, limit range to 16-bit lRange = min(lRange, _UI16_MAX); return lRange; } /* * CDisplayML::UpdateScrollBar(nBar, fUpdateRange) * * @mfunc * Update either the horizontal or the vertical scrollbar and * figure whether the scrollbar should be visible or not. * * @rdesc * BOOL */ BOOL CDisplayML::UpdateScrollBar( INT nBar, //@parm Which scroll bar : SB_HORZ, SB_VERT BOOL fUpdateRange) //@parm Should the range be recomputed and updated { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::UpdateScrollBar"); // Note: In the old days we didn't allow autosize & scroll bars, so to keep // forms working, we need this special logic with respect to autosize. if (!IsActive() || _fInRecalcScrollBars || !_ped->fInOurHost() && _ped->TxGetAutoSize()) { // No scroll bars unless we are inplace active and we are not in the // process of updating scroll bars already. return TRUE; } const DWORD dwScrollBars = _ped->TxGetScrollBars(); const BOOL fHide = !(dwScrollBars & ES_DISABLENOSCROLL); BOOL fReturn = FALSE; BOOL fEnabled = TRUE; BOOL fEnabledOld; LONG lScroll; CTxtSelection *psel = _ped->GetSelNC(); BOOL fShowCaret = FALSE; // Get scrolling position if(nBar == SB_VERT) { if(!(dwScrollBars & WS_VSCROLL)) return FALSE; fEnabledOld = _fVScrollEnabled; if(GetMaxVpScroll() <= _dvpView) fEnabled = FALSE; } else { if(!(dwScrollBars & WS_HSCROLL)) { // Even if we don't have scrollbars, we may allow horizontal // scrolling. if(!_fUScrollEnabled && _dupLineMax > _dupView) _fUScrollEnabled = !!(dwScrollBars & ES_AUTOHSCROLL); return FALSE; } fEnabledOld = _fUScrollEnabled; if(_dupLineMax <= _dupView) fEnabled = FALSE; } // Don't allow ourselves to be re-entered. // Be sure to turn this to FALSE on exit _fInRecalcScrollBars = TRUE; // !s beforehand because all true values aren't necessarily equal if(!fEnabled != !fEnabledOld) { if(_fDeferUpdateScrollBar) _fUpdateScrollBarDeferred = TRUE; else { if (nBar == SB_HORZ) _fUScrollEnabled = fEnabled; else _fVScrollEnabled = fEnabled; } if(!_fDeferUpdateScrollBar) { if(!fHide) { // Don't hide scrollbar, just disable _ped->TxEnableScrollBar(nBar, fEnabled ? ESB_ENABLE_BOTH : ESB_DISABLE_BOTH); if (!fEnabled) { // The scroll bar is disabled. Therefore, all the text fits // on the screen so make sure the drawing reflects this. _vpScroll = 0; _dvpFirstVisible = 0; _cpFirstVisible = 0; _iliFirstVisible = 0; _sPage = 0; _ped->TxInvalidate(); } } else { fReturn = TRUE; // Make sure to hide caret before showing scrollbar if(psel) fShowCaret = psel->ShowCaret(FALSE); // Hide or show scroll bar _ped->TxShowScrollBar(nBar, fEnabled); // The scroll bar affects the window which in turn affects the // display. Therefore, if word wrap, repaint _ped->TxInvalidate(); // Needed for bug fix #5521 _ped->TxUpdateWindow(); if(fShowCaret) psel->ShowCaret(TRUE); } } } // Set scrollbar range and thumb position if(fEnabled) { if(fUpdateRange && !_fDeferUpdateScrollBar) _ped->TxSetScrollRange(nBar, 0, GetScrollRange(nBar), FALSE); if(_fDeferUpdateScrollBar) _fUpdateScrollBarDeferred = TRUE; else { lScroll = (nBar == SB_VERT) ? ConvertVPosToScrollPos(_vpScroll) : ConvertUPosToScrollPos(_upScroll); _ped->TxSetScrollPos(nBar, lScroll, TRUE); } } _fInRecalcScrollBars = FALSE; return fReturn; } /* * CDisplayML::GetNaturalSize(hdcDraw, hicTarget, dwMode, pwidth, pheight) * * @mfunc * Recalculate display to input width & height for TXTNS_FITTOCONTENT[2]. * * @rdesc * S_OK - Call completed successfully */ HRESULT CDisplayML::GetNaturalSize( HDC hdcDraw, //@parm DC for drawing HDC hicTarget, //@parm DC for information DWORD dwMode, //@parm Type of natural size required LONG *pwidth, //@parm Width in device units to use for fitting LONG *pheight) //@parm Height in device units to use for fitting { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetNaturalSize"); HRESULT hr = S_OK; // Set the height temporarily so the zoom factor will work out LONG yOrigHeightClient = SetClientHeight(*pheight); // Adjust height and width by view inset LONG widthView = *pwidth; LONG heightView = *pheight; GetViewDim(widthView, heightView); // Store adjustment so we can restore it to height & width LONG widthAdj = *pwidth - widthView; LONG heightAdj = *pheight - heightView; // Init measurer at cp = 0 CMeasurer me(this); CLine liNew; LONG xWidth = 0, lineWidth; LONG dvp = 0; LONG cchText = _ped->GetTextLength(); BOOL fFirstInPara = TRUE; LONG dulMax = GetWordWrap() ? DXtoLX(widthView) : duMax; // The following loop generates new lines do { // Stuff text into new line UINT uiFlags = 0; // If word wrap is turned on, then we want to break on // words, otherwise, measure white space, etc. if(GetWordWrap()) uiFlags = MEASURE_BREAKATWORD; if(fFirstInPara) uiFlags |= MEASURE_FIRSTINPARA; me.SetDulLayout(dulMax); if(!Measure(me, &liNew, 0, uiFlags)) { hr = E_FAIL; goto exit; } fFirstInPara = liNew._fHasEOP; // Keep track of width of widest line lineWidth = liNew._dup; if(dwMode == TXTNS_FITTOCONTENT2) lineWidth += liNew._upStart + me.GetRightIndent(); xWidth = max(xWidth, lineWidth); dvp += liNew.GetHeight(); // Bump height } while (me.GetCp() < cchText); // Add caret size to width to guarantee that text fits. We don't // want to word break because the caret won't fit when the caller // tries a window this size. xWidth += _ped->GetCaretWidth(); *pwidth = xWidth; *pheight = dvp; // Restore insets so output reflects true client rect needed *pwidth += widthAdj; *pheight += heightAdj; exit: SetClientHeight(yOrigHeightClient); return hr; } /* * CDisplayML::Clone() * * @mfunc * Make a copy of this object * * @rdesc * NULL - failed * CDisplay * * * @devnote * Caller of this routine is the owner of the new display object. */ CDisplay *CDisplayML::Clone() const { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::Clone"); CDisplayML *pdp = new CDisplayML(_ped); if(pdp) { // Initialize our base class if(pdp->CDisplay::Init()) { pdp->InitFromDisplay(this); pdp->_upScroll = _upScroll; pdp->_fVScrollEnabled = _fVScrollEnabled; pdp->_fUScrollEnabled = _fUScrollEnabled; pdp->_fWordWrap = _fWordWrap; pdp->_cpFirstVisible = _cpFirstVisible; pdp->_iliFirstVisible = _iliFirstVisible; pdp->_vpScroll = _vpScroll; pdp->ResetDrawInfo(this); if(_pddTarget) { // Create a duplicate target device for this object pdp->SetMainTargetDC(_pddTarget->GetDC(), _dulTarget); } // This can't be the active view since it is a clone // of some view. pdp->SetActiveFlag(FALSE); } } return pdp; } void CDisplayML::DeferUpdateScrollBar() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::DeferUpdateScrollBar"); _fDeferUpdateScrollBar = TRUE; _fUpdateScrollBarDeferred = FALSE; } BOOL CDisplayML::DoDeferredUpdateScrollBar() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::DoDeferredUpdateScrollBar"); _fDeferUpdateScrollBar = FALSE; if(!_fUpdateScrollBarDeferred) return FALSE; _fUpdateScrollBarDeferred = FALSE; BOOL fHorizontalUpdated = UpdateScrollBar(SB_HORZ, TRUE); return UpdateScrollBar(SB_VERT, TRUE) || fHorizontalUpdated; } /* * CDisplayML::GetMaxUScroll() * * @mfunc * Get the maximum x scroll value * * @rdesc * Maximum x scroll value * */ LONG CDisplayML::GetMaxUScroll() const { return _dupLineMax + _ped->GetCaretWidth(); } /* * CDisplayML::CreateEmptyLine() * * @mfunc * Create an empty line * * @rdesc * TRUE - iff successful */ BOOL CDisplayML::CreateEmptyLine() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CreateEmptyLine"); // Make sure that this is being called appropriately AssertSz(_ped->GetTextLength() == 0, "CDisplayML::CreateEmptyLine called inappropriately"); CMeasurer me(this); // Create a measurer CLine * pliNew = Add(1, NULL); // Add one new line if(!pliNew) { _ped->GetCallMgr()->SetOutOfMemory(); TRACEWARNSZ("CDisplayML::CreateEmptyLine unable to add CLine to CLineArray"); return FALSE; } // Measure the empty line me.SetDulLayout(1); if(!pliNew->Measure(me, MEASURE_BREAKATWORD | MEASURE_FIRSTINPARA)) { Assert(FALSE); return FALSE; } return TRUE; } /* * CDisplayML::AdjustToDisplayLastLine() * * @mfunc * Calculate the vpScroll necessary to get the last line to display * * @rdesc * Updated vpScroll * */ LONG CDisplayML::AdjustToDisplayLastLine( LONG yBase, //@parm actual vpScroll to display LONG vpScroll) //@parm proposed amount to scroll { LONG iliFirst; LONG vFirst; if(yBase >= _dvp) { // Want last line to be entirely displayed. // Compute new first visible line iliFirst = LineFromVpos(this, vpScroll, &vFirst, NULL); // Is top line partial? if(vpScroll != vFirst) { // Yes - bump scroll to the next line so the ScrollView // won't bump the scroll back to display the entire // partial line since we want the bottom to display. vpScroll = VposFromLine(this, iliFirst + 1); } } return vpScroll; } /* * CDisplayML::GetResizeHeight() * * @mfunc * Calculates height to return for a request resize * * @rdesc * Updated vpScroll */ LONG CDisplayML::GetResizeHeight() const { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetResizeHeight"); return CalcScrollHeight(_dvp); } /* * CDisplayML::RebindFirstVisible(fResetCp) * * @mfunc * rebind the first visible line * */ void CDisplayML::RebindFirstVisible( BOOL fResetCp) //@parm If TRUE, reset cp to 0 { LONG cp = fResetCp ? 0 : _cpFirstVisible; // Change first visible entries because CLinePtr::SetCp() and // YPosFromLine() use them, but they're not valid _dvpFirstVisible = 0; _cpFirstVisible = 0; _iliFirstVisible = 0; _vpScroll = 0; // Recompute scrolling position and first visible values after edit Set_yScroll(cp); } /* * CDisplayML::Set_yScroll(cp) * * @mfunc * Set _yScroll corresponding to cp, making sure that in PageView * the display starts at the top of a page */ void CDisplayML::Set_yScroll( LONG cp) //@parm cp at which to set valid _yScroll { // Recompute scrolling position and first visible values after edit CLinePtr rp(this); if(!rp.SetCp(cp, FALSE)) // Couldn't get to cp, so find out cp = rp.CalculateCp(); // cp we got to _vpScroll = VposFromLine(this, rp); _cpFirstVisible = cp - rp.GetIch(); _iliFirstVisible = rp; Sync_yScroll(); // Make sure _yScroll, _sPage, etc., } // are valid in PageView /* * CDisplayML::Sync_yScroll() * * @mfunc * Make sure that in PageView the display starts at the top of a page. * Notify client if page number changes */ void CDisplayML::Sync_yScroll() { if(IsInPageView()) // _yScroll must be to line at { // top of page CLine *pli = Elem(_iliFirstVisible); for(; !pli->_fFirstOnPage && _iliFirstVisible; _iliFirstVisible--) { pli--; // Back up to previous line _vpScroll -= pli->GetHeight(); _cpFirstVisible -= pli->_cch; } LONG sPage = _sPage; if(sPage != CalculatePage(0)) { _ped->TxInvalidate(); _ped->TxNotify(EN_PAGECHANGE, NULL); } } } /* * CDisplayML::Paginate(ili, fRebindFirstVisible) * * @mfunc * Recompute page breaks from ili on * * @rdesc * TRUE if success */ BOOL CDisplayML::Paginate ( LONG ili, //@parm Line to redo pagination from BOOL fRebindFirstVisible) //@parm If TRUE, call RebindFirstVisible() { LONG cLine = Count(); if(!IsInPageView() || ili >= cLine || ili < 0) return FALSE; LONG iliSave = ili; CLine * pli = Elem(ili); // Synchronize to top of current page for(; ili && !pli->_fFirstOnPage; pli--, ili--) ; // Guard against widow-orphan changes by backing up an extra page if(ili && iliSave - ili < 2) { for(pli--, ili--; ili && !pli->_fFirstOnPage; pli--, ili--) ; } LONG cLinePage = 1; // One line on new page LONG dvpHeight = pli->GetHeight(); // Height on new page pli->_fFirstOnPage = TRUE; // First line on page ili++; // One less line to consider pli++; // Advance to next line for(; ili < cLine; ili++, pli++) // Process all lines from ili to EOD { dvpHeight += pli->GetHeight(); // Add in current line height cLinePage++; // One more line on page (maybe) pli->_fFirstOnPage = FALSE; CLine *pliPrev = pli - 1; // Point at previous line if(dvpHeight > _dvpView || pliPrev->_fHasFF || pli->_fPageBreakBefore) { cLinePage--; if(cLinePage > 1 && !pliPrev->_fHasFF) // && fWidowOrphanControl) { if(pli->_fHasFF && pli->_cch == 1) // FF line height causing continue; // eject, so leave it on current page //If we are in the middle of a wrapped object, bump it to next page //We do not do widow/orphan if it could divide wrapped objects between pages if (_ped->IsRich()) { if (pli->_cObjectWrapLeft || pli->_cObjectWrapRight) { CLine *pliOrig = pli; if (pli->_cObjectWrapLeft && !pli->_fFirstWrapLeft) { while (!pli->_fFirstWrapLeft) pli--; } int cLineBack = pliOrig - pli; pli = pliOrig; if (pli->_cObjectWrapRight && !pli->_fFirstWrapRight) { while (!pli->_fFirstWrapRight) pli--; } cLineBack = max(cLineBack, (int)(pliOrig - pli)); pli = pliOrig; if (cLineBack < cLinePage) //Don't do this if object is larger than page { cLinePage -= cLineBack; pliPrev -= cLineBack; pli -= cLineBack; ili -= cLineBack; } } // If this line and the previous one are in the same para, // we might need widow/orphan logic if (!pli->_fFirstInPara && !pliPrev->_cObjectWrapLeft && !pliPrev->_cObjectWrapRight && (cLinePage > 1) ) { // If this line ends in an EOP bump both to following page // (widow/orphan), but only if either the line is short, or // we absolutely know that there will only be one line on // the page. Do the same if prev line ends in a hyphenated // word, and the preceding line does not if (pli->_cchEOP && (pli->_dup < _dupView/2 || ili >= cLine - 1) || // Do we need -2 instead of -1? pliPrev->_ihyph && ili > 1 && !pliPrev->_fFirstOnPage && !pliPrev[-1]._ihyph) { cLinePage--; // Point to previous line pliPrev--; pli--; ili--; } } // Don't end a page with a heading. if(cLinePage > 1 && pliPrev->_nHeading && !pliPrev->_cObjectWrapLeft && !pliPrev->_cObjectWrapRight) { cLinePage--; pliPrev--; pli--; ili--; } } } pli->_fFirstOnPage = TRUE; // Define first line on page cLinePage = 1; // One line on new page dvpHeight = pli->GetHeight(); // Current height of new page } } if(fRebindFirstVisible) RebindFirstVisible(); return TRUE; } /* * CDisplayML::CalculatePage(iliFirst) * * @mfunc * Compute page number for _iliFirstVisible starting with iliFirst * * @rdesc * Page number calculated */ LONG CDisplayML::CalculatePage ( LONG iliFirst) { if(Count() < 2 || !IsInPageView()) { _sPage = 0; return 0; } if(iliFirst < 1) _sPage = 0; Assert(iliFirst >= 0 && iliFirst < Count()); LONG iDir = 1; LONG n = _iliFirstVisible - iliFirst; CLine *pli = Elem(iliFirst); // Point at next/previous line if(n < 0) { n = -n; iDir = -1; } else pli++; for(; n--; pli += iDir) if(pli->_fFirstOnPage) { _sPage += iDir; if(_sPage < 0) _sPage = 0; } AssertSz(Elem(_iliFirstVisible)->_fFirstOnPage, "CDisplayML::CalculatePage: _iliFirstVisible not top of page"); return _sPage; } /* * CDisplayML::GetPage(piPage, dwFlags, pcrg) * * @mfunc * Get page number for _iliFirstVisible * * @rdesc * HRESULT = !piPage ? E_INVALIDARG : * IsInPageView() ? NOERROR : E_FAIL */ HRESULT CDisplayML::GetPage( LONG *piPage, //@parm Out parm for page number DWORD dwFlags, //@parm Flags for which page to use CHARRANGE *pcrg) //@parm Out parm for CHARRANGE for page { if(!piPage) return E_INVALIDARG; *piPage = 0; if(dwFlags) // No flags defined yet return E_INVALIDARG; if(!IsInPageView()) return E_FAIL; #ifdef DEBUG if(_sPage < 20) { LONG sPage = _sPage; CalculatePage(0); AssertSz(sPage == _sPage, "CDisplayML::GetPage: invalid cached page number"); } #endif *piPage = _sPage; if(pcrg) { pcrg->cpMin = _cpFirstVisible; GetCliVisible(&pcrg->cpMost, TRUE); } return NOERROR; } /* * CDisplayML::SetPage(iPage) * * @mfunc * Go to page iPage * * @rdesc * HRESULT */ HRESULT CDisplayML::SetPage ( LONG iPage) { if(!IsInPageView()) return E_FAIL; LONG nLine = Count(); if(iPage < 0 || !nLine) return E_INVALIDARG; CLine *pli = Elem(0); // Scroll from page 0 LONG vpScroll = 0; LONG vpScrollLast = 0; nLine--; // Decrement nLine so pli++ below will always be valid for(LONG ili = 0; ili < nLine && iPage; ili++) { vpScroll += pli->GetHeight(); pli++; if(pli->_fFirstOnPage) // Start of new page { vpScrollLast = vpScroll; iPage--; // One less to go } } if(!_iliFirstVisible) // This shouldn't be necessary... _vpScroll = 0; ScrollView(_upScroll, vpScrollLast, FALSE, FALSE); return NOERROR; } /* * CDisplayML::GetCurrentPageHeight() * * @mfunc * Return page height of current page in PageView mode * * @rdesc * page height of current page in PageView mode; else 0; */ LONG CDisplayML::GetCurrentPageHeight() const { if(!IsInPageView()) return 0; LONG cLine = Count(); LONG dvp = 0; LONG i = _iliFirstVisible; CLine * pli = Elem(i); do { dvp += pli->GetHeight(); // Add in first line's height in any pli++; // case i++; } while(i < cLine && !pli->_fFirstOnPage); return dvp; } // ================================ DEBUG methods ============================================ #ifdef DEBUG /* * CDisplayML::CheckLineArray() * * @mfunc * DEBUG routine that Asserts unless: * 1) sum of all line counts equals count of characters in story * 2) sum of all line heights equals height of display galley */ void CDisplayML::CheckLineArray() const { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CheckLineArray"); LONG ili = Count(); // If we are marked as needing a recalc or if we are in the process of a // background recalc, we cannot verify the line array if(!_fRecalcDone || _fNeedRecalc || !ili) return; LONG cchText = _ped->GetTextLength(); if (!cchText) return; LONG cp = 0; BOOL fFirstInPara; BOOL fPrevLineEOP = TRUE; LONG dvp = 0; CLine const *pli = Elem(0); CTxtPtr tp(_ped, 0); while(ili--) { fFirstInPara = pli->_fFirstInPara; if(fPrevLineEOP ^ fFirstInPara) { tp.SetCp(cp); AssertSz(fFirstInPara && IsASCIIEOP(tp.GetPrevChar()), "CDisplayML::CheckLineArray: Invalid first/prev flags"); } AssertSz(pli->_cch, "CDisplayML::CheckLineArray: cch == 0"); dvp += pli->GetHeight(); cp += pli->_cch; fPrevLineEOP = pli->_fHasEOP; pli++; } if((cp != cchText) && (cp != _cpCalcMax)) { Tracef(TRCSEVINFO, "sigma (*this)[]._cch = %ld, cchText = %ld", cp, cchText); AssertSz(FALSE, "CDisplayML::CheckLineArray: sigma(*this)[]._cch != cchText"); } if(dvp != _dvp) { Tracef(TRCSEVINFO, "sigma (*this)[]._dvp = %ld, _dvp = %ld", dvp, _dvp); AssertSz(FALSE, "CDisplayML::CheckLineArray: sigma(*this)[]._dvp != _dvp"); } } void CDisplayML::DumpLines( LONG iliFirst, LONG cli) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::DumpLines"); LONG cch; LONG ili; TCHAR rgch[512]; if(Count() == 1) wcscpy(rgch, TEXT("1 line")); else wsprintf(rgch, TEXT("%d lines"), Count()); #ifdef UNICODE // TraceTag needs to take UNICODE... #else TRACEINFOSZ(TRCSEVINFO, rgch); #endif if(cli < 0) cli = Count(); else cli = min(cli, Count()); if(iliFirst < 0) iliFirst = Count() - cli; else cli = min(cli, Count() - iliFirst); for(ili = iliFirst; cli > 0; ili++, cli--) { const CLine * const pli = Elem(ili); wsprintf(rgch, TEXT("Line %d (%ldc%ldw%ldh): \""), ili, pli->_cch, pli->_dup, pli->GetHeight()); cch = wcslen(rgch); cch += GetLineText(ili, rgch + cch, CchOfCb(sizeof(rgch)) - cch - 4); rgch[cch++] = TEXT('\"'); rgch[cch] = TEXT('\0'); #ifdef UNICODE // TraceTag needs to take UNICODE... #else TRACEINFOSZ(TRCSEVINFO, rgch); #endif } } /* * CDisplayML::CheckView() * * @mfunc * DEBUG routine that checks coherence between _iliFirstVisible, * _cpFirstVisible, and _dvpFirstVisible */ void CDisplayML::CheckView() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CheckView"); LONG dvp; VerifyFirstVisible(&dvp); if(dvp != _vpScroll + _dvpFirstVisible && !IsInPageView()) { Tracef(TRCSEVINFO, "sigma CLine._dvp = %ld, CDisplay.vFirstLine = %ld", dvp, _vpScroll + _dvpFirstVisible); AssertSz(FALSE, "CLine._dvp != VIEW.vFirstLine"); } } /* * CDisplayML::VerifyFirstVisible(pHeight) * * @mfunc * DEBUG routine that checks coherence between _iliFirstVisible * and _cpFirstVisible * * @rdesc TRUE if things are hunky dory; FALSE otherwise */ BOOL CDisplayML::VerifyFirstVisible( LONG *pHeight) { LONG cchSum; LONG ili = _iliFirstVisible; CLine const *pli = Elem(0); LONG dvp; for(cchSum = dvp = 0; ili--; pli++) { cchSum += pli->_cch; dvp += pli->GetHeight(); } if(pHeight) *pHeight = dvp; if(cchSum != _cpFirstVisible) { Tracef(TRCSEVINFO, "sigma CLine._cch = %ld, CDisplay.cpFirstVisible = %ld", cchSum, _cpMin); AssertSz(FALSE, "sigma CLine._cch != VIEW.cpMin"); return FALSE; } return TRUE; } #endif // DEBUG