3653 lines
90 KiB
C++
3653 lines
90 KiB
C++
/*
|
|
* @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
|
|
|
|
|