|
|
/*
* @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:<nl> * 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-1998, 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;
// If we need to calc at least this many characters, then put up a wait
// cursor. NB! 4096 is not a measured number; it just seemed like a good
// one.
#define NUMCHARFORWAITCURSOR 4096
#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 yHeight) 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 *lp = Elem(Count() - 1); // Get last line in array
if(lp->_cchEOP) yHeight += lp->GetHeight(); } return yHeight; }
/*
* CDisplayML::GetMaxYScroll() * * @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::GetMaxYScroll() 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)
yScroll = Elem(max(0, Count() - 1))->_yHeight;
if(yScroll > _yHeightView) yScroll = _yHeightView;
yScroll = _yHeight - yScroll; } #endif //0
return CalcScrollHeight(_yHeight); }
/*
* CDisplayML::ConvertScrollToYPos() * * @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 Y position * gets greater than that. */ LONG CDisplayML::ConvertScrollToYPos( LONG yPos) //@parm Scroll position
{ // Get maximum scroll range
LONG yRange = GetMaxYScroll();
// Has maximum scroll range exceeded 16-bits?
if(yRange >= _UI16_MAX) { // Yes - Extrapolate to "real" yPos
yPos = MulDiv(yPos, yRange, _UI16_MAX); } return yPos; }
/*
* CDisplayML::ConvertYPosToScrollPos() * * @mfunc * Calculate the scroll position from the Y position in the document. * * @rdesc * Scroll position from Y position * * @devnote * This routine exists because the thumb position messages * are limited to 16-bits so we extrapolate when the Y position * gets greater than that. * */ inline LONG CDisplayML::ConvertYPosToScrollPos( LONG yPos) //@parm Y position in document
{ // Get maximum scroll range
LONG yRange = GetMaxYScroll();
// Has maximum scroll range exceeded 16-bits?
if(yRange >= _UI16_MAX) { // Yes - Extrapolate to "real" yPos
yPos = MulDiv(yPos, _UI16_MAX, yRange); } return yPos; }
CDisplayML::CDisplayML (CTxtEdit* ped) : CDisplay (ped), _pddTarget(NULL) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CDisplayML");
Assert(!_xWidthMax && !_yHeightMax);
_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(!_yCalcMax && !_xWidth && !_yHeight && !_cpMin); Assert(!_fBgndRecalc && !_fVScrollEnabled && !_fHScrollEnabled);
// 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
// ??? - CF need fixing for windowless case
if(dwScrollBars & WS_HSCROLL) { _ped->TxSetScrollRange (SB_HORZ, 0, 1, TRUE); _ped->TxEnableScrollBar(SB_HORZ, ESB_DISABLE_BOTH); } }
SetWordWrap(_ped->TxGetWordWrap()); _cpFirstVisible = _cpMin; Assert(!_xScroll && !_yScroll && !_iliFirstVisible && !_cpFirstVisible && !_dyFirstVisible);
_TEST_INVARIANT_
return TRUE; }
//================================ Device drivers ===================================
/*
* CDisplayML::SetMainTargetDC(hdc, xWidthMax) * * @mfunc * Sets a target device for this display and updates view * * @devnote * Target device can't be a metafile (can 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 xWidthMax) //@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 || !xWidthMax);
// If xWidthMax 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 xWidthMax = 0 means use the view rect width
_xWidthMax = (xWidthMax <= 0) ? 0 : max(DXtoLX(GetXWidthSys()), xWidthMax); // 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 <nl> * A <nl> * * I'll get a calced size that's basically 2 * heightof(A). * With a scrollbar, this could wordwrap to * * a a <nl> * A A <nl> * * 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(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 ( BOOL fWait) //@parm Recalc lines down to _cpWait/_yWait; 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 xWidth; LONG yHeight = 0; LONG cchText = _ped->GetTextLength(); BOOL fWaitingForFirstVisible = TRUE; LONG yHeightView = _yHeightView; LONG yHeightScrollOld = GetMaxYScroll(); LONG yHeightScrollNew;
Remove(0, -1); // Remove all old lines from *this
_yCalcMax = 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; _yWait = -1; fWait = TRUE; }
// Init measurer at cp = 0
CMeasurer me(this); 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(!pliNew->Measure(me, -1, -1, uiFlags)) { Assert(FALSE); goto err; }
fFirstInPara = pliNew->_bFlags & fliHasEOP; yHeight += pliNew->GetHeight(); _cpCalcMax = me.GetCp();
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) { _yWait = yHeight + yHeightView; fWaitingForFirstVisible = FALSE; } } else if(yHeight > _yWait && cliWait-- <= 0) { fDone = FALSE; break; } } }
_yCalcMax = yHeight; _fRecalcDone = fDone; _fNeedRecalc = FALSE; yHeightScrollNew = CalcScrollHeight(yHeight);
if(fDone && (yHeight != _yHeight || yHeightScrollNew != yHeightScrollOld) || yHeightScrollNew > yHeightScrollOld) { _fViewChanged = TRUE; }
_yHeight = yHeight; xWidth = CalcDisplayWidth(); if(fDone && xWidth != _xWidth || xWidth > _xWidth) { _xWidth = xWidth; _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) { _yWait = -1; _cpWait = -1; CheckLineArray(); _fLineRecalcErr = FALSE; }
#ifdef DEBUG
if( 1 ) { _TEST_INVARIANT_ } //Array memory allocation tracking
{ void **pv = (void**)((char*)this + sizeof(CDisplay)); PvSet(*pv); } #endif
return TRUE;
err: TRACEERRORSZ("CDisplayML::RecalcLines() failed");
if(!_fLineRecalcErr) { _cpCalcMax = me.GetCp(); _yCalcMax = yHeight; _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 ( const 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/_yWait; 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; // long Temporary
CLine * pliNew; CLinePtr rpOld(this); LONG xWidth; LONG yHeight; LONG yHeightPrev = 0; LONG cchText = _ped->GetTextLength(); UINT uiFlags; BOOL fReplaceResult; BOOL fTryForMatch = TRUE; LONG yHeightScrollOld = GetMaxYScroll(); LONG yHeightScrollNew; 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 == _yWait)), "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 CLineArray and backup to start of line
rpOld.RpSetCp(rtp.GetCp(), FALSE); cchSkip = rpOld.RpGetIch(); rpOld.RpAdvanceCp(-cchSkip); // Point rp at 1st char in line
ili = rpOld; // Save line # at change for
if(ili && (IsInOutlineView() || // numbering. Back up if not
rtp.GetPF()->IsListNumbered())) // first number in list or if
{ // in OutlineView (Outline
ili--; // symbol may change)
}
// 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(lT-- > 0 && rpOld > 0 && (!rpOld[-1]._cchEOP || ili < rpOld)) { cliBackedUp++; rpOld--; cchSkip += rpOld->_cch; }
// Init measurer at rtp
CMeasurer me(this, rtp);
me.Advance(-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]._bFlags & fliHasEOP; }
yHeight = YposFromLine(rpOld);
// Update first-affected and pre-edit-match lines in pled
pled->_iliFirst = rpOld; pled->_cpFirst = pled->_cpMatchOld = me.GetCp(); pled->_yFirst = pled->_yMatchOld = yHeight; AssertSz(pled->_yFirst >= 0, "CDisplayML::RecalcLines _yFirst < 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
_yCalcMax = yHeight; _cpCalcMax = me.GetCp();
// 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 a head and calculate some lines anyway. This
// prevents any weird background recalcs from occuring when it is
// unnecessary to go into background recalc.
if(fWait && _yWait > 0 && yHeight > _yWait && me.GetCp() > _cpWait) { _yHeight = yHeight; rpOld.Remove(-1); // Remove all old lines from here on
StartBackgroundRecalc(); // Start up the background recalc
pled->SetMax(this); return TRUE; }
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; }
// Can't reuse old results if we've got a target device
// For SPEED: it'd be nice to cache a few values when we do have a
// target device - a good caching heuristic could halve the measuring
const LONG cchNonWhite = rpOld.IsValid() ? rpOld->_cch - rpOld->_cchWhite : 0; uiFlags = MEASURE_BREAKATWORD | (fFirstInPara ? MEASURE_FIRSTINPARA : 0);
if (cchSkip > 0 && cchSkip >= cchNonWhite && !IsInOutlineView() && !_ped->fUseLineServices() && (!_pddTarget || !_pddTarget->IsValid())) { me.NewLine(*rpOld); // Don't remeasure anything we
me.Advance(cchNonWhite); // already have valid info on
me._li._cch = cchNonWhite; me._li._xWidth = rpOld->_xWidth;
// Clear out any of the old flags _except_ for tabs and OLE or
// OffScreen. Note that this algorithm is somewhat bogus; there
// is no guarantee that the line still matches the flag state.
// However,those flags are simply 'hints'--i.e. the line _may_
// be in that state. Keeping those flags set will result
// in a minor slowdown for rendering the line.
me._li._bFlags &= (fliHasTabs | fliHasOle | fliUseOffScreenDC | fliHasSpecialChars);
if(rpOld->_bFlags & fliOffScreenOnce) me._li._bFlags &= ~fliUseOffScreenDC; me._li._cchEOP = 0; uiFlags |= MEASURE_DONTINIT; // CLine part of me already init'd
}
// Stuff text into new line
Tracef(TRCSEVINFO, "Measuring new line from cp = %d", me.GetCp());
if(!pliNew->Measure(me, -1, -1, uiFlags)) { Assert(FALSE); goto err; }
if(!pliNew->_cch) { TRACEWARNSZ( "CDisplayML::RecalcLines measure returned a zero length line"); goto errspace; }
fFirstInPara = pliNew->_bFlags & fliHasEOP; yHeightPrev = yHeight; yHeight += 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(*rpOld) && !IsInOutlineView()) { // 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->_yFirst += rpOld->GetHeight(); AssertSz(pled->_yFirst >= 0, "CDisplayML::RecalcLines _yFirst < 0"); pled->_yMatchOld += 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 && yHeight > _yWait && 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) { // Assume there are no matches to try for
BOOL frpOldValid = FALSE;
// If we run out of runs, then no match is possible. Therefore,
// we only try for a match as long as we have runs.
if(fTryForMatch) { // We are trying for a match so assume that there
// is a match after all
frpOldValid = TRUE;
// Look for match in old line break CArray
lT = me.GetCp() - cchNew + cchOld; while (rpOld.IsValid() && pled->_cpMatchOld < lT) { pled->_yMatchOld += 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.
fTryForMatch = FALSE; 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->_bFlags |= fliFirstInPara; if(rgliNew.Count() > 0) { if(!(rgliNew.Elem(rgliNew.Count() - 1)->_bFlags & fliHasEOP)) rpOld->_bFlags &= ~fliFirstInPara; } else if( rpOld >= pled->_iliFirst && pled->_iliFirst ) { if(!(rpOld[pled->_iliFirst - rpOld -1]._bFlags & fliHasEOP)) rpOld->_bFlags &= ~fliFirstInPara; }
pled->_iliMatchOld = rpOld;
// Replace old lines by new ones
lT = rpOld - pled->_iliFirst; rpOld = pled->_iliFirst; 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->_yMatchNew = yHeight; pled->_yMatchNewTop = yHeightPrev; pled->_iliMatchNew = rpOld; pled->_cpMatchNew = me.GetCp();
// Compute height and cp after all matches
_cpCalcMax = me.GetCp();
if(frpOldValid && rpOld.IsValid()) { do { yHeight += rpOld->GetHeight(); _cpCalcMax += rpOld->_cch; } while( rpOld.NextRun() ); }
// 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(!pliNew->Measure(me, -1, -1, MEASURE_BREAKATWORD | (fFirstInPara ? MEASURE_FIRSTINPARA : 0))) { Assert(FALSE); goto err; } fFirstInPara = pliNew->_bFlags & fliHasEOP; yHeight += pliNew->GetHeight();
if(fBackground && GetTickCount() >= (DWORD)dwBgndTickMax) { fDone = FALSE; // Took too long, stop for now
break; }
if(fWait && yHeight > _yWait && me.GetCp() > _cpWait && cliWait-- <= 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->_yMatchNew = yHeight; pled->_yMatchNewTop = yHeightPrev; pled->_yMatchOld = _yHeight; _cpCalcMax = me.GetCp();
// 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.
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 messed up because we may have changed the line that
// the first visible cp is on.
rpOld.BindToCp(me.GetCp()); pled->_iliMatchNew = rpOld.GetLineIndex(); pled->_cpMatchNew = me.GetCp() - rpOld.RpGetIch();
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->_yFirst -= pliNew->GetHeight(); AssertSz(pled->_yFirst >= 0, "CDisplayML::RecalcLines _yFirst < 0"); pled->_cpFirst -= pliNew->_cch; } match: _fRecalcDone = fDone; _fNeedRecalc = FALSE; _yCalcMax = yHeight;
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.
_yWait = -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
yHeightScrollNew = CalcScrollHeight(yHeight);
if (_fViewChanged || fDone && (yHeight != _yHeight || yHeightScrollNew != yHeightScrollOld) || yHeightScrollNew > yHeightScrollOld) { //!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 _yHeight to a new value, something != to yHeight.
_yHeight = yHeight; UpdateScrollBar(SB_VERT, TRUE); } else _yHeight = yHeight; // Guarantee heights agree
// Determine display width and update scrollbar
xWidth = CalcDisplayWidth(); if(_fViewChanged || (fDone && xWidth != _xWidth) || xWidth > _xWidth) { _xWidth = xWidth; 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
return TRUE;
errspace: _ped->GetCallMgr()->SetOutOfMemory(); _fNeedRecalc = TRUE; _cpCalcMax = _yCalcMax = 0; _fLineRecalcErr = TRUE;
err: if(!_fLineRecalcErr) { _cpCalcMax = me.GetCp(); _yCalcMax = yHeight; }
TRACEERRORSZ("CDisplayML::RecalcLines() failed");
if(!_fLineRecalcErr) { _fLineRecalcErr = TRUE; _ped->GetCallMgr()->SetOutOfMemory(); _fLineRecalcErr = FALSE; // fix up CArray & bail
} pled->SetMax(this);
return FALSE; }
/*
* CDisplayML::CalcDisplayWidth() * * @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::CalcDisplayWidth () { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CalcDisplayWidth");
LONG ili = Count(); CLine * pli; LONG xWidth = 0, lineWidth;
if(ili) { // Note: pli++ breaks array encapsulation (pli = Elem(ili) doesn't,
// but is a bit slower)
pli = Elem(0); for(xWidth = 0; ili--; pli++) { lineWidth = pli->_xLeft + pli->_xWidth + pli->_xLineOverhang; xWidth = max(xWidth, lineWidth); } } return xWidth; }
/*
* 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(), "CDisplayML::StartBackgroundRecalc _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 tp(_ped, _cpCalcMax); RecalcLines(tp, cch, cch, TRUE, FALSE, NULL);
_fInBkgndRecalc = FALSE; }
/*
* CDisplayML::WaitForRecalc(cpMax, yMax) * * @mfunc * Ensures that lines are recalced until a specific character * position or ypos. * * @rdesc * success */ BOOL CDisplayML::WaitForRecalc( LONG cpMax, //@parm Position recalc up to (-1 to ignore)
LONG yMax) //@parm ypos to recalc up to (-1 to ignore)
{ TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::WaitForRecalc");
_TEST_INVARIANT_
if(IsFrozen() || !_ped->fInplaceActive()) return TRUE;
BOOL fReturn = TRUE; LONG cch;
if((yMax < 0 || yMax >= _yCalcMax) && (cpMax < 0 || cpMax >= _cpCalcMax)) { cch = _ped->GetTextLength() - _cpCalcMax; if(cch > 0 || Count() == 0) { HCURSOR hcur = NULL; BOOL fSetCursor = (cch > NUMCHARFORWAITCURSOR);
_cpWait = cpMax; _yWait = yMax; if(fSetCursor) hcur = SetCursor(LoadCursor(0, IDC_WAIT)); TRACEINFOSZ("Lazy recalc"); if(!_cpCalcMax || _fNeedRecalc) { fReturn = RecalcLines(TRUE); RebindFirstVisible(); if(!fReturn) InitVars(); } else { CRchTxtPtr rtp(_ped, _cpCalcMax); fReturn = RecalcLines(rtp, cch, cch, FALSE, TRUE, NULL); }
if(fSetCursor) SetCursor(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 <p ili> * * @rdesc * Returns TRUE if lines were recalc'd up to ili */ 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, _yScroll + _yHeightView); }
/*
* 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);
// FUTURE (alexgo, ricksa): This is called from EM_GETLINE whose parameter
// is a WPARAM which is unsigned we need to fix the type of ili.
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::YposFromLine(ili) * * @mfunc * Computes top of line position * * @rdesc * top position of given line (relative to the first line) */ LONG CDisplayML::YposFromLine( LONG ili) //@parm Line we're interested in
{ TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::YposFromLine"); _TEST_INVARIANT_
if(!WaitForRecalcIli(ili)) // out of range, use last valid line
{ ili = Count() - 1; ili = (ili > 0) ? ili : 0; }
LONG cli = ili - _iliFirstVisible; CLine * pli = Elem(_iliFirstVisible); LONG yPos = _yScroll + _dyFirstVisible;
while(cli > 0) { yPos += pli->GetHeight(); cli--; pli++; } while(cli < 0) { pli--; yPos -= pli->GetHeight(); cli++; }
AssertSz(yPos >= 0, "CDisplayML::YposFromLine height less than 0");
return yPos; }
/*
* CDisplayML::CpFromLine(ili, pyHeight) * * @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 <lt> 0 means caret line)
LONG *pyHeight) //@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 y = _yScroll + _dyFirstVisible; 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; y = 0; cp = 0; iStart = 0; } else if( cli <= 0 ) { CheckView(); for(ili = _iliFirstVisible-1; cli < 0; cli++, ili--) { pli = Elem(ili); y -= pli->GetHeight(); cp -= pli->_cch; } goto end; }
for(ili = iStart; cli > 0; cli--, ili++) { pli = Elem(ili); if(!IsMain() || !WaitForRecalcIli(ili)) break; y += pli->GetHeight(); cp += pli->_cch; }
end: if(pyHeight) *pyHeight = y;
return cp; }
/*
* CDisplayML::LineFromYPos(yPos, pyLine, pcpFirst) * * @mfunc * Computes line at given y position. Returns top of line ypos * cp at start of line cp, and line index. * * @rdesc * index of line found */ LONG CDisplayML::LineFromYpos ( LONG yPos, //@parm Ypos to look for (relative to first line)
LONG *pyLine, //@parm Returns ypos at top of line /r first line (can be NULL)
LONG *pcpFirst) //@parm Returns cp at start of line (can be NULL)
{ TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::LineFromYpos");
_TEST_INVARIANT_
LONG cpLi; LONG dy; LONG ili = 0; LONG yLi; CLine *pli;
if(!WaitForRecalc(-1, _yScroll)) { yLi = 0; cpLi = 0; goto done; }
cpLi = _cpFirstVisible; ili = _iliFirstVisible; yLi = _yScroll + _dyFirstVisible; dy = yPos - yLi; if(dy < 0 && -dy <= _yScroll) { // Closer to first visible line than to first line:
// go backwards from first visible line.
while(yPos < yLi && ili > 0) { pli = Elem(--ili); yLi -= pli->GetHeight(); cpLi -= pli->_cch; } } else { if(dy < 0) { // Closer to first line than to first visible line:
// so start at first line.
cpLi = _cpMin; yLi = 0; ili = 0; } pli = Elem(ili); while(yPos > yLi && ili < Count()-1) { yLi += pli->GetHeight(); cpLi += pli->_cch; ili++; pli++; } if(yPos < yLi && ili > 0) { ili--; pli--; yLi -= pli->GetHeight(); cpLi -= pli->_cch; } }
done: if(pyLine) *pyLine = yLi;
if(pcpFirst) *pcpFirst = cpLi;
return ili; }
/*
* 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.RpSetCp(cp, fAtEnd)) return -1;
return (LONG)rp; }
//============================== Point <-> cp conversion ==============================
/*
* CDisplayML::CpFromPoint(pt, prcClient, ptp, prp, fAllowEOL, pHit, pdx) * * @mfunc * Determine cp at given point * * @devnote * --- Use when in-place active only --- * * @rdesc * Computed cp, -1 if failed */ LONG CDisplayML::CpFromPoint( POINT pt, //@parm Point to compute cp at (client coords)
const RECT *prcClient, //@parm Client rectangle (can be NULL if active).
CRchTxtPtr * const ptp, //@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) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CpFromPoint");
_TEST_INVARIANT_
LONG cp; LONG cch = 0; RECT rcView; HITTEST hit = HT_Nothing;
GetViewRect(rcView, prcClient);
// Get line under hit
int y = pt.y; pt.y += _yScroll - rcView.top;
LONG yLine; LONG ili = LineFromYpos(pt.y, &yLine, &cp); if(ili < 0) return -1;
if(y >= rcView.top && y <= rcView.bottom) { pt.y -= yLine;
CLine *pli = Elem(ili);
AssertSz(pli || !ili, "CDisplayML::CpFromPoint invalid line pointer");
if(pli) { CMeasurer me(this); // Create measurer
me.SetCp(cp);
// Transform to galley coordinates
// Adjust coordinate relative to where the view starts from.
pt.x -= rcView.left;
// Is x coordinate within the view?
if (pt.x >= 0) { // Adjust by the scroll value
pt.x += _xScroll; }
// Get character in line
cch = pli->CchFromXpos(me, pt, pdispdim, &hit, pcpActual); // Don't allow click at EOL to select EOL marker and take into
// account single line edits as well
if(!fAllowEOL && cch == pli->_cch && pli->_cchEOP) { // Adjust position on line by amount backed up. OK for
// me._rpCF and me._rpPF to get out of sync with me._rpTX,
// since they're not needed for me.GetCp().
cch += me._rpTX.BackupCpCRLF(); } cp = me.GetCp(); } } if(ptp) ptp->SetCp(cp); if(prp) prp->RpSet(ili, cch); if (phit) *phit = hit;
return cp; }
/*
* CDisplayML::PointFromTp(rtp, prcClient, fAtEnd, pt, prp, taMode) * * @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 RECT *prcClient, //@parm Client rectangle (can be NULL if active).
BOOL fAtEnd, //@parm Return end of prev line for ambiguous cp
POINT & 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");
_TEST_INVARIANT_
LONG dy = 0; RECT rcView; CLinePtr rp(this);
if(!WaitForRecalc(rtp.GetCp(), -1) || !rp.RpSetCp(rtp.GetCp(), fAtEnd)) return -1;
AssertSz(_ped->_fInPlaceActive || prcClient, "CDisplayML::PointFromTp() called with invalid client rect");
GetViewRect(rcView, prcClient);
pt.x = rcView.left - _xScroll; pt.y = YposFromLine(rp) - _yScroll + rcView.top;
CMeasurer me(this, rtp); me.Advance(-rp.RpGetIch()); // Backup to start of line
me.NewLine(*rp); // Measure from there to where we are
LONG xCalc = rp->XposFromCch(me, rp.RpGetIch(), taMode, pdispdim, &dy);
if(pt.x + xCalc <= rcView.right || !GetWordWrap() || GetTargetDev()) { // Width is in view or there is no wordwrap so just
// add the length to the point.
pt.x += xCalc; } else { // Remember we ignore trailing spaces at the end of the line in
// the width, therefore the x value that MeasureText finds can
// be greater than the width in the line so we truncate to the
// previously calculated width which will ignore the spaces.
pt.x += rp->_xLeft + rp->_xWidth; // We *don't* worry about xLineOverhang here
}
pt.y += dy;
if(prp) *prp = rp; return rp; }
//==================================== Rendering =======================================
/*
* CDisplayML::Render(rcView, rcRender) * * @mfunc * Searches paragraph boundaries around a range */ void CDisplayML::Render ( const RECT &rcView, //@parm View RECT
const RECT &rcRender) //@parm RECT to render (must be container in
// client rect)
{ TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::Render");
_TEST_INVARIANT_ LONG cp; BOOL fLinesToRender = TRUE; LONG lCount = Count(); CTxtSelection *psel = _ped->GetSelNC(); POINT pt; LONG yBottomOfRender; LONG yHeightForBitmap = 0; LONG yHeightForLine; LONG yLine; LONG yRenderHeight = rcRender.top + _yScroll - rcView.top;
if(psel) psel->ClearCchPending(); // Review (murrays) this routine is called twice from disp.cpp (once for
// rendering and once for transparent hit testing) and once from edit.cpp
// (for printing). Only the display rendering needs to notify the
// update and it does it there. So the following is either undesired or
// redundant, i.e., should be deleted.
// Fire event "updating"
//if(IsMain())
// _ped->TxNotify(EN_UPDATE, NULL);
// Calculate line and cp to start display at
LONG ili = LineFromYpos(rcRender.top + _yScroll - rcView.top, &yLine, &cp); CLine *pli = Elem(ili); LONG yLi = pli ? pli->GetHeight() : 0; // Null == some forms^3 empty control
yLi = max(yLi, 0);
if(yRenderHeight > yLine + yLi) fLinesToRender = FALSE;
// Calculate point where text will start being displayed
pt.x = rcView.left - _xScroll; pt.y = rcView.top - _yScroll + yLine; yBottomOfRender = BottomOfRender(rcView, rcRender);
// We only need check for whether we want to offscreen render if the
// control is not transparent. Remember if the control is transparent,
// the rendering of mixed character formats will work because characters
// in adjoining runs are only truncated if ExtTextOut is trying to clear
// the display area at the same time.
if (!IsMetafile() && IsMain() && !IsTransparent()) { // Initialize height counter to first position to display
yLi = pt.y;
// Loop through visible lines until we have examined entire
// line array or we have exceeded visible height
CLine *pli = Elem(ili); for (LONG iliLoop = ili; iliLoop < lCount && yLi < yBottomOfRender; iliLoop++, pli++) { if(pli->_fCollapsed) continue; yHeightForLine = pli->_yHeight; // Get local copy of line height
if(pli->_bFlags & fliUseOffScreenDC) yHeightForBitmap = max(yHeightForLine, yHeightForBitmap);
yLi += yHeightForLine; } }
// Create renderer
CRenderer re(this);
// Prepare renderer
if(!re.StartRender(rcView, rcRender, yHeightForBitmap)) return; // Init renderer at start of first line to render
re.SetCurPoint(pt); cp = re.SetCp(cp); yLi = pt.y;
if(fLinesToRender) { // Render each line in update rectangle
for (; ili < lCount; ili++) { if (!Elem(ili)->Render(re) || re.GetCurPoint().y >= yBottomOfRender) { break; } #ifdef DEBUG
cp += Elem(ili)->_cch; yLi += Elem(ili)->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, "CDisplayML::RenderView() - cp out of sync with line table"); pt = re.GetCurPoint(); AssertSz(pt.y == yLi, "CDisplayML::RenderView() - y out of sync with line table"); #endif
} } re.EndRender(); // Finish rendering
}
//=================================== View Updating ===================================
/*
* CDisplayML::RecalcView(fUpdateScrollBars) * * @mfunc * Recalc all lines breaks and update first visible line * * @rdesc * TRUE if success */ BOOL CDisplayML::RecalcView( BOOL fUpdateScrollBars, RECT* prc) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcView");
BOOL fRet = TRUE; LONG yHeightOld = _yHeight; LONG yScrollHeightOld = GetMaxYScroll(); LONG xWidthOld = _xWidth;
// Full recalc lines
if(!RecalcLines()) { // We're in deep trouble now, the recalc failed. Let's try to get out
// of this with our head still mostly attached
InitVars(); fRet = FALSE; goto Done; }
if(!_ped->GetTextLength()) // This is an empty control so
CreateEmptyLine(); // create one empty line
// Force _xScroll = 0 if x scroll range is smaller than the view width
if(_xWidth <= _xWidthView) _xScroll = 0;
RebindFirstVisible();
CheckView();
// We only need to resize if the size needed to display the object has
// changed.
if (yHeightOld != _yHeight || yScrollHeightOld != GetMaxYScroll() || xWidthOld != _xWidth) { 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( const 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; RECT rcClient; RECT rcView; CLed led; CTxtSelection *psel = _ped->GetSelNC(); LONG cpStartOfUpdate = rtp.GetCp(); BOOL fNeedViewChange = FALSE; LONG yHeightOld = _yHeight; LONG yScrollHeightOld = GetMaxYScroll(); LONG xWidthOld = _xWidth; LONG yScrollOld = _yScroll; 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.
return TRUE; }
AssertSz(rtp.GetCp() <= _cpCalcMax, "CDisplayML::UpdateView(...) - rtp > _cpCaclMax");
_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.
_yWait = _yScroll + _yHeightView; _cpWait = -1;
if(!RecalcLines(rtp, cchOld, cchNew, FALSE, TRUE, &led)) { // We're in trouble now, the recalc failed. Let's try to get
// out of this with our head still mostly attached
InitVars(); fRecalcVisible = TRUE; fReturn = FALSE; _ped->TxInvalidateRect (NULL, FALSE); fNeedViewChange = TRUE; goto Exit; }
if(!_ped->GetTextLength()) { if(LineCount()) // There are currently elements in
Clear(AF_DELETEMEM); // line array, so zap them
// This is an empty control so create one empty line
CreateEmptyLine(); }
if(_xWidth <= _xWidthView) { // 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 _xWidthView. UpdateCaret forces redraw
// only when such lines are growing, misses shrinking.
if (_xScroll != 0) { _ped->TxInvalidateRect(NULL, FALSE); //REVIEW: find a smaller rectange?
} _xScroll = 0;
}
if(led._yFirst >= _yScroll + _yHeightView) { // Update is after view: don't do anything
fRecalcVisible = FALSE; AssertNr(VerifyFirstVisible()); goto finish; } else if(led._yMatchNew <= _yScroll + _dyFirstVisible && led._yMatchOld <= _yScroll + _dyFirstVisible && _yScroll < _yHeight) { if (_yHeight != 0) { // Update is entirely before view: just update scroll position
// but don't touch the screen
_yScroll += led._yMatchNew - led._yMatchOld; _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; } 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.
_yScroll = 0; _iliFirstVisible = 0; _cpFirstVisible = 0; }
AssertNr(VerifyFirstVisible()); } else { // Update overlaps visible view
RECT rc = rcClient; RECT rcUpdate;
// 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.
if ( 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::RpSetCp() and
// YPosFromLine() use them, but they're not valid
_dyFirstVisible = 0; _cpFirstVisible = 0; _iliFirstVisible = 0; _yScroll = 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);
// TODO: make following more efficient (work around rp.CalculateCp()
// all the way from zero)
// Recompute scrolling position and first visible values after edit
CLinePtr rp(this); rp.RpSetCp(cpNewFirstVisible, FALSE); _yScroll = YposFromLine(rp); _cpFirstVisible = rp.CalculateCp() - rp.RpGetIch(); _iliFirstVisible = rp; } 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._yMatchOld < yScrollOld + _yHeightView && led._yMatchNew < _yScroll + _yHeightView) { // 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 + (INT) (led._yMatchOld - yScrollOld); if(rc.top < rc.bottom) { // Calculate difference between new and old screen positions
const INT dy = (INT) ((led._yMatchNew - _yScroll)) - (led._yMatchOld - yScrollOld);
if(dy) { if(!IsTransparent()) { _ped->TxScrollWindowEx(0, dy, &rc, &rcView, NULL, &rcUpdate, 0); _ped->TxInvalidateRect(&rcUpdate, FALSE); fNeedViewChange = TRUE;
if(dy < 0) { rc.top = rc.bottom + dy; _ped->TxInvalidateRect(&rc, FALSE); fNeedViewChange = TRUE; } } else { // Adjust rect since we don't scroll in transparent
// mode
RECT rcInvalidate = rc; rcInvalidate.top += dy;
_ped->TxInvalidateRect(&rcInvalidate, FALSE); fNeedViewChange = TRUE; } } } else { rc.top = rcView.top + led._yMatchNew - _yScroll; _ped->TxInvalidateRect(&rc, FALSE); 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 + (INT) (max(led._yMatchNew, led._yMatchOld) - _yScroll); }
rc.top = rcView.top + (INT) (led._yFirst - _yScroll);
// Set first line edited to be rendered using off-screen bitmap
if (led._iliFirst < Count() && !IsTransparent() && !(Elem(led._iliFirst)->_bFlags & fliUseOffScreenDC)) { Elem(led._iliFirst)->_bFlags |= (fliOffScreenOnce | fliUseOffScreenDC); } // Invalidate part of update that is above match (if any)
_ped->TxInvalidateRect (&rc, FALSE); 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 (yHeightOld != _yHeight || yScrollHeightOld != GetMaxYScroll() || xWidthOld != _xWidth) { if(FAILED(RequestResize())) _ped->GetCallMgr()->SetOutOfMemory(); } if(DoDeferredUpdateScrollBar()) { if(FAILED(RequestResize())) _ped->GetCallMgr()->SetOutOfMemory(); DoDeferredUpdateScrollBar(); }
Exit: return fReturn; }
void CDisplayML::InitVars() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::InitVars");
_yScroll = _xScroll = 0; _iliFirstVisible = 0; _cpFirstVisible = _cpMin = 0; _dyFirstVisible = 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 yHeight = _dyFirstVisible; LONG cp; LONG cchWhite = 0;
for(cp = _cpFirstVisible; yHeight < _yHeightView && ili < Count(); cli++, ili++) { const CLine* pli = Elem(ili); yHeight += pli->GetHeight();
if (fLastCharOfLastVisible) { if (yHeight > _yHeightView) { // Back up cp to last visible character
cp -= cchWhite; break; }
// Save last lines white space to adjust cp if
// this is the last fully displayed line.
cchWhite = pli->_cchWhite; } 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; RECT rc; RECT 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; }
#ifdef LINESERVICES
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
GetViewRect(rcView); // Get view rectangle
// Compute first line to invert and where to start on it
if(cp >= _cpFirstVisible) { POINT pt; rtp.SetCp(cp); if(PointFromTp(rtp, NULL, FALSE, pt, &rp, TA_TOP) < 0) return FALSE;
rc.left = pt.x; rc.top = pt.y; } else { cp = _cpFirstVisible; rp = _iliFirstVisible; rc.left = -1; rc.top = rcView.top + _dyFirstVisible; }
// 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(rc.left == -1) rc.left = rp->_xLeft - _xScroll + rcView.left;
//If we are inverting the active end of the selection, draw it offscreen
//to minimize flicker.
if (IN_RANGE(cp - rp.RpGetIch(), cpActive, cp - rp.RpGetIch() + rp->_cch) && !IsTransparent() && !(rp->_bFlags & fliUseOffScreenDC)) { rp->_bFlags |= (fliOffScreenOnce | fliUseOffScreenDC); }
cp += rp->_cch - rp.RpGetIch();
rc.left = rcView.left; rc.right = rcView.right;
_ped->TxInvalidateRect(&rc, TRUE); rc.top = rc.bottom; if(!rp.NextRun()) break; } _ped->TxUpdateWindow(); // Make sure window gets repainted
return TRUE; }
//=================================== Scrolling =============================
/*
* CDisplay::GetYScroll() * * @mfunc * Returns vertical scrolling position */ LONG CDisplayML::GetYScroll() const { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetYScroll");
return _yScroll; }
/*
* CDisplay::VScroll(wCode, yPos) * * @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 yPos) //@parm Thumb position (yPos <lt> 0 for EM_SCROLL behavior)
{ TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::VScroll");
LONG cliVisible; LONG dy = 0; BOOL fTracking = FALSE; LONG i; const LONG ili = _iliFirstVisible; CLine * pli = NULL; INT yHeightSys = GetYHeightSys(); LONG yScroll = _yScroll; AssertSz(_ped->_fInPlaceActive, "CDisplay::VScroll() called when not in-place");
if(yPos) { // Convert this from 16-bit to 32-bit if necessary.
yPos = ConvertScrollToYPos(yPos); }
yPos = min(yPos, _yHeight);
switch(wCode) { case SB_BOTTOM: if(yPos < 0) return FALSE; WaitForRecalc(_ped->GetTextLength(), -1); yScroll = _yHeight; break;
case SB_LINEDOWN: cliVisible = GetCliVisible(); if(_iliFirstVisible + cliVisible < Count() && 0 == _dyFirstVisible) { i = _iliFirstVisible + cliVisible; pli = Elem(i); if(IsInOutlineView()) { // Scan for uncollapsed line
for(; pli->_fCollapsed && i < Count(); pli++, i++); } if(i < Count()) dy = pli->_yHeight; } else if(cliVisible > 1) { pli = Elem(_iliFirstVisible); dy = _dyFirstVisible; // TODO: scan until find uncollapsed line
dy += pli->_yHeight; } else dy = _yHeight - _yScroll;
if(dy >= _yHeightView) dy = yHeightSys;
// Nothing to scroll, early exit
if ( !dy ) return MAKELRESULT(0, TRUE);
yScroll += dy; break;
case SB_LINEUP: if(_iliFirstVisible > 0) { pli = Elem(_iliFirstVisible - 1); // TODO: scan until find uncollapsed line
dy = pli->_yHeight; } else if(yScroll > 0) dy = min(yScroll, yHeightSys);
if(dy > _yHeightView) dy = yHeightSys; yScroll -= dy; break;
case SB_PAGEDOWN: cliVisible = GetCliVisible(); yScroll += _yHeightView; if(yScroll < _yHeight && cliVisible > 0) { // TODO: Scan until find uncollapsed line
dy = Elem(_iliFirstVisible + cliVisible - 1)->_yHeight; if(dy >= _yHeightView) dy = yHeightSys;
else if(dy > _yHeightView - dy) { // Go at least a line if line is very big
dy = _yHeightView - dy; } yScroll -= dy; } break;
case SB_PAGEUP: cliVisible = GetCliVisible(); yScroll -= _yHeightView;
if (yScroll < 0) { // Scroll position can't be negative and we don't
// need to back up to be sure we display a full line.
yScroll = 0; } else if(cliVisible > 0) { // TODO: Scan until find uncollapsed line
dy = Elem(_iliFirstVisible)->_yHeight; if(dy >= _yHeightView) dy = yHeightSys;
else if(dy > _yHeightView - dy) { // Go at least a line if line is very big
dy = _yHeightView - dy; }
yScroll += dy; } break;
case SB_THUMBTRACK: case SB_THUMBPOSITION:
if(yPos < 0) return FALSE;
yScroll = yPos; fTracking = TRUE; break;
case SB_TOP: if(yPos < 0) return FALSE; yScroll = 0; break;
case SB_ENDSCROLL: UpdateScrollBar(SB_VERT); return MAKELRESULT(0, TRUE);
default: return FALSE; } ScrollView(_xScroll, min(yScroll, max(_yHeight - _yHeightView, 0)), fTracking, TRUE);
// 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 - ili), 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 yScroll position by adding the difference of the line
// we want to go to and the current _yScroll position
LONG dyScroll = CalcYLineScrollDelta(cli, FALSE); if(dyScroll < 0 || _yHeight - (_yScroll + dyScroll) > _yHeightView - dyScroll) ScrollView(_xScroll, _yScroll + dyScroll, FALSE, FALSE); }
/*
* CDisplayML::FractionalScrollView (yDelta) * * @mfunc * Allow view to be scrolled by fractional lines. */ void CDisplayML::FractionalScrollView ( LONG yDelta ) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::FractionalScrollView");
if ( yDelta) ScrollView(_xScroll, min(yDelta + _yScroll, max(_yHeight - _yHeightView, 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 _dyFirstVisible is zero, then we're aligned on a line, so
// nothing more to do.
if(_dyFirstVisible) { LONG yScroll = _yScroll + _dyFirstVisible;
if(iDirection <= 0) { yScroll += Elem(_iliFirstVisible)->_yHeight; }
ScrollView(_xScroll, yScroll, FALSE, TRUE); } #endif // 0
}
/*
* CDisplayML::CalcYLineScrollDelta (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::CalcYLineScrollDelta ( LONG cli, BOOL fFractionalFirst ) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CalcYLineScrollDelta");
LONG yScroll = 0;
if(fFractionalFirst && _dyFirstVisible) // Scroll partial for 1st.
{ Assert(_dyFirstVisible <= 0); // get jonmat
if(cli < 0) { cli++; yScroll = _dyFirstVisible; } else { cli--; yScroll = Elem(_iliFirstVisible)->_yHeight + _dyFirstVisible; } }
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 == yScroll, "CDisplayML::CalcYLineScrollDelta last line & scroll"); yScroll = _yHeight - _yScroll;
// Limit scroll length to approximately 3 lines.
yScroll = min(yScroll, 3 * GetYHeightSys()); } } 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.
yScroll = _dyFirstVisible;
// Limit scroll length to approximately 3 lines.
yScroll = max(yScroll, -3 * GetYHeightSys()); } }
if(cli) yScroll += YposFromLine(_iliFirstVisible + cli) - YposFromLine(_iliFirstVisible); return yScroll; }
/*
* CDisplayML::ScrollView(xScroll, yScroll, 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 yScroll <gt> pdp->yHeight and yScroll <lt> 0 * * @rdesc * TRUE if actual scrolling occurred, * FALSE if no change */ BOOL CDisplayML::ScrollView ( LONG xScroll, //@parm New x scroll position
LONG yScroll, //@parm New y scroll position
BOOL fTracking, //@parm TRUE indicates we are tracking scrollbar thumb
BOOL fFractionalScroll) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::ScrollView"); // (don't update the scrollbar pos)
BOOL fTryAgain = TRUE; RECT rcUpdate; // ??? we may want use a region here but ScrollView is
// rarely called with both a xScroll and yScroll value.
LONG xWidthMax; LONG dx = 0; LONG dy = 0; RECT rcView; CTxtSelection *psel = _ped->GetSelNC(); COleObject *pipo; BOOL fRestoreCaret = FALSE;
AssertSz(_ped->_fInPlaceActive, "CDisplayML::ScrollView() called when not in-place"); GetViewRect(rcView); if(xScroll == -1) xScroll = _xScroll; if(yScroll == -1) yScroll = _yScroll; // Determine vertical scrolling pos
while(1) { BOOL fNothingBig = TRUE; LONG yFirst; LONG dyFirst; LONG cpFirst; LONG iliFirst; LONG yHeight; LONG iliT;
yScroll = min(yScroll, GetMaxYScroll()); yScroll = max(0, yScroll); dy = 0;
// Ensure all visible lines are recalced
if(!WaitForRecalcView()) return FALSE;
// Compute new first visible line
iliFirst = LineFromYpos(yScroll, &yFirst, &cpFirst);
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 _yScroll, use last line instead
iliFirst = max(0, Count() - 1); cpFirst = _ped->GetTextLength() - Elem(iliFirst)->_cch; yScroll = _yHeight - Elem(iliFirst)->_yHeight; yFirst = _yScroll; } dyFirst = yFirst - yScroll; // Figure whether there is a big line
// (more that a third of the view rect)
for(iliT = iliFirst, yHeight = dyFirst; yHeight < _yHeightView && iliT < Count(); iliT++) { const CLine* pli = Elem(iliT); if(pli->_yHeight >= _yHeightView / 3) fNothingBig = FALSE; yHeight += pli->_yHeight; }
// If no big line and first pass, try to adjust
// scrolling pos to show complete line at top
if(!fFractionalScroll && fTryAgain && fNothingBig && dyFirst != 0) { fTryAgain = FALSE; // prevent any infinite loop
Assert(dyFirst < 0);
Tracef(TRCSEVINFO, "adjusting scroll for partial line at %d", dyFirst); // partial line visible at top, try to get a complete line showing
yScroll += dyFirst;
LONG yHeightLine = Elem(iliFirst)->_yHeight;
// 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 && yScroll + _yHeightView + yHeightLine > _yHeight) || (!fTracking && _yScroll <= yScroll)) { // Scrolling down so move down a little more
yScroll += yHeightLine; } } else { dy = 0; if(yScroll != _yScroll) { _iliFirstVisible = iliFirst; _dyFirstVisible = dyFirst; _cpFirstVisible = cpFirst; dy = _yScroll - yScroll; _yScroll = yScroll;
AssertSz(_yScroll >= 0, "CDisplayML::ScrollView _yScroll < 0"); AssertNr(VerifyFirstVisible()); if(!WaitForRecalcView()) return FALSE; } break; } } CheckView();
// Determine horizontal scrolling pos.
xWidthMax = _xWidth;
// REVIEW (Victork) Restricting the range of the scroll is not really needed and could even be bad (bug 6104)
xScroll = min(xScroll, xWidthMax); xScroll = max(0, xScroll);
dx = _xScroll - xScroll; if(dx) _xScroll = xScroll;
// Now perform the actual scrolling
if(IsMain() && (dy || dx)) { // Scroll only if scrolling < view dimensions and we are in-place
if(IsActive() && !IsTransparent() && dy < _yHeightView && dx < _xWidthView) { // 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; } _ped->TxScrollWindowEx((INT) dx, (INT) dy, NULL, &rcView, NULL, &rcUpdate, 0);
_ped->TxInvalidateRect(&rcUpdate, FALSE);
if(fRestoreCaret) _ped->TxShowCaret(FALSE); } else _ped->TxInvalidateRect(&rcView, FALSE);
if(psel) psel->UpdateCaret(FALSE);
if(!fTracking && dy) { UpdateScrollBar(SB_VERT); _ped->SendScrollEvent(EN_VSCROLL); } if(!fTracking && dx) { UpdateScrollBar(SB_HORZ); _ped->SendScrollEvent(EN_HSCROLL); } _ped->TxUpdateWindow();
// 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( dx, dy ); } } return dy || dx; }
/*
* CDisplayML::GetScrollRange(nBar) * * @mfunc * Returns the max part of a scrollbar range for scrollbar <p nBar> * * @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 = GetMaxYScroll(); } else if((_ped->TxGetScrollBars() & WS_HSCROLL) && _fHScrollEnabled) { // Scroll range is maximum width plus room for the caret.
lRange = max(0, _xWidth + dxCaret); } // 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(GetMaxYScroll() <= _yHeightView) fEnabled = FALSE; } else { if(!(dwScrollBars & WS_HSCROLL)) { // Even if we don't have scrollbars, we may allow horizontal
// scrolling.
if(!_fHScrollEnabled && _xWidth > _xWidthView) _fHScrollEnabled = !!(dwScrollBars & ES_AUTOHSCROLL);
return FALSE; }
fEnabledOld = _fHScrollEnabled; if(_xWidth <= _xWidthView) 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) _fHScrollEnabled = 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.
_yScroll = 0; _dyFirstVisible = 0; _cpFirstVisible = 0; _iliFirstVisible = 0; _ped->TxInvalidateRect(NULL, FALSE); } } 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->TxInvalidateRect(NULL, TRUE); // 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) ? ConvertYPosToScrollPos(_yScroll) : ConvertXPosToScrollPos(_xScroll);
_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. * * @rdesc * S_OK - Call completed successfully <nl> */ 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 yHeight = 0; LONG cchText = _ped->GetTextLength(); BOOL fFirstInPara = TRUE;
LONG xWidthMax = GetWordWrap() ? widthView : -1;
// 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; if(!liNew.Measure(me, -1, xWidthMax, uiFlags)) { hr = E_FAIL; goto exit; } fFirstInPara = liNew._bFlags & fliHasEOP;
// Keep track of width of widest line
lineWidth = liNew._xWidth + liNew._xLineOverhang; xWidth = max(xWidth, lineWidth); yHeight += liNew._yHeight; // 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 += dxCaret;
*pwidth = xWidth; *pheight = yHeight;
// 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->_xScroll = _xScroll; pdp->_fVScrollEnabled = _fVScrollEnabled; pdp->_fHScrollEnabled = _fHScrollEnabled; pdp->_fWordWrap = _fWordWrap; pdp->_cpFirstVisible = _cpFirstVisible; pdp->_iliFirstVisible = _iliFirstVisible; pdp->_yScroll = _yScroll; pdp->ResetDrawInfo(this);
if(_pddTarget) { // Create a duplicate target device for this object
pdp->SetMainTargetDC(_pddTarget->GetDC(), _xWidthMax); }
// 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; }
LONG CDisplayML::GetMaxPixelWidth(void) const { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetMaxPixelWidth");
return _xWidthMax ? LXtoDX(_xWidthMax) : GetViewWidth(); }
/*
* CDisplayML::GetMaxXScroll() * * @mfunc * Get the maximum x scroll value * * @rdesc * Maximum x scroll value * */ LONG CDisplayML::GetMaxXScroll() const { return _xWidth + dxCaret; }
/*
* 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
if(!pliNew->Measure(me, -1, -1, MEASURE_BREAKATWORD | MEASURE_FIRSTINPARA)) { Assert(FALSE); return FALSE; } return TRUE; }
/*
* CDisplayML::AdjustToDisplayLastLine() * * @mfunc * Calculate the yscroll necessary to get the last line to display * * @rdesc * Updated yScroll * */ LONG CDisplayML::AdjustToDisplayLastLine( LONG yBase, //@parm actual yScroll to display
LONG yScroll) //@parm proposed amount to scroll
{ LONG iliFirst; LONG yFirst;
if(yBase >= _yHeight) { // Want last line to be entirely displayed.
// Compute new first visible line
iliFirst = LineFromYpos(yScroll, &yFirst, NULL);
// Is top line partial?
if(yScroll != yFirst) { // 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.
yScroll = YposFromLine(iliFirst + 1); } } return yScroll; }
/*
* CDisplayML::GetResizeHeight() * * @mfunc * Calculates height to return for a request resize * * @rdesc * Updated yScroll */ LONG CDisplayML::GetResizeHeight() const { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetResizeHeight");
return CalcScrollHeight(_yHeight); }
void CDisplayML::RebindFirstVisible() { LONG cp = _cpFirstVisible;
// Change first visible entries because CLinePtr::RpSetCp() and
// YPosFromLine() use them, but they're not valid
_dyFirstVisible = 0; _cpFirstVisible = 0; _iliFirstVisible = 0; _yScroll = 0;
// Recompute scrolling position and first visible values after edit
// force _yScroll = 0 if y scroll range is smaller than the view height
if(_yHeight > _yHeightView) { CLinePtr rp(this); rp.RpSetCp(cp, FALSE); _yScroll = YposFromLine(rp); // TODO: make following more efficient (work around rp.CalculateCp()
// all the way from zero)
// We use rp.GetCp() instead of cp, because cp could now be
// woefully out of date. RpSetCp will set us to the closest
// available cp.
_cpFirstVisible = rp.CalculateCp() - rp.RpGetIch(); _iliFirstVisible = rp; } }
// ================================ 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 yHeight = 0; CLine const *pli = Elem(0); CRchTxtPtr rtp(_ped);
while(ili--) { fFirstInPara = (pli->_bFlags & fliFirstInPara) != 0; AssertSz(!(fPrevLineEOP ^ fFirstInPara), "CDisplayML::CheckLineArray: Invalid first/prev flags");
AssertSz(pli->_cch, "CDisplayML::CheckLineArray: cch == 0");
yHeight += pli->GetHeight(); cp += pli->_cch; fPrevLineEOP = (pli->_bFlags & fliHasEOP) != 0; 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(yHeight != _yHeight) { Tracef(TRCSEVINFO, "sigma (*this)[]._yHeight = %ld, _yHeight = %ld", yHeight, _yHeight); AssertSz(FALSE, "CDisplayML::CheckLineArray: sigma(*this)[]._yHeight != _yHeight"); } }
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%x): \""), ili, pli->_cch, pli->_xWidth + pli->_xLineOverhang, pli->_yHeight, pli->_bFlags); 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 _dyFirstVisible */ void CDisplayML::CheckView() { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CheckView");
LONG yHeight; VerifyFirstVisible(&yHeight);
if(yHeight != _yScroll + _dyFirstVisible) { Tracef(TRCSEVINFO, "sigma CLine._yHeight = %ld, CDisplay.yFirstLine = %ld", yHeight, _yScroll + _dyFirstVisible); AssertSz(FALSE, "CLine._yHeight != VIEW.yFirstLine"); } }
/*
* 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 yHeight;
for(cchSum = yHeight = 0; ili--; pli++) { cchSum += pli->_cch; yHeight += pli->GetHeight(); }
if(pHeight) *pHeight = yHeight;
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
|