3880 lines
96 KiB
C++
3880 lines
96 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-2000, Microsoft Corporation. All rights reserved.
|
||
|
*/
|
||
|
|
||
|
#include "_common.h"
|
||
|
#include "_dispml.h"
|
||
|
#include "_edit.h"
|
||
|
#include "_font.h"
|
||
|
#include "_measure.h"
|
||
|
#include "_render.h"
|
||
|
#include "_select.h"
|
||
|
#include "_dfreeze.h"
|
||
|
|
||
|
/*
|
||
|
#include "icecap.h"
|
||
|
|
||
|
class CCapProfile
|
||
|
{
|
||
|
public:
|
||
|
CCapProfile() { StartProfile(PROFILE_THREADLEVEL, PROFILE_CURRENTID); }
|
||
|
~CCapProfile() { StopProfile(PROFILE_THREADLEVEL, PROFILE_CURRENTID); }
|
||
|
};
|
||
|
*/
|
||
|
ASSERTDATA
|
||
|
|
||
|
//
|
||
|
// Invariant support
|
||
|
//
|
||
|
#define DEBUG_CLASSNAME CDisplayML
|
||
|
#include "_invar.h"
|
||
|
|
||
|
// Timer tick counts for background task
|
||
|
#define cmsecBgndInterval 300
|
||
|
#define cmsecBgndBusy 100
|
||
|
|
||
|
// Lines ahead
|
||
|
const LONG cExtraBeforeLazy = 60;
|
||
|
|
||
|
// cursor. NB! 6144 is not a measured number; 4096 was the former number,
|
||
|
// and it wasn't measured either; it just seemed like a good one. We bumped
|
||
|
// the number to 6144 as a safe-fix to a problem which caused cursor-flashing
|
||
|
// for the eBook reader. The eBook reader's idle process pumps up to
|
||
|
// 5120 characters into RichEdit between ReCalc attempts. However, each
|
||
|
// recalc can still work on more that 5120 characters; if the
|
||
|
// insertion started at the middle of a line, then recalc starts
|
||
|
// at the beginning of the line, picking up a few extra characters.
|
||
|
#define NUMCHARFORWAITCURSOR 6144
|
||
|
|
||
|
#ifndef DEBUG
|
||
|
#define CheckView()
|
||
|
#define CheckLineArray()
|
||
|
#endif
|
||
|
|
||
|
|
||
|
// =========================== CDisplayML =====================================================
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
/*
|
||
|
* CDisplayML::Invariant
|
||
|
*
|
||
|
* @mfunc Make sure the display is in a valid state
|
||
|
*
|
||
|
* @rdesc TRUE if the tests succeeded, FALSE otherwise
|
||
|
*/
|
||
|
BOOL CDisplayML::Invariant(void) const
|
||
|
{
|
||
|
CDisplay::Invariant();
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
#endif // DEBUG
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::CalcScrollHeight()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Calculate the maximum Y scroll position.
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Maximum possible scrolling position
|
||
|
*
|
||
|
* @devnote
|
||
|
* This routine exists because plain text controls do not have
|
||
|
* the auto-EOP and so the scroll height is different than
|
||
|
* the height of the control if the text ends in an EOP type
|
||
|
* character.
|
||
|
*/
|
||
|
LONG CDisplayML::CalcScrollHeight(LONG dvp) const
|
||
|
{
|
||
|
// The max scroll height for plain text controls is calculated
|
||
|
// differently because they don't have an automatic EOP character.
|
||
|
if(!_ped->IsRich() && Count())
|
||
|
{
|
||
|
// If last character is an EOP, bump scroll height
|
||
|
CLine *pli = Elem(Count() - 1); // Get last line in array
|
||
|
if(pli->_cchEOP)
|
||
|
dvp += pli->GetHeight();
|
||
|
}
|
||
|
return dvp;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::GetMaxVpScroll()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Calculate the maximum Y scroll position.
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Maximum possible scrolling position
|
||
|
*
|
||
|
* @devnote
|
||
|
* This routine exists because we may have to come back and modify this
|
||
|
* calculation for 1.0 compatibility. If we do, this routine only needs
|
||
|
* to be changed in one place rather than the three at which it is used.
|
||
|
*
|
||
|
*/
|
||
|
inline LONG CDisplayML::GetMaxVpScroll() const
|
||
|
{
|
||
|
// The following code is turn off because we don't want to support
|
||
|
// 1.0 mode unless someone complained about it.
|
||
|
#if 0
|
||
|
if (_ped->Get10Mode())
|
||
|
{
|
||
|
// Ensure last line is always visible
|
||
|
// (use dy as temp to calculate max scroll)
|
||
|
vpScroll = Elem(max(0, Count() - 1))->_dvp;
|
||
|
|
||
|
if(vpScroll > _dvpView)
|
||
|
vpScroll = _dvpView;
|
||
|
|
||
|
vpScroll = _dvp - vpScroll;
|
||
|
}
|
||
|
#endif //0
|
||
|
|
||
|
return CalcScrollHeight(_dvp);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::ConvertScrollToVPos(vPos)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Calculate the real scroll position from the scroll position
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Y position from scroll
|
||
|
*
|
||
|
* @devnote
|
||
|
* This routine exists because the thumb position messages
|
||
|
* are limited to 16-bits so we extrapolate when the V position
|
||
|
* gets greater than that.
|
||
|
*/
|
||
|
LONG CDisplayML::ConvertScrollToVPos(
|
||
|
LONG vPos) //@parm Scroll position
|
||
|
{
|
||
|
// Get maximum scroll range
|
||
|
LONG vpRange = GetMaxVpScroll();
|
||
|
|
||
|
// Has maximum scroll range exceeded 16-bits?
|
||
|
if(vpRange >= _UI16_MAX)
|
||
|
{
|
||
|
// Yes - Extrapolate to "real" vPos
|
||
|
vPos = MulDiv(vPos, vpRange, _UI16_MAX);
|
||
|
}
|
||
|
return vPos;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::ConvertVPosToScrollPos()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Calculate the scroll position from the V position in the document.
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Scroll position from V position
|
||
|
*
|
||
|
* @devnote
|
||
|
* This routine exists because the thumb position messages
|
||
|
* are limited to 16-bits so we extrapolate when the V position
|
||
|
* gets greater than that.
|
||
|
*
|
||
|
*/
|
||
|
inline LONG CDisplayML::ConvertVPosToScrollPos(
|
||
|
LONG vPos) //@parm V position in document
|
||
|
{
|
||
|
// Get maximum scroll range
|
||
|
LONG vRange = GetMaxVpScroll();
|
||
|
|
||
|
// Has maximum scroll range exceeded 16-bits?
|
||
|
if(vRange >= _UI16_MAX)
|
||
|
{
|
||
|
// Yes - Extrapolate to "real" vPos
|
||
|
vPos = MulDiv(vPos, _UI16_MAX, vRange);
|
||
|
}
|
||
|
return vPos;
|
||
|
}
|
||
|
|
||
|
CDisplayML::CDisplayML (CTxtEdit* ped)
|
||
|
: CDisplay (ped), _pddTarget(NULL)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CDisplayML");
|
||
|
|
||
|
Assert(!_dulTarget && !_dvlTarget);
|
||
|
|
||
|
_fMultiLine = TRUE;
|
||
|
}
|
||
|
|
||
|
CDisplayML::~CDisplayML()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::~CDisplayML");
|
||
|
|
||
|
delete _pddTarget;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::Init()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Init this display for the screen
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE iff initialization succeeded
|
||
|
*/
|
||
|
BOOL CDisplayML::Init()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::Init");
|
||
|
|
||
|
// Initialize our base class
|
||
|
if(!CDisplay::Init())
|
||
|
return FALSE;
|
||
|
|
||
|
AssertSz(_ped, "CDisplayML::Init(): _ped not initialized in display");
|
||
|
// Verify allocation zeroed memory out
|
||
|
Assert(!_vpCalcMax && !_dupLineMax && !_dvp && !_cpMin);
|
||
|
Assert(!_fBgndRecalc && !_fVScrollEnabled && !_fUScrollEnabled);
|
||
|
|
||
|
// The printer view is not main, therefore we do this to make
|
||
|
// sure scroll bars are not created for print views.
|
||
|
DWORD dwScrollBars = _ped->TxGetScrollBars();
|
||
|
|
||
|
if(IsMain() && (dwScrollBars & ES_DISABLENOSCROLL))
|
||
|
{
|
||
|
if(dwScrollBars & WS_VSCROLL)
|
||
|
{
|
||
|
// This causes wlm to assert on the mac. something about
|
||
|
// scrollbar being disabled
|
||
|
_ped->TxSetScrollRange (SB_VERT, 0, 1, TRUE);
|
||
|
_ped->TxEnableScrollBar(SB_VERT, ESB_DISABLE_BOTH);
|
||
|
}
|
||
|
|
||
|
// Set horizontal scroll range and pos
|
||
|
if(dwScrollBars & WS_HSCROLL)
|
||
|
{
|
||
|
_ped->TxSetScrollRange (SB_HORZ, 0, 1, TRUE);
|
||
|
_ped->TxEnableScrollBar(SB_HORZ, ESB_DISABLE_BOTH);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SetWordWrap(_ped->TxGetWordWrap());
|
||
|
_cpFirstVisible = _cpMin;
|
||
|
|
||
|
Assert(!_upScroll && !_vpScroll && !_iliFirstVisible &&
|
||
|
!_cpFirstVisible && !_dvpFirstVisible);
|
||
|
|
||
|
_TEST_INVARIANT_
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//================================ Device drivers ===================================
|
||
|
/*
|
||
|
* CDisplayML::SetMainTargetDC(hdc, dulTarget)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Sets a target device for this display and updates view
|
||
|
*
|
||
|
* @devnote
|
||
|
* Target device can't be a metafile (can't get char width out of a
|
||
|
* metafile)
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE if success
|
||
|
*/
|
||
|
BOOL CDisplayML::SetMainTargetDC (
|
||
|
HDC hdc, //@parm Target DC, NULL for same as rendering device
|
||
|
LONG dulTarget) //@parm Max line width (not used for screen)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::SetMainTargetDC");
|
||
|
|
||
|
if(SetTargetDC(hdc))
|
||
|
{
|
||
|
// This is here because this is what RE 1.0 did.
|
||
|
SetWordWrap(hdc || !dulTarget);
|
||
|
|
||
|
// If dulTarget is greater than zero, then the caller is
|
||
|
// trying to set the maximum width of the window (for measuring,
|
||
|
// line breaking, etc.) However,in order to make our measuring
|
||
|
// algorithms more reasonable, we force the max size to be
|
||
|
// *at least* as wide as the width of a character.
|
||
|
// Note that dulTarget = 0 means use the view rect width
|
||
|
_dulTarget = (dulTarget <= 0) ? 0 : max(DXtoLX(GetDupSystemFont()), dulTarget);
|
||
|
// Need to do a full recalc. If it fails, it fails, the lines are
|
||
|
// left in a reasonable state. No need to call WaitForRecalc()
|
||
|
// because UpdateView() starts at position zero and we're always
|
||
|
// calc'd up to there
|
||
|
CDisplay::UpdateView();
|
||
|
|
||
|
// Caret/selection has most likely moved
|
||
|
CTxtSelection *psel = _ped->GetSelNC();
|
||
|
if(psel)
|
||
|
psel->UpdateCaret(FALSE);
|
||
|
return TRUE;
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// Useful for both main and printing devices. jonmat 6/08/1995
|
||
|
BOOL CDisplayML::SetTargetDC( HDC hdc, LONG dxpInch, LONG dypInch)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::SetTargetDC");
|
||
|
|
||
|
CDevDesc *pddTarget = NULL;
|
||
|
|
||
|
// Don't allow metafiles to be set as the target device
|
||
|
if(hdc && GetDeviceCaps(hdc, TECHNOLOGY) == DT_METAFILE)
|
||
|
return FALSE;
|
||
|
|
||
|
if(hdc)
|
||
|
{
|
||
|
// Allocate device first to see if we can. We don't want to change
|
||
|
// our state if this is going to fail.
|
||
|
pddTarget = new CDevDesc(_ped);
|
||
|
if(!pddTarget)
|
||
|
return FALSE; // We couldn't so we are done
|
||
|
}
|
||
|
|
||
|
// Remove any cached information for the old target device
|
||
|
if(_pddTarget)
|
||
|
{
|
||
|
delete _pddTarget;
|
||
|
_pddTarget = NULL;
|
||
|
}
|
||
|
if(hdc)
|
||
|
{
|
||
|
_pddTarget = pddTarget; // Update device because we have one
|
||
|
_pddTarget->SetDC(hdc, dxpInch, dypInch);
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
//================================= Line recalc ==============================
|
||
|
/*
|
||
|
* CDisplayML::RecalcScrollBars()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Recalculate the scroll bars if the view has changed.
|
||
|
*
|
||
|
*
|
||
|
* @devnote There is a possibility of recursion here, so we
|
||
|
* need to protect ourselves.
|
||
|
*
|
||
|
* To visualize this, consider two types of characters, 'a' characters
|
||
|
* which are small in height and 'A' 's which are really tall, but the same
|
||
|
* width as an 'a'. So if I have
|
||
|
*
|
||
|
* a a A <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(rtp, fWait)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Recalc all line breaks.
|
||
|
* This method does a lazy calc after the last visible line
|
||
|
* except for a bottomless control
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE if success
|
||
|
*/
|
||
|
BOOL CDisplayML::RecalcLines (
|
||
|
CRchTxtPtr &rtp, //@parm Where change happened
|
||
|
BOOL fWait) //@parm Recalc lines down to _cpWait/_vpWait; then be lazy
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcLines");
|
||
|
|
||
|
LONG cliWait = cExtraBeforeLazy; // Extra lines before being lazy
|
||
|
BOOL fDone = TRUE;
|
||
|
BOOL fFirstInPara = TRUE;
|
||
|
CLine * pliNew = NULL;
|
||
|
LONG dupLineMax;
|
||
|
LONG dvp = 0;
|
||
|
LONG cchText = _ped->GetTextLength();
|
||
|
BOOL fWaitingForFirstVisible = TRUE;
|
||
|
LONG dvpView = _dvpView;
|
||
|
LONG dvpScrollOld = GetMaxVpScroll();
|
||
|
LONG dvpScrollNew;
|
||
|
|
||
|
DeleteSubLayouts(0, -1);
|
||
|
Remove(0, -1); // Remove all old lines from *this
|
||
|
_vpCalcMax = 0; // Set both maxes to start of text
|
||
|
_cpCalcMax = 0;
|
||
|
|
||
|
// Don't stop at bottom of view if we're bottomless and active.
|
||
|
if(!_ped->TxGetAutoSize() && IsActive())
|
||
|
{
|
||
|
// Be lazy - don't bother going past visible portion
|
||
|
_cpWait = -1;
|
||
|
_vpWait = -1;
|
||
|
fWait = TRUE;
|
||
|
}
|
||
|
|
||
|
CMeasurer me(this, rtp);
|
||
|
me.SetCp(0);
|
||
|
me.SetNumber(0);
|
||
|
|
||
|
// The following loop generates new lines
|
||
|
while(me.GetCp() < cchText)
|
||
|
{
|
||
|
// Add one new line
|
||
|
pliNew = Add(1, NULL);
|
||
|
if (!pliNew)
|
||
|
{
|
||
|
_ped->GetCallMgr()->SetOutOfMemory();
|
||
|
TRACEWARNSZ("Out of memory Recalc'ing lines");
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
// Stuff text into new line
|
||
|
UINT uiFlags = MEASURE_BREAKATWORD |
|
||
|
(fFirstInPara ? MEASURE_FIRSTINPARA : 0);
|
||
|
|
||
|
Tracef(TRCSEVINFO, "Measuring new line from cp = %d", me.GetCp());
|
||
|
|
||
|
if(!Measure(me, pliNew, Count() - 1, uiFlags))
|
||
|
{
|
||
|
Assert(FALSE);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
fFirstInPara = pliNew->_fHasEOP;
|
||
|
dvp += pliNew->GetHeight();
|
||
|
_cpCalcMax = me.GetCp();
|
||
|
AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax");
|
||
|
|
||
|
if(fWait)
|
||
|
{
|
||
|
// Do we want to do a background recalc? - the answer is yes if
|
||
|
// three things are true: (1) We have recalc'd beyond the old first
|
||
|
// visible character, (2) We have recalc'd beyond the visible
|
||
|
// portion of the screen and (3) we have gone beyond the next
|
||
|
// cExtraBeforeLazy lines to make page down go faster.
|
||
|
|
||
|
if(fWaitingForFirstVisible)
|
||
|
{
|
||
|
if(me.GetCp() > _cpFirstVisible)
|
||
|
{
|
||
|
_vpWait = dvp + dvpView;
|
||
|
fWaitingForFirstVisible = FALSE;
|
||
|
}
|
||
|
}
|
||
|
else if(dvp > _vpWait && cliWait-- <= 0 && me._rgpobjWrap.Count() == 0)
|
||
|
{
|
||
|
fDone = FALSE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Create 1 line for empty controls
|
||
|
if(!Count())
|
||
|
CreateEmptyLine();
|
||
|
|
||
|
Paginate(0);
|
||
|
|
||
|
_vpCalcMax = dvp;
|
||
|
_fRecalcDone = fDone;
|
||
|
_fNeedRecalc = FALSE;
|
||
|
dvpScrollNew = CalcScrollHeight(dvp);
|
||
|
|
||
|
if(fDone && (dvp != _dvp || dvpScrollNew != dvpScrollOld)
|
||
|
|| dvpScrollNew > dvpScrollOld)
|
||
|
{
|
||
|
_fViewChanged = TRUE;
|
||
|
}
|
||
|
|
||
|
_dvp = dvp;
|
||
|
dupLineMax = CalcDisplayDup();
|
||
|
if(fDone && dupLineMax != _dupLineMax || dupLineMax > _dupLineMax)
|
||
|
{
|
||
|
_dupLineMax = dupLineMax;
|
||
|
_fViewChanged = TRUE;
|
||
|
}
|
||
|
|
||
|
Tracef(TRCSEVINFO, "CDisplayML::RecalcLine() - Done. Recalced down to line #%d", Count());
|
||
|
|
||
|
if(!fDone) // if not done, do rest in background
|
||
|
fDone = StartBackgroundRecalc();
|
||
|
|
||
|
if(fDone)
|
||
|
{
|
||
|
_vpWait = -1;
|
||
|
_cpWait = -1;
|
||
|
CheckLineArray();
|
||
|
_fLineRecalcErr = FALSE;
|
||
|
}
|
||
|
|
||
|
#if defined(DEBUG) && !defined(NOFULLDEBUG)
|
||
|
if( 1 )
|
||
|
{
|
||
|
_TEST_INVARIANT_
|
||
|
}
|
||
|
//Array memory allocation tracking
|
||
|
{
|
||
|
void **pv = (void**)((char*)this + sizeof(CDisplay) + sizeof(void*));
|
||
|
PvSet(*pv);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return TRUE;
|
||
|
|
||
|
err:
|
||
|
TRACEERRORSZ("CDisplayML::RecalcLines() failed");
|
||
|
|
||
|
if(!_fLineRecalcErr)
|
||
|
{
|
||
|
_cpCalcMax = me.GetCp();
|
||
|
AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax");
|
||
|
_vpCalcMax = dvp;
|
||
|
_fLineRecalcErr = TRUE;
|
||
|
_ped->GetCallMgr()->SetOutOfMemory();
|
||
|
_fLineRecalcErr = FALSE; // fix up CArray & bail
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::RecalcLines(rtp, cchOld, cchNew, fBackground, fWait, pled)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Recompute line breaks after text modification
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE if success
|
||
|
*
|
||
|
* @devnote
|
||
|
* Most people call this the trickiest piece of code in RichEdit...
|
||
|
*/
|
||
|
BOOL CDisplayML::RecalcLines (
|
||
|
CRchTxtPtr &rtp, //@parm Where change happened
|
||
|
LONG cchOld, //@parm Count of chars deleted
|
||
|
LONG cchNew, //@parm Count of chars added
|
||
|
BOOL fBackground, //@parm This method called as background process
|
||
|
BOOL fWait, //@parm Recalc lines down to _cpWait/_vpWait; then be lazy
|
||
|
CLed *pled) //@parm Returns edit impact on lines (can be NULL)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcLines");
|
||
|
|
||
|
LONG cchEdit;
|
||
|
LONG cchSkip;
|
||
|
LONG cliBackedUp = 0;
|
||
|
LONG cliWait = cExtraBeforeLazy;
|
||
|
BOOL fDone = TRUE;
|
||
|
BOOL fFirstInPara = TRUE;
|
||
|
LONG ili;
|
||
|
CLed led;
|
||
|
LONG lT = 0; // long Temporary
|
||
|
LONG iliMain;
|
||
|
CLine * pliMain;
|
||
|
CLine * pliNew;
|
||
|
CLinePtr rpOld(this);
|
||
|
LONG dupLineMax;
|
||
|
LONG dvp;
|
||
|
LONG dvpPrev = 0;
|
||
|
LONG cchText = _ped->GetTextLength();
|
||
|
UINT uiFlags;
|
||
|
BOOL fReplaceResult;
|
||
|
LONG dvpExtraLine = 0;
|
||
|
LONG dvpScrollOld = GetMaxVpScroll();
|
||
|
LONG dvpScrollNew;
|
||
|
WORD wNumber = 0;
|
||
|
CLineArray rgliNew;
|
||
|
DWORD dwBgndTickMax = fBackground ? GetTickCount() + cmsecBgndBusy : 0;
|
||
|
|
||
|
if(!pled)
|
||
|
pled = &led;
|
||
|
|
||
|
#if defined(DEBUG) || defined(_RELEASE_ASSERTS_)
|
||
|
LONG cp = rtp.GetCp();
|
||
|
|
||
|
if(cp > _cpCalcMax)
|
||
|
Tracef(TRCSEVERR, "rtp %ld, _cpCalcMax %ld", cp, _cpCalcMax);
|
||
|
|
||
|
AssertSz(cp <= _cpCalcMax, "CDisplayML::RecalcLines Caller didn't setup RecalcLines()");
|
||
|
AssertSz(!(fWait && fBackground), "CDisplayML::RecalcLines wait and background both true");
|
||
|
AssertSz(!(fWait && (-1 == _cpWait) && (-1 == _vpWait)),
|
||
|
"CDisplayML::RecalcLines background recalc parms invalid");
|
||
|
#endif
|
||
|
|
||
|
// We will not use background recalc if this is already a background recalc,
|
||
|
// or if the control is not active or if this is an auto sized control.
|
||
|
if(!IsActive() || _ped->TxGetAutoSize())
|
||
|
fWait = FALSE;
|
||
|
|
||
|
// Init line pointer on old CLayout and backup to start of line
|
||
|
rpOld.SetCp(rtp.GetCp(), FALSE);
|
||
|
cchSkip = rpOld.GetIch();
|
||
|
rpOld.Move(-cchSkip); // Point rp at 1st char in line
|
||
|
|
||
|
ili = rpOld; // Save line # at change for
|
||
|
if(!Elem(ili)->IsNestedLayout()) // numbering
|
||
|
{
|
||
|
if(ili && (IsInOutlineView() || // Back up if not first number
|
||
|
rtp.GetPF()->IsListNumbered())) // in list or if in OutlineView
|
||
|
{ // (Outline symbol may change)
|
||
|
ili--;
|
||
|
}
|
||
|
|
||
|
// Back up at least one line in case we can now fit more on it
|
||
|
// If on a line border, e.g., just inserted an EOP, backup 2; else 1
|
||
|
lT = !cchSkip + 1;
|
||
|
|
||
|
while(rpOld > 0 &&
|
||
|
((lT-- && (!rpOld[-1]._cchEOP || ili < rpOld)) ||
|
||
|
(rpOld[-1]._cObjectWrapLeft || rpOld[-1]._cObjectWrapRight)))
|
||
|
{
|
||
|
cliBackedUp++;
|
||
|
rpOld--;
|
||
|
cchSkip += rpOld->_cch;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Init measurer at rtp
|
||
|
CMeasurer me(this, rtp);
|
||
|
|
||
|
me.Move(-cchSkip); // Point at start of text to measure
|
||
|
cchEdit = cchNew + cchSkip; // Number of chars affected by edit
|
||
|
me.SetNumber(rpOld.GetNumber()); // Initialize list number
|
||
|
|
||
|
// Determine whether we're on first line of paragraph
|
||
|
if(rpOld > 0)
|
||
|
{
|
||
|
fFirstInPara = rpOld[-1]._fHasEOP;
|
||
|
me.SetIhyphPrev(rpOld[-1]._ihyph);
|
||
|
}
|
||
|
|
||
|
dvp = VposFromLine(this, rpOld);
|
||
|
|
||
|
// Update first-affected and pre-edit-match lines in pled
|
||
|
pled->_iliFirst = rpOld;
|
||
|
pled->_cpFirst = pled->_cpMatchOld = me.GetCp();
|
||
|
pled->_vpFirst = pled->_vpMatchOld = dvp;
|
||
|
AssertSz(pled->_vpFirst >= 0, "CDisplayML::RecalcLines _vpFirst < 0");
|
||
|
|
||
|
Tracef(TRCSEVINFO, "Start recalcing from line #%d, cp=%d", pled->_iliFirst, pled->_cpFirst);
|
||
|
|
||
|
// In case of error, set both maxes to where we are now
|
||
|
_vpCalcMax = dvp;
|
||
|
_cpCalcMax = me.GetCp();
|
||
|
AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax");
|
||
|
|
||
|
// If we are past the requested area to recalc and background recalc is
|
||
|
// allowed, then just go directly to background recalc. If there is no
|
||
|
// height, we just go ahead and calculate some lines anyway. This
|
||
|
// prevents any weird background recalcs from occuring when it is
|
||
|
// unnecessary to go into background recalc.
|
||
|
if(fWait && _vpWait > 0 && dvp > _vpWait && me.GetCp() > _cpWait)
|
||
|
{
|
||
|
_dvp = dvp;
|
||
|
DeleteSubLayouts((LONG)rpOld, -1);
|
||
|
rpOld.Remove(-1); // Remove all old lines from here on
|
||
|
StartBackgroundRecalc(); // Start up the background recalc
|
||
|
pled->SetMax(this);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
pliMain = NULL;
|
||
|
iliMain = rpOld.GetLineIndex();
|
||
|
if (iliMain)
|
||
|
{
|
||
|
iliMain--;
|
||
|
pliMain = rpOld.GetLine() - 1;
|
||
|
}
|
||
|
pliNew = NULL;
|
||
|
|
||
|
// The following loop generates new lines for each line we backed
|
||
|
// up over and for lines directly affected by edit
|
||
|
while(cchEdit > 0)
|
||
|
{
|
||
|
pliNew = rgliNew.Add(1, NULL); // Add one new line
|
||
|
if (!pliNew)
|
||
|
{
|
||
|
TRACEWARNSZ("CDisplayML::RecalcLines unable to alloc additional CLine in CLineArray");
|
||
|
goto errspace;
|
||
|
}
|
||
|
|
||
|
uiFlags = MEASURE_BREAKATWORD | (fFirstInPara ? MEASURE_FIRSTINPARA : 0);
|
||
|
|
||
|
// Stuff text into new line
|
||
|
Tracef(TRCSEVINFO, "Measuring new line from cp = %d", me.GetCp());
|
||
|
|
||
|
dvpExtraLine = 0;
|
||
|
if(!Measure(me, pliNew, rgliNew.Count() - 1, uiFlags, 0, iliMain, pliMain, &dvpExtraLine))
|
||
|
{
|
||
|
Assert(FALSE);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
Assert(pliNew->_cch);
|
||
|
|
||
|
fFirstInPara = pliNew->_fHasEOP;
|
||
|
dvpPrev = dvp;
|
||
|
dvp += pliNew->GetHeight();
|
||
|
cchEdit -= pliNew->_cch;
|
||
|
AssertSz(cchEdit + me.GetCp() <= cchText, "CDisplayML::RecalcLines: want to measure beyond EOD");
|
||
|
|
||
|
// Calculate on what line the edit started. We do this because
|
||
|
// we want to render the first edited line off screen so if
|
||
|
// the line is being edited via the keyboard we don't clip
|
||
|
// any characters.
|
||
|
if(cchSkip > 0)
|
||
|
{
|
||
|
// Check whether we backed up and the line we are examining
|
||
|
// changed at all. Even if it didn't change in outline view
|
||
|
// have to redraw in case outline symbol changes
|
||
|
if (cliBackedUp && cchSkip >= pliNew->_cch &&
|
||
|
pliNew->IsEqual(*(CLine *)(rpOld.GetLine())) && !IsInOutlineView()
|
||
|
&& !pliNew->_cObjectWrapLeft && !pliNew->_cObjectWrapRight)
|
||
|
{
|
||
|
// Perfect match, this line was not the first edited.
|
||
|
Tracef(TRCSEVINFO, "New line matched old line #%d", (LONG)rpOld);
|
||
|
|
||
|
cchSkip -= rpOld->_cch;
|
||
|
|
||
|
// Update first affected line and match in pled
|
||
|
pled->_iliFirst++;
|
||
|
pled->_cpFirst += rpOld->_cch;
|
||
|
pled->_cpMatchOld += rpOld->_cch;
|
||
|
pled->_vpFirst += rpOld->GetHeight();
|
||
|
AssertSz(pled->_vpFirst >= 0, "CDisplayML::RecalcLines _vpFirst < 0");
|
||
|
pled->_vpMatchOld += rpOld->GetHeight();
|
||
|
cliBackedUp--;
|
||
|
|
||
|
rgliNew.Clear(AF_KEEPMEM); // Discard new line
|
||
|
if(!(rpOld++)) // Next line
|
||
|
cchSkip = 0;
|
||
|
}
|
||
|
else // No match in the line, so
|
||
|
cchSkip = 0; // this line is the first to
|
||
|
} // be edited
|
||
|
|
||
|
if(fBackground && GetTickCount() >= dwBgndTickMax)
|
||
|
{
|
||
|
fDone = FALSE; // took too long, stop for now
|
||
|
goto no_match;
|
||
|
}
|
||
|
|
||
|
if (fWait && dvp > _vpWait && me.GetCp() > _cpWait && cliWait-- <= 0)
|
||
|
{
|
||
|
// Not really done, just past region we're waiting for
|
||
|
// so let background recalc take it from here
|
||
|
fDone = FALSE;
|
||
|
goto no_match;
|
||
|
}
|
||
|
} // while(cchEdit > 0) { }
|
||
|
|
||
|
Tracef(TRCSEVINFO, "Done recalcing edited text. Created %d new lines", rgliNew.Count());
|
||
|
|
||
|
// Edit lines have been exhausted. Continue breaking lines,
|
||
|
// but try to match new & old breaks
|
||
|
|
||
|
wNumber = me._wNumber;
|
||
|
|
||
|
while(me.GetCp() < cchText)
|
||
|
{
|
||
|
// We are trying for a match so assume that there
|
||
|
// is a match after all
|
||
|
BOOL frpOldValid = TRUE;
|
||
|
|
||
|
// Look for match in old line break CArray
|
||
|
lT = me.GetCp() - cchNew + cchOld;
|
||
|
while (rpOld.IsValid() && pled->_cpMatchOld < lT)
|
||
|
{
|
||
|
pled->_vpMatchOld += rpOld->GetHeight();
|
||
|
pled->_cpMatchOld += rpOld->_cch;
|
||
|
|
||
|
if(!rpOld.NextRun())
|
||
|
{
|
||
|
// No more line array entries so we can give up on
|
||
|
// trying to match for good.
|
||
|
frpOldValid = FALSE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If perfect match, stop.
|
||
|
if (frpOldValid && rpOld.IsValid() && pled->_cpMatchOld == lT &&
|
||
|
rpOld->_cch && me._wNumber == rpOld->_bNumber)
|
||
|
{
|
||
|
Tracef(TRCSEVINFO, "Found match with old line #%d", rpOld.GetLineIndex());
|
||
|
|
||
|
// Update fliFirstInPara flag in 1st old line that matches. Note
|
||
|
// that if the new array doesn't have any lines, we have to look
|
||
|
// into the line array preceding the current change.
|
||
|
rpOld->_fFirstInPara = TRUE;
|
||
|
if(rgliNew.Count() > 0)
|
||
|
{
|
||
|
if(!(rgliNew.Elem(rgliNew.Count() - 1)->_fHasEOP))
|
||
|
rpOld->_fFirstInPara = FALSE;
|
||
|
}
|
||
|
else if(rpOld >= pled->_iliFirst && pled->_iliFirst)
|
||
|
{
|
||
|
if(!(rpOld[pled->_iliFirst - rpOld - 1]._fHasEOP))
|
||
|
rpOld->_fFirstInPara = FALSE;
|
||
|
}
|
||
|
|
||
|
pled->_iliMatchOld = rpOld;
|
||
|
|
||
|
// Replace old lines by new ones
|
||
|
lT = rpOld - pled->_iliFirst;
|
||
|
rpOld = pled->_iliFirst;
|
||
|
DeleteSubLayouts(pled->_iliFirst, lT);
|
||
|
if(!rpOld.Replace (lT, &rgliNew))
|
||
|
{
|
||
|
TRACEWARNSZ("CDisplayML::RecalcLines unable to alloc additional CLines in rpOld");
|
||
|
goto errspace;
|
||
|
}
|
||
|
frpOldValid = rpOld.ChgRun(rgliNew.Count());
|
||
|
rgliNew.Clear(AF_KEEPMEM); // Clear aux array
|
||
|
|
||
|
// Remember information about match after editing
|
||
|
Assert((cp = rpOld.CalculateCp()) == me.GetCp());
|
||
|
pled->_vpMatchOld += dvpExtraLine;
|
||
|
pled->_vpMatchNew = dvp + dvpExtraLine;
|
||
|
pled->_vpMatchNewTop = dvpPrev;
|
||
|
pled->_iliMatchNew = rpOld;
|
||
|
pled->_cpMatchNew = me.GetCp();
|
||
|
|
||
|
// Compute height and cp after all matches
|
||
|
_cpCalcMax = me.GetCp();
|
||
|
AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax");
|
||
|
|
||
|
if(frpOldValid && rpOld.IsValid())
|
||
|
{
|
||
|
do
|
||
|
{
|
||
|
dvp += rpOld->GetHeight();
|
||
|
_cpCalcMax += rpOld->_cch;
|
||
|
}
|
||
|
while( rpOld.NextRun() );
|
||
|
#ifdef DEBUG
|
||
|
CTxtPtr tp(_ped, _cpCalcMax);
|
||
|
AssertSz(!IN_RANGE(STARTFIELD, tp.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Make sure _cpCalcMax is sane after the above update
|
||
|
AssertSz(_cpCalcMax <= cchText, "CDisplayML::RecalcLines match extends beyond EOF");
|
||
|
|
||
|
// We stop calculating here.Note that if _cpCalcMax < size
|
||
|
// of text, this means a background recalc is in progress.
|
||
|
// We will let that background recalc get the arrays
|
||
|
// fully in sync.
|
||
|
|
||
|
AssertSz(_cpCalcMax == cchText || _fBgndRecalc,
|
||
|
"CDisplayML::Match less but no background recalc");
|
||
|
|
||
|
if(_cpCalcMax != cchText)
|
||
|
{
|
||
|
// This is going to be finished by the background recalc
|
||
|
// so set the done flag appropriately.
|
||
|
fDone = FALSE;
|
||
|
}
|
||
|
goto match;
|
||
|
}
|
||
|
|
||
|
// Add a new line
|
||
|
pliNew = rgliNew.Add(1, NULL);
|
||
|
if(!pliNew)
|
||
|
{
|
||
|
TRACEWARNSZ("CDisplayML::RecalcLines unable to alloc additional CLine in CLineArray");
|
||
|
goto errspace;
|
||
|
}
|
||
|
|
||
|
Tracef(TRCSEVINFO, "Measuring new line from cp = %d", me.GetCp());
|
||
|
|
||
|
// Stuff some text into new line
|
||
|
wNumber = me._wNumber;
|
||
|
if(!Measure(me, pliNew, rgliNew.Count() - 1,
|
||
|
MEASURE_BREAKATWORD | (fFirstInPara ? MEASURE_FIRSTINPARA : 0), 0,
|
||
|
iliMain, pliMain))
|
||
|
{
|
||
|
Assert(FALSE);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
fFirstInPara = pliNew->_fHasEOP;
|
||
|
dvp += pliNew->GetHeight();
|
||
|
|
||
|
if(fBackground && GetTickCount() >= (DWORD)dwBgndTickMax)
|
||
|
{
|
||
|
fDone = FALSE; // Took too long, stop for now
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(fWait && dvp > _vpWait && me.GetCp() > _cpWait
|
||
|
&& cliWait-- <= 0 && me._rgpobjWrap.Count() == 0)
|
||
|
{ // Not really done, just past region we're
|
||
|
fDone = FALSE; // waiting for so let background recalc
|
||
|
break; // take it from here
|
||
|
}
|
||
|
} // while(me < cchText) ...
|
||
|
|
||
|
no_match:
|
||
|
// Didn't find match: whole line array from _iliFirst needs to be changed
|
||
|
pled->_iliMatchOld = Count();
|
||
|
pled->_cpMatchOld = cchText;
|
||
|
pled->_vpMatchNew = dvp;
|
||
|
pled->_vpMatchNewTop = dvpPrev;
|
||
|
pled->_vpMatchOld = _dvp;
|
||
|
_cpCalcMax = me.GetCp();
|
||
|
AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax");
|
||
|
|
||
|
// Replace old lines by new ones
|
||
|
rpOld = pled->_iliFirst;
|
||
|
|
||
|
// We store the result from the replace because although it can fail the
|
||
|
// fields used for first visible must be set to something sensible whether
|
||
|
// the replace fails or not. Further, the setting up of the first visible
|
||
|
// fields must happen after the Replace because the lines could have
|
||
|
// changed in length which in turns means that the first visible position
|
||
|
// has failed.
|
||
|
|
||
|
DeleteSubLayouts(rpOld, -1);
|
||
|
fReplaceResult = rpOld.Replace(-1, &rgliNew);
|
||
|
|
||
|
// _iliMatchNew & _cpMatchNew are used for first visible constants so we
|
||
|
// need to set them to something reasonable. In particular the rendering
|
||
|
// logic expects _cpMatchNew to be set to the first character of the first
|
||
|
// visible line. rpOld is used because it is convenient.
|
||
|
|
||
|
// Note we can't use RpBindToCp at this point because the first visible
|
||
|
// information is screwed up because we may have changed the line that
|
||
|
// the first visible cp is on.
|
||
|
rpOld.BindToCp(me.GetCp(), cchText);
|
||
|
pled->_iliMatchNew = rpOld.GetLineIndex();
|
||
|
pled->_cpMatchNew = me.GetCp() - rpOld.GetIch();
|
||
|
|
||
|
if (!fReplaceResult)
|
||
|
{
|
||
|
TRACEERRORSZ("CDisplayML::RecalcLines rpOld.Replace() failed");
|
||
|
goto errspace;
|
||
|
}
|
||
|
|
||
|
// Adjust first affected line if this line is gone
|
||
|
// after replacing by new lines
|
||
|
if(pled->_iliFirst >= Count() && Count() > 0)
|
||
|
{
|
||
|
Assert(pled->_iliFirst == Count());
|
||
|
pled->_iliFirst = Count() - 1;
|
||
|
pliNew = Elem(pled->_iliFirst);
|
||
|
pled->_vpFirst -= pliNew->GetHeight();
|
||
|
AssertSz(pled->_vpFirst >= 0, "CDisplayML::RecalcLines _vpFirst < 0");
|
||
|
pled->_cpFirst -= pliNew->_cch;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
if (_ped->GetTextLength())
|
||
|
Assert(Count());
|
||
|
#endif
|
||
|
|
||
|
//Create 1 line for empty controls
|
||
|
if(!Count())
|
||
|
CreateEmptyLine();
|
||
|
|
||
|
match:
|
||
|
_fRecalcDone = fDone;
|
||
|
_fNeedRecalc = FALSE;
|
||
|
_vpCalcMax = dvp;
|
||
|
|
||
|
Tracef(TRCSEVINFO, "CDisplayML::RecalcLine(rtp, ...) - Done. Recalced down to line #%d", Count() - 1);
|
||
|
|
||
|
// Clear wait fields since we want caller's to set them up.
|
||
|
_vpWait = -1;
|
||
|
_cpWait = -1;
|
||
|
|
||
|
if(fDone && fBackground)
|
||
|
{
|
||
|
TRACEINFOSZ("Background line recalc done");
|
||
|
_ped->TxKillTimer(RETID_BGND_RECALC);
|
||
|
_fBgndRecalc = FALSE;
|
||
|
_fRecalcDone = TRUE;
|
||
|
}
|
||
|
|
||
|
// Determine display height and update scrollbar
|
||
|
dvpScrollNew = CalcScrollHeight(dvp);
|
||
|
|
||
|
if (_fViewChanged || fDone && (dvp != _dvp || dvpScrollNew != dvpScrollOld)
|
||
|
|| dvpScrollNew > dvpScrollOld)
|
||
|
{
|
||
|
//!NOTE:
|
||
|
// UpdateScrollBar can cause a resize of the window by hiding or showing
|
||
|
// scrollbars. As a consequence of resizing the lines may get recalculated
|
||
|
// therefore updating _dvp to a new value, something != to dvp.
|
||
|
_dvp = dvp;
|
||
|
UpdateScrollBar(SB_VERT, TRUE);
|
||
|
}
|
||
|
else
|
||
|
_dvp = dvp; // Guarantee heights agree
|
||
|
|
||
|
// Determine display width and update scrollbar
|
||
|
dupLineMax = CalcDisplayDup();
|
||
|
if(_fViewChanged || (fDone && dupLineMax != _dupLineMax) || dupLineMax > _dupLineMax)
|
||
|
{
|
||
|
_dupLineMax = dupLineMax;
|
||
|
UpdateScrollBar(SB_HORZ, TRUE);
|
||
|
}
|
||
|
|
||
|
_fViewChanged = FALSE;
|
||
|
|
||
|
// If not done, do the rest in background
|
||
|
if(!fDone && !fBackground)
|
||
|
fDone = StartBackgroundRecalc();
|
||
|
|
||
|
if(fDone)
|
||
|
{
|
||
|
CheckLineArray();
|
||
|
_fLineRecalcErr = FALSE;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
if( 1 )
|
||
|
{
|
||
|
_TEST_INVARIANT_
|
||
|
}
|
||
|
#endif // DEBUG
|
||
|
|
||
|
Paginate(pled->_iliFirst);
|
||
|
return TRUE;
|
||
|
|
||
|
errspace:
|
||
|
_ped->GetCallMgr()->SetOutOfMemory();
|
||
|
_fNeedRecalc = TRUE;
|
||
|
_cpCalcMax = _vpCalcMax = 0;
|
||
|
_fLineRecalcErr = TRUE;
|
||
|
|
||
|
err:
|
||
|
if(!_fLineRecalcErr)
|
||
|
{
|
||
|
_cpCalcMax = me.GetCp();
|
||
|
AssertSz(!IN_RANGE(STARTFIELD, me.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax");
|
||
|
_vpCalcMax = dvp;
|
||
|
}
|
||
|
|
||
|
TRACEERRORSZ("CDisplayML::RecalcLines() failed");
|
||
|
|
||
|
if(!_fLineRecalcErr)
|
||
|
{
|
||
|
_fLineRecalcErr = TRUE;
|
||
|
_ped->GetCallMgr()->SetOutOfMemory();
|
||
|
_fLineRecalcErr = FALSE; // fix up CArray & bail
|
||
|
}
|
||
|
pled->SetMax(this);
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::CalcDisplayDup()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Calculates width of this display by walking line CArray and
|
||
|
* returning widest line. Used for horizontal scrollbar routines.
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Widest line width in display
|
||
|
*/
|
||
|
LONG CDisplayML::CalcDisplayDup()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CalcDisplayDup");
|
||
|
|
||
|
LONG dupLineMax = 0;
|
||
|
|
||
|
if (_ped->fInOurHost() && (_ped->GetHost())->TxGetHorzExtent(&dupLineMax) == S_OK)
|
||
|
{
|
||
|
return dupLineMax;
|
||
|
}
|
||
|
|
||
|
LONG ili = Count();
|
||
|
CLine *pli;
|
||
|
|
||
|
if(ili)
|
||
|
{
|
||
|
LONG dupLine;
|
||
|
pli = Elem(0);
|
||
|
|
||
|
for(dupLineMax = 0; ili--; pli++)
|
||
|
{
|
||
|
dupLine = pli->_upStart + pli->_dup;
|
||
|
dupLineMax = max(dupLineMax, dupLine);
|
||
|
}
|
||
|
}
|
||
|
return dupLineMax;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::StartBackgroundRecalc()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Starts background line recalc (at _cpCalcMax position)
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE if done with background recalc
|
||
|
*/
|
||
|
BOOL CDisplayML::StartBackgroundRecalc()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::StartBackgroundRecalc");
|
||
|
|
||
|
if(_fBgndRecalc)
|
||
|
return FALSE; // Already in background recalc
|
||
|
|
||
|
AssertSz(_cpCalcMax <= _ped->GetTextLength(), "_cpCalcMax > text length");
|
||
|
|
||
|
if(_cpCalcMax == _ped->GetTextLength())
|
||
|
return TRUE; // Enough chars are recalc'd
|
||
|
|
||
|
if(!_ped->TxSetTimer(RETID_BGND_RECALC, cmsecBgndInterval))
|
||
|
{
|
||
|
// Could not instantiate a timer so wait for recalculation
|
||
|
WaitForRecalc(_ped->GetTextLength(), -1);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
_fRecalcDone = FALSE;
|
||
|
_fBgndRecalc = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::StepBackgroundRecalc()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Steps background line recalc (at _cpCalcMax position)
|
||
|
* Called by timer proc and also when going inactive.
|
||
|
*/
|
||
|
void CDisplayML::StepBackgroundRecalc()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::StepBackgroundRecalc");
|
||
|
|
||
|
_TEST_INVARIANT_
|
||
|
|
||
|
if(!_fBgndRecalc) // Not in background recalc,
|
||
|
return; // so don't do anything
|
||
|
|
||
|
LONG cch = _ped->GetTextLength() - _cpCalcMax;
|
||
|
|
||
|
// Don't try recalc when processing OOM or had an error doing recalc or
|
||
|
// if we are asserting.
|
||
|
#ifdef DEBUG
|
||
|
if(_fInBkgndRecalc || _fLineRecalcErr)
|
||
|
{
|
||
|
if(_fInBkgndRecalc)
|
||
|
TRACEINFOSZ("avoiding reentrant background recalc");
|
||
|
else
|
||
|
TRACEINFOSZ("OOM: not stepping recalc");
|
||
|
return;
|
||
|
}
|
||
|
#else
|
||
|
if(_fInBkgndRecalc || _fLineRecalcErr)
|
||
|
return;
|
||
|
#endif
|
||
|
|
||
|
_fInBkgndRecalc = TRUE;
|
||
|
if(!IsActive())
|
||
|
{
|
||
|
// Background recalc is over if we are no longer active because
|
||
|
// we can no longer get the information we need for recalculating.
|
||
|
// But, if we are half recalc'd we need to set ourselves up to
|
||
|
// recalc again when we go active.
|
||
|
InvalidateRecalc();
|
||
|
cch = 0;
|
||
|
}
|
||
|
|
||
|
// Background recalc is over if no more chars or no longer active
|
||
|
if(cch <= 0)
|
||
|
{
|
||
|
TRACEINFOSZ("Background line recalc done");
|
||
|
_ped->TxKillTimer(RETID_BGND_RECALC);
|
||
|
_fBgndRecalc = FALSE;
|
||
|
_fRecalcDone = TRUE;
|
||
|
_fInBkgndRecalc = FALSE;
|
||
|
CheckLineArray();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CRchTxtPtr rtp(_ped, _cpCalcMax);
|
||
|
AssertSz(!IN_RANGE(STARTFIELD, rtp.GetPrevChar(), ENDFIELD), "Illegal cpCalcMax");
|
||
|
RecalcLines(rtp, cch, cch, TRUE, FALSE, NULL);
|
||
|
|
||
|
_fInBkgndRecalc = FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::WaitForRecalc(cpMax, vpMax)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Ensures that lines are recalced until a specific character
|
||
|
* position or vPos.
|
||
|
*
|
||
|
* @rdesc
|
||
|
* success
|
||
|
*/
|
||
|
BOOL CDisplayML::WaitForRecalc(
|
||
|
LONG cpMax, //@parm Position recalc up to (-1 to ignore)
|
||
|
LONG vpMax) //@parm vPos to recalc up to (-1 to ignore)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::WaitForRecalc");
|
||
|
|
||
|
_TEST_INVARIANT_
|
||
|
|
||
|
if(IsFrozen())
|
||
|
return TRUE;
|
||
|
|
||
|
BOOL fReturn = TRUE;
|
||
|
LONG cch;
|
||
|
|
||
|
if((vpMax < 0 || vpMax >= _vpCalcMax) &&
|
||
|
(cpMax < 0 || cpMax >= _cpCalcMax))
|
||
|
{
|
||
|
cch = _ped->GetTextLength() - _cpCalcMax;
|
||
|
if(cch > 0 || Count() == 0)
|
||
|
{
|
||
|
HCURSOR hcur = NULL;
|
||
|
|
||
|
_cpWait = cpMax;
|
||
|
_vpWait = vpMax;
|
||
|
|
||
|
if(cch > NUMCHARFORWAITCURSOR)
|
||
|
hcur = _ped->TxSetCursor(LoadCursor(0, IDC_WAIT));
|
||
|
TRACEINFOSZ("Lazy recalc");
|
||
|
|
||
|
CRchTxtPtr rtp(_ped, _cpCalcMax);
|
||
|
if(!_cpCalcMax || _fNeedRecalc)
|
||
|
{
|
||
|
fReturn = RecalcLines(rtp, TRUE);
|
||
|
RebindFirstVisible();
|
||
|
if(!fReturn)
|
||
|
InitVars();
|
||
|
}
|
||
|
else
|
||
|
fReturn = RecalcLines(rtp, cch, cch, FALSE, TRUE, NULL);
|
||
|
|
||
|
if(hcur)
|
||
|
_ped->TxSetCursor(hcur);
|
||
|
}
|
||
|
else if(!cch)
|
||
|
{
|
||
|
// If there was nothing else to calc, make sure that we think
|
||
|
// recalc is done.
|
||
|
#ifdef DEBUG
|
||
|
if( !_fRecalcDone )
|
||
|
{
|
||
|
TRACEWARNSZ("For some reason we didn't think background "
|
||
|
"recalc was done, but it was!!");
|
||
|
}
|
||
|
#endif // DEBUG
|
||
|
_fRecalcDone = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If view rect changed, make sure to update scrollbars
|
||
|
RecalcScrollBars();
|
||
|
|
||
|
return fReturn;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::WaitForRecalcIli(ili)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Wait until line array is recalculated up to line <p ili>
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Returns TRUE if lines were recalc'd up to ili
|
||
|
*/
|
||
|
//REVIEW (keithcu) This recalcs up to the end! I'm not certain how great
|
||
|
//our background recalc, etc. stuff is. It seems not to work all that well for
|
||
|
//the complexity it adds to our codebase. I think we should either throw it
|
||
|
//away or redo it.
|
||
|
BOOL CDisplayML::WaitForRecalcIli (
|
||
|
LONG ili) //@parm Line index to recalculate line array up to
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::WaitForRecalcIli");
|
||
|
|
||
|
LONG cchGuess;
|
||
|
|
||
|
while(!_fRecalcDone && ili >= Count())
|
||
|
{
|
||
|
// just go ahead and recalc everything.
|
||
|
cchGuess = _ped->GetTextLength();
|
||
|
if(IsFrozen() || !WaitForRecalc(cchGuess, -1))
|
||
|
return FALSE;
|
||
|
}
|
||
|
return ili < Count();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::WaitForRecalcView()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Ensure visible lines are completly recalced
|
||
|
*
|
||
|
* @rdesc TRUE iff successful
|
||
|
*/
|
||
|
BOOL CDisplayML::WaitForRecalcView()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::WaitForRecalcView");
|
||
|
|
||
|
return WaitForRecalc(-1, _vpScroll + _dvpView);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::InitLinePtr ( CLinePtr & plp )
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Initialize a CLinePtr properly
|
||
|
*/
|
||
|
void CDisplayML::InitLinePtr (
|
||
|
CLinePtr & plp ) //@parm Ptr to line to initialize
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::InitLinePtr");
|
||
|
|
||
|
plp.Init( *this );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::GetLineText(ili, pchBuff, cchMost)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Copy given line of this display into a character buffer
|
||
|
*
|
||
|
* @rdesc
|
||
|
* number of character copied
|
||
|
*/
|
||
|
LONG CDisplayML::GetLineText(
|
||
|
LONG ili, //@parm Line to get text of
|
||
|
TCHAR *pchBuff, //@parm Buffer to stuff text into
|
||
|
LONG cchMost) //@parm Length of buffer
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetLineText");
|
||
|
|
||
|
_TEST_INVARIANT_
|
||
|
|
||
|
CTxtPtr tp (_ped, 0);
|
||
|
|
||
|
if(ili >= 0 && (ili < Count() || WaitForRecalcIli(ili)))
|
||
|
{
|
||
|
cchMost = min(cchMost, Elem(ili)->_cch);
|
||
|
if(cchMost > 0)
|
||
|
{
|
||
|
tp.SetCp(CpFromLine(ili, NULL));
|
||
|
return tp.GetText(cchMost, pchBuff);
|
||
|
}
|
||
|
}
|
||
|
*pchBuff = TEXT('\0');
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::LineCount
|
||
|
*
|
||
|
* @mfunc returns the number of lines in this control. Note that for plain
|
||
|
* text mode, we will add on an extra line of the last character is
|
||
|
* a CR. This is for compatibility with MLE
|
||
|
*
|
||
|
* @rdesc LONG
|
||
|
*/
|
||
|
LONG CDisplayML::LineCount() const
|
||
|
{
|
||
|
LONG cLine = Count();
|
||
|
|
||
|
if (!_ped->IsRich() && (!cLine || // If plain text with no lines
|
||
|
Elem(cLine - 1)->_cchEOP)) // or last line ending with a CR,
|
||
|
{ // then inc line count
|
||
|
cLine++;
|
||
|
}
|
||
|
return cLine;
|
||
|
}
|
||
|
|
||
|
// ================================ Line info retrieval ====================================
|
||
|
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::CpFromLine(ili, pdvp)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Computes cp at start of given line
|
||
|
* (and top of line position relative to this display)
|
||
|
*
|
||
|
* @rdesc
|
||
|
* cp of given line
|
||
|
*/
|
||
|
LONG CDisplayML::CpFromLine (
|
||
|
LONG ili, //@parm Line we're interested in (if <lt> 0 means caret line)
|
||
|
LONG *pdvp) //@parm Returns top of line relative to display
|
||
|
// (NULL if don't want that info)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CpFromLine");
|
||
|
|
||
|
_TEST_INVARIANT_
|
||
|
|
||
|
LONG cli;
|
||
|
LONG vp = _vpScroll + _dvpFirstVisible;
|
||
|
LONG cp = _cpFirstVisible;
|
||
|
CLine *pli;
|
||
|
LONG iStart = _iliFirstVisible;
|
||
|
|
||
|
cli = ili - _iliFirstVisible;
|
||
|
if(cli < 0 && -cli >= ili)
|
||
|
{
|
||
|
// Closer to first line than to first visible line,
|
||
|
// so start at the first line
|
||
|
cli = ili;
|
||
|
vp = 0;
|
||
|
cp = 0;
|
||
|
iStart = 0;
|
||
|
}
|
||
|
else if( cli <= 0 )
|
||
|
{
|
||
|
CheckView();
|
||
|
for(ili = _iliFirstVisible-1; cli < 0; cli++, ili--)
|
||
|
{
|
||
|
pli = Elem(ili);
|
||
|
vp -= pli->GetHeight();
|
||
|
cp -= pli->_cch;
|
||
|
}
|
||
|
goto end;
|
||
|
}
|
||
|
|
||
|
for(ili = iStart; cli > 0; cli--, ili++)
|
||
|
{
|
||
|
pli = Elem(ili);
|
||
|
if(!IsMain() || !WaitForRecalcIli(ili))
|
||
|
break;
|
||
|
vp += pli->GetHeight();
|
||
|
cp += pli->_cch;
|
||
|
}
|
||
|
|
||
|
end:
|
||
|
if(pdvp)
|
||
|
*pdvp = vp;
|
||
|
|
||
|
return cp;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::LineFromCp(cp, fAtEnd)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Computes line containing given cp.
|
||
|
*
|
||
|
* @rdesc
|
||
|
* index of line found, -1 if no line at that cp.
|
||
|
*/
|
||
|
LONG CDisplayML::LineFromCp(
|
||
|
LONG cp, //@parm cp to look for
|
||
|
BOOL fAtEnd) //@parm If true, return previous line for ambiguous cp
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::LineFromCp");
|
||
|
|
||
|
_TEST_INVARIANT_
|
||
|
|
||
|
CLinePtr rp(this);
|
||
|
|
||
|
if(!WaitForRecalc(cp, -1) || !rp.SetCp(cp, fAtEnd))
|
||
|
return -1;
|
||
|
|
||
|
return (LONG)rp;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::CpFromPoint(pt, prcClient, prtp, prp, fAllowEOL, phit,
|
||
|
* pdispdim, pcpActual)
|
||
|
* @mfunc
|
||
|
* Determine cp at given point
|
||
|
*
|
||
|
* @devnote
|
||
|
* --- Use when in-place active only ---
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Computed cp, -1 if failed
|
||
|
*/
|
||
|
LONG CDisplayML::CpFromPoint(
|
||
|
POINTUV pt, //@parm Point to compute cp at (client coords)
|
||
|
const RECTUV *prcClient,//@parm Client rectangle (can be NULL if active).
|
||
|
CRchTxtPtr * const prtp,//@parm Returns text pointer at cp (may be NULL)
|
||
|
CLinePtr * const prp, //@parm Returns line pointer at cp (may be NULL)
|
||
|
BOOL fAllowEOL, //@parm Click at EOL returns cp after CRLF
|
||
|
HITTEST * phit, //@parm Out parm for hit-test value
|
||
|
CDispDim * pdispdim, //@parm Out parm for display dimensions
|
||
|
LONG *pcpActual, //@parm Out cp that pt is above
|
||
|
CLine * pliParent) //@parm Parent pli for table row displays
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CpFromPoint");
|
||
|
CMeasurer me(this);
|
||
|
|
||
|
return CLayout::CpFromPoint(me, pt, prcClient, prtp, prp, fAllowEOL, phit, pdispdim, pcpActual);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::PointFromTp(rtp, prcClient, fAtEnd, pt, prp, taMode, pdispdim)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Determine coordinates at given tp
|
||
|
*
|
||
|
* @devnote
|
||
|
* --- Use when in-place active only ---
|
||
|
*
|
||
|
* @rdesc
|
||
|
* line index at cp, -1 if error
|
||
|
*/
|
||
|
LONG CDisplayML::PointFromTp(
|
||
|
const CRchTxtPtr &rtp, //@parm Text ptr to get coordinates at
|
||
|
const RECTUV *prcClient,//@parm Client rectangle (can be NULL if active).
|
||
|
BOOL fAtEnd, //@parm Return end of prev line for ambiguous cp
|
||
|
POINTUV & pt, //@parm Returns point at cp in client coords
|
||
|
CLinePtr * const prp, //@parm Returns line pointer at tp (may be null)
|
||
|
UINT taMode, //@parm Text Align mode: top, baseline, bottom
|
||
|
CDispDim * pdispdim) //@parm Out parm for display dimensions
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::PointFromTp");
|
||
|
CMeasurer me(this, rtp);
|
||
|
return CLayout::PointFromTp(me, rtp, prcClient, fAtEnd, pt, prp, taMode, pdispdim);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Render(rcView, rcRender)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Renders text.
|
||
|
*/
|
||
|
void CDisplayML::Render(
|
||
|
const RECTUV &rcView, //@parm View RECT
|
||
|
const RECTUV &rcRender) //@parm RECT to render (must be container in client rect)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::Render");
|
||
|
|
||
|
_TEST_INVARIANT_
|
||
|
|
||
|
LONG cp;
|
||
|
LONG ili;
|
||
|
LONG lCount = Count();
|
||
|
CTxtSelection *psel = _ped->GetSelNC();
|
||
|
POINTUV pt;
|
||
|
LONG vpLine;
|
||
|
|
||
|
if(psel)
|
||
|
psel->ClearCchPending();
|
||
|
|
||
|
// Calculate line and cp to start display at
|
||
|
if(IsInPageView())
|
||
|
{
|
||
|
cp = _cpFirstVisible;
|
||
|
ili = _iliFirstVisible;
|
||
|
vpLine = _vpScroll;
|
||
|
}
|
||
|
else
|
||
|
ili = LineFromVpos(this, rcRender.top + _vpScroll - rcView.top, &vpLine, &cp);
|
||
|
|
||
|
CLine *pli = Elem(ili);
|
||
|
CLine *pliFirst = pli;
|
||
|
LONG dvpBottom = BottomOfRender(rcView, rcRender);
|
||
|
LONG vpLi = pli->GetHeight();
|
||
|
|
||
|
// Calculate point where text will start being displayed
|
||
|
pt.u = rcView.left - _upScroll;
|
||
|
pt.v = rcView.top - _vpScroll + vpLine;
|
||
|
|
||
|
// Create and prepare renderer
|
||
|
CRenderer re(this);
|
||
|
|
||
|
if(!re.StartRender(rcView, rcRender))
|
||
|
return;
|
||
|
|
||
|
// Init renderer at start of first line to render
|
||
|
re.SetCurPoint(pt);
|
||
|
POINTUV ptFirst = pt;
|
||
|
LONG cpFirst = cp = re.SetCp(cp);
|
||
|
vpLi = pt.v;
|
||
|
|
||
|
// Render each line in update rectangle
|
||
|
for (;; pli++, ili++)
|
||
|
{
|
||
|
BOOL fLastLine = ili == lCount - 1 ||
|
||
|
re.GetCurPoint().v + pli->GetHeight() >= dvpBottom ||
|
||
|
IsInPageView() && ili + 1 < lCount && (pli + 1)->_fFirstOnPage;
|
||
|
|
||
|
//Support khyphChangeAfter
|
||
|
if (ili > 0)
|
||
|
re.SetIhyphPrev((pli - 1)->_ihyph);
|
||
|
|
||
|
//Don't draw the line if it doesn't intersect the rendering area,
|
||
|
//but draw at least 1 line so that we erase the control
|
||
|
if (pt.v + pli->GetHeight() < rcRender.top && !fLastLine)
|
||
|
{
|
||
|
pt.v += pli->GetHeight();
|
||
|
re.SetCurPoint(pt);
|
||
|
re.Move(pli->_cch);
|
||
|
}
|
||
|
else if (!CLayout::Render(re, pli, &rcView, fLastLine, ili, lCount))
|
||
|
break;
|
||
|
|
||
|
if (fLastLine)
|
||
|
break;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
cp += pli->_cch;
|
||
|
vpLi += pli->GetHeight();
|
||
|
|
||
|
// Rich controls with password characters stop at EOPs,
|
||
|
// so re.GetCp() may be less than cp.
|
||
|
AssertSz(_ped->IsRich() && _ped->fUsePassword() || re.GetCp() == cp, "cp out of sync with line table");
|
||
|
#endif
|
||
|
pt = re.GetCurPoint();
|
||
|
AssertSz(pt.v == vpLi, "CDisplayML::RenderView() - y out of sync with line table");
|
||
|
|
||
|
}
|
||
|
|
||
|
re.EndRender(pliFirst, pli, cpFirst, ptFirst);
|
||
|
}
|
||
|
|
||
|
|
||
|
//=================================== View Updating ===================================
|
||
|
/*
|
||
|
* CDisplayML::RecalcView(fUpdateScrollBars)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Recalc all lines breaks and update first visible line
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE if success
|
||
|
*/
|
||
|
BOOL CDisplayML::RecalcView(
|
||
|
BOOL fUpdateScrollBars, RECTUV* prc)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcView");
|
||
|
|
||
|
BOOL fRet = TRUE;
|
||
|
LONG dvpOld = _dvp;
|
||
|
LONG vpScrollHeightOld = GetMaxVpScroll();
|
||
|
LONG dupOld = _dupLineMax;
|
||
|
LONG vpScrollHeightNew;
|
||
|
|
||
|
// Full recalc lines
|
||
|
CRchTxtPtr rtp(_ped, 0);
|
||
|
if(!RecalcLines(rtp, FALSE))
|
||
|
{
|
||
|
// We're in deep crap now, the recalc failed. Let's try to get out
|
||
|
// of this with our head still mostly attached
|
||
|
InitVars();
|
||
|
fRet = FALSE;
|
||
|
goto Done;
|
||
|
}
|
||
|
|
||
|
// Force _upScroll = 0 if x scroll range is smaller than the view width
|
||
|
if(_dupLineMax <= _dupView)
|
||
|
_upScroll = 0;
|
||
|
|
||
|
vpScrollHeightNew = GetMaxVpScroll();
|
||
|
RebindFirstVisible(vpScrollHeightNew <= _dvpView);
|
||
|
|
||
|
CheckView();
|
||
|
|
||
|
// We only need to resize if the size needed to display the object has
|
||
|
// changed.
|
||
|
if (dvpOld != _dvp || vpScrollHeightOld != vpScrollHeightNew ||
|
||
|
dupOld != _dupLineMax)
|
||
|
{
|
||
|
if(FAILED(RequestResize()))
|
||
|
_ped->GetCallMgr()->SetOutOfMemory();
|
||
|
else if (prc && _ped->_fInOurHost)/*bug fix# 5830, forms3 relies on old behavior*/
|
||
|
_ped->TxGetClientRect(prc);
|
||
|
}
|
||
|
|
||
|
Done:
|
||
|
|
||
|
// Now update scrollbars
|
||
|
if(fUpdateScrollBars)
|
||
|
RecalcScrollBars();
|
||
|
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::UpdateView(&rtp, cchOld, cchNew)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Recalc lines and update the visible part of the display
|
||
|
* (the "view") on the screen.
|
||
|
*
|
||
|
* @devnote
|
||
|
* --- Use when in-place active only ---
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE if success
|
||
|
*/
|
||
|
BOOL CDisplayML::UpdateView(
|
||
|
CRchTxtPtr &rtp, //@parm Text ptr where change happened
|
||
|
LONG cchOld, //@parm Count of chars deleted
|
||
|
LONG cchNew) //@parm Count of chars inserted
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::UpdateView");
|
||
|
|
||
|
BOOL fReturn = TRUE;
|
||
|
BOOL fRecalcVisible = TRUE;
|
||
|
RECTUV rcClient;
|
||
|
RECTUV rcView;
|
||
|
CLed led;
|
||
|
CTxtSelection *psel = _ped->GetSelNC();
|
||
|
LONG cpStartOfUpdate = rtp.GetCp();
|
||
|
BOOL fNeedViewChange = FALSE;
|
||
|
LONG dvpOld = _dvp;
|
||
|
LONG vpScrollHeightOld = GetMaxVpScroll();
|
||
|
LONG dupOld = _dupLineMax;
|
||
|
LONG vpScrollOld = _vpScroll;
|
||
|
LONG cpNewFirstVisible;
|
||
|
|
||
|
if(_fNoUpdateView)
|
||
|
return fReturn;
|
||
|
|
||
|
AssertSz(_ped->_fInPlaceActive, "CDisplayML::UpdateView called when inactive");
|
||
|
|
||
|
if(rtp.GetCp() > _cpCalcMax || _fNeedRecalc)
|
||
|
{
|
||
|
// We haven't even calc'ed this far, so don't bother with updating
|
||
|
// here. Background recalc will eventually catch up to us.
|
||
|
if(!rtp.GetCp())
|
||
|
{ // Changes started at start of doc
|
||
|
_cpCalcMax = 0; // so previous calc'd state is
|
||
|
_vpCalcMax = 0; // completely invalid
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
AssertSz(rtp.GetCp() <= _cpCalcMax, "CDisplayML::UpdateView: rtp > _cpCalcMax");
|
||
|
|
||
|
_ped->TxGetClientRect(&rcClient);
|
||
|
GetViewRect(rcView, &rcClient);
|
||
|
|
||
|
if(psel && !psel->PuttingChar())
|
||
|
psel->ClearCchPending();
|
||
|
|
||
|
DeferUpdateScrollBar();
|
||
|
|
||
|
// In general, background recalc should not start until both the scroll
|
||
|
// position is beyond the visible view and the cp is beyond the first visible
|
||
|
// character. However, for the recalc we will only wait on the height.
|
||
|
// Later calls to WaitForRecalc will wait on cpFirstVisible if that is
|
||
|
// necessary.
|
||
|
_vpWait = _vpScroll + _dvpView;
|
||
|
_cpWait = -1;
|
||
|
|
||
|
if(!RecalcLines(rtp, cchOld, cchNew, FALSE, TRUE, &led))
|
||
|
{
|
||
|
// We're in deep crap now, the recalc failed. Let's try to get
|
||
|
// out of this with our head still mostly attached
|
||
|
InitVars();
|
||
|
fRecalcVisible = TRUE;
|
||
|
fReturn = FALSE;
|
||
|
_ped->TxInvalidate();
|
||
|
fNeedViewChange = TRUE;
|
||
|
goto Exit;
|
||
|
}
|
||
|
|
||
|
if(_dupLineMax <= _dupView)
|
||
|
{
|
||
|
// x scroll range is smaller than the view width, force x scrolling position = 0
|
||
|
// we have to redraw all when this means scrolling back to home.
|
||
|
// Problem lines are lines with trailing spaces crossing _dupView. UpdateCaret forces redraw
|
||
|
// only when such lines are growing, misses shrinking.
|
||
|
if (_upScroll != 0)
|
||
|
_ped->TxInvalidate(); //REVIEW: find a smaller rectangle?
|
||
|
|
||
|
_upScroll = 0;
|
||
|
}
|
||
|
|
||
|
if(led._vpFirst >= _vpScroll + _dvpView)
|
||
|
{
|
||
|
// Update is after view: don't do anything
|
||
|
fRecalcVisible = FALSE;
|
||
|
AssertNr(VerifyFirstVisible());
|
||
|
goto finish;
|
||
|
}
|
||
|
else if(led._vpMatchNew <= _vpScroll + _dvpFirstVisible &&
|
||
|
led._vpMatchOld <= _vpScroll + _dvpFirstVisible &&
|
||
|
_vpScroll < _dvp)
|
||
|
{
|
||
|
if (_dvp != 0)
|
||
|
{
|
||
|
// Update is entirely before view: just update scroll position
|
||
|
// but don't touch the screen
|
||
|
_vpScroll += led._vpMatchNew - led._vpMatchOld;
|
||
|
_iliFirstVisible += led._iliMatchNew - led._iliMatchOld;
|
||
|
_iliFirstVisible = max(_iliFirstVisible, 0);
|
||
|
|
||
|
_cpFirstVisible += led._cpMatchNew - led._cpMatchOld;
|
||
|
_cpFirstVisible = min(_ped->GetTextLength(), _cpFirstVisible);
|
||
|
_cpFirstVisible = max(0, _cpFirstVisible);
|
||
|
fRecalcVisible = FALSE;
|
||
|
Sync_yScroll();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Odd outline case. Height of control can be recalc'd to zero due
|
||
|
// when outline mode collapses all lines to 0. Example of how to
|
||
|
// do this is tell outline to collapse to heading 1 and there is none.
|
||
|
_vpScroll = 0;
|
||
|
_iliFirstVisible = 0;
|
||
|
_cpFirstVisible = 0;
|
||
|
_sPage = 0;
|
||
|
}
|
||
|
|
||
|
AssertNr(VerifyFirstVisible());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Update overlaps visible view
|
||
|
RECTUV rc = rcClient;
|
||
|
|
||
|
// Do we need to resync the first visible? Note that this if check
|
||
|
// is mostly an optmization; we could decide to _always_ recompute
|
||
|
// this _iliFirstVisible if we wanted to unless rtp is inside a table,
|
||
|
// in which case _cpFirstVisible won't change and the following may
|
||
|
// mess up _dvpFirstVisible.
|
||
|
const CParaFormat *pPF = rtp.GetPF();
|
||
|
|
||
|
if((!pPF->_bTableLevel || rtp._rpTX.IsAtTRD(0)) &&
|
||
|
(cpStartOfUpdate <= _cpFirstVisible ||
|
||
|
led._iliMatchOld <= _iliFirstVisible ||
|
||
|
led._iliMatchNew <= _iliFirstVisible ||
|
||
|
led._iliFirst <= _iliFirstVisible ))
|
||
|
{
|
||
|
// Edit overlaps the first visible. We try to maintain
|
||
|
// approximately the same place in the file visible.
|
||
|
cpNewFirstVisible = _cpFirstVisible;
|
||
|
|
||
|
if(_iliFirstVisible - 1 == led._iliFirst)
|
||
|
{
|
||
|
// Edit occurred on line before visible view. Most likely
|
||
|
// this means that the first character got pulled back to
|
||
|
// the previous line so we want that line to be visible.
|
||
|
cpNewFirstVisible = led._cpFirst;
|
||
|
}
|
||
|
|
||
|
// Change first visible entries because CLinePtr::SetCp() and
|
||
|
// VposFromLine() use them, but they're not valid
|
||
|
_dvpFirstVisible = 0;
|
||
|
_cpFirstVisible = 0;
|
||
|
_iliFirstVisible = 0;
|
||
|
_vpScroll = 0;
|
||
|
|
||
|
// With certain formatting changes, it's possible for
|
||
|
// cpNewFirstVisible to be less that what's been calculated so far
|
||
|
// in RecalcLines above. Wait for things to catch up.
|
||
|
|
||
|
WaitForRecalc(cpNewFirstVisible, -1);
|
||
|
Set_yScroll(cpNewFirstVisible);
|
||
|
}
|
||
|
AssertNr(VerifyFirstVisible());
|
||
|
|
||
|
// Is there a match in the display area? - this can only happen if the
|
||
|
// old match is on the screen and the new match will be on the screen
|
||
|
if (led._vpMatchOld < vpScrollOld + _dvpView &&
|
||
|
led._vpMatchNew < _vpScroll + _dvpView)
|
||
|
{
|
||
|
// We have a match inside visible view
|
||
|
// Scroll the part that is below the old y pos of the match
|
||
|
// or invalidate if the new y of the match is now below the view
|
||
|
rc.top = rcView.top + (led._vpMatchOld - vpScrollOld);
|
||
|
if(rc.top < rc.bottom)
|
||
|
{
|
||
|
// Calculate difference between new and old screen positions
|
||
|
const INT dvp = (led._vpMatchNew - _vpScroll) - (led._vpMatchOld - vpScrollOld);
|
||
|
|
||
|
if(dvp)
|
||
|
{
|
||
|
if(!IsTransparent() && _ped->GetBackgroundType() == -1)
|
||
|
{
|
||
|
LONG dxp, dyp;
|
||
|
GetDxpDypFromDupDvp(0, dvp, GetTflow(), dxp, dyp);
|
||
|
|
||
|
RECTUV rcClip = {rcClient.left, rcView.top, rcClient.right, rcView.bottom };
|
||
|
RECT rcxyClip, rcxy;
|
||
|
RectFromRectuv(rcxyClip, rcClip);
|
||
|
RectFromRectuv(rcxy, rc);
|
||
|
|
||
|
_ped->TxScrollWindowEx(dxp, dyp, &rcxy, &rcxyClip);
|
||
|
fNeedViewChange = TRUE;
|
||
|
|
||
|
if(dvp < 0)
|
||
|
{
|
||
|
rc.top = rc.bottom + dvp;
|
||
|
|
||
|
_ped->TxInvalidateRect(&rc);
|
||
|
fNeedViewChange = TRUE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Just invalidate cuz we don't scroll in transparent
|
||
|
// mode
|
||
|
RECTUV rcInvalidate = rc;
|
||
|
rcInvalidate.top += dvp;
|
||
|
|
||
|
_ped->TxInvalidateRect(&rcInvalidate);
|
||
|
fNeedViewChange = TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rc.top = rcView.top + led._vpMatchNew - _vpScroll;
|
||
|
_ped->TxInvalidateRect(&rc);
|
||
|
fNeedViewChange = TRUE;
|
||
|
}
|
||
|
|
||
|
// Since we found that the new match falls on the screen, we can
|
||
|
// safely set the bottom to the new match since this is the most
|
||
|
// that can have changed.
|
||
|
rc.bottom = rcView.top + max(led._vpMatchNew, led._vpMatchOld) - _vpScroll;
|
||
|
}
|
||
|
|
||
|
rc.top = rcView.top + led._vpFirst - _vpScroll;
|
||
|
|
||
|
// Set first line edited to be rendered using off-screen bitmap
|
||
|
if (led._iliFirst < Count() && !IsTransparent() && !Elem(led._iliFirst)->_fUseOffscreenDC)
|
||
|
Elem(led._iliFirst)->_fOffscreenOnce = Elem(led._iliFirst)->_fUseOffscreenDC = TRUE;
|
||
|
|
||
|
// Invalidate part of update that is above match (if any)
|
||
|
_ped->TxInvalidateRect (&rc);
|
||
|
fNeedViewChange = TRUE;
|
||
|
}
|
||
|
|
||
|
finish:
|
||
|
if(fRecalcVisible)
|
||
|
{
|
||
|
fReturn = WaitForRecalcView();
|
||
|
if(!fReturn)
|
||
|
return FALSE;
|
||
|
}
|
||
|
if(fNeedViewChange)
|
||
|
_ped->GetHost()->TxViewChange(FALSE);
|
||
|
|
||
|
CheckView();
|
||
|
|
||
|
// We only need to resize if size needed to display object has changed
|
||
|
if (dvpOld != _dvp || vpScrollHeightOld != GetMaxVpScroll() ||
|
||
|
dupOld != _dupLineMax)
|
||
|
{
|
||
|
if(FAILED(RequestResize()))
|
||
|
_ped->GetCallMgr()->SetOutOfMemory();
|
||
|
}
|
||
|
if(DoDeferredUpdateScrollBar())
|
||
|
{
|
||
|
if(FAILED(RequestResize()))
|
||
|
_ped->GetCallMgr()->SetOutOfMemory();
|
||
|
DoDeferredUpdateScrollBar();
|
||
|
}
|
||
|
|
||
|
Exit:
|
||
|
return fReturn;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::RecalcLine(cp)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Show line
|
||
|
*/
|
||
|
void CDisplayML::RecalcLine(
|
||
|
LONG cp) //@parm cp line to recalc
|
||
|
{
|
||
|
CNotifyMgr *pnm = GetPed()->GetNotifyMgr();
|
||
|
if(pnm)
|
||
|
pnm->NotifyPostReplaceRange(NULL, cp, 0, 0, cp, cp);
|
||
|
}
|
||
|
|
||
|
void CDisplayML::InitVars()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::InitVars");
|
||
|
|
||
|
_vpScroll = _upScroll = 0;
|
||
|
_iliFirstVisible = 0;
|
||
|
_cpFirstVisible = _cpMin = 0;
|
||
|
_dvpFirstVisible = 0;
|
||
|
_sPage = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::GetCliVisible(pcpMostVisible)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Get count of visible lines and update _cpMostVisible for PageDown()
|
||
|
*
|
||
|
* @rdesc
|
||
|
* count of visible lines
|
||
|
*/
|
||
|
LONG CDisplayML::GetCliVisible(
|
||
|
LONG* pcpMostVisible, //@parm Returns cpMostVisible
|
||
|
BOOL fLastCharOfLastVisible) const //@parm Want cp of last visible char
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetCliVisible");
|
||
|
|
||
|
LONG cli = 0; // Initialize count
|
||
|
LONG ili = _iliFirstVisible; // Start with 1st visible line
|
||
|
LONG dvp = _dvpFirstVisible;
|
||
|
LONG cp;
|
||
|
const CLine *pli = Elem(ili);
|
||
|
|
||
|
for(cp = _cpFirstVisible;
|
||
|
dvp < _dvpView && ili < Count();
|
||
|
cli++, ili++, pli++)
|
||
|
{
|
||
|
dvp += pli->GetHeight();
|
||
|
|
||
|
//Note: I removed the support to give the last visible non-white character.
|
||
|
//Does anyone want that? It never worked in LS displays.
|
||
|
if (fLastCharOfLastVisible && dvp > _dvpView)
|
||
|
break;
|
||
|
|
||
|
if(IsInPageView() && cli && pli->_fFirstOnPage)
|
||
|
break;
|
||
|
|
||
|
cp += pli->_cch;
|
||
|
}
|
||
|
|
||
|
if(pcpMostVisible)
|
||
|
*pcpMostVisible = cp;
|
||
|
|
||
|
return cli;
|
||
|
}
|
||
|
|
||
|
//================================== Inversion (selection) ============================
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::InvertRange(cp, cch)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Invert a given range on screen (for selection)
|
||
|
*
|
||
|
* @devnote
|
||
|
* --- Use when in-place active only ---
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE if success
|
||
|
*/
|
||
|
BOOL CDisplayML::InvertRange (
|
||
|
LONG cp, //@parm Active end of range to invert
|
||
|
LONG cch, //@parm Signed length of range
|
||
|
SELDISPLAYACTION selAction) //@parm Describes what we are doing to the selection
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::InvertRange");
|
||
|
|
||
|
LONG cpMost;
|
||
|
RECTUV rc, rcClient, rcView;
|
||
|
CLinePtr rp(this);
|
||
|
CRchTxtPtr rtp(_ped);
|
||
|
LONG y;
|
||
|
LONG cpActive = _ped->GetSel()->GetCp();
|
||
|
|
||
|
AssertSz(_ped->_fInPlaceActive, "CDisplayML::InvertRange() called when not in-place active");
|
||
|
|
||
|
if(cch < 0) // Define cpMost, set cp = cpMin,
|
||
|
{ // and cch = |cch|
|
||
|
cpMost = cp - cch;
|
||
|
cch = -cch;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cpMost = cp;
|
||
|
cp -= cch;
|
||
|
}
|
||
|
|
||
|
#ifndef NOLINESERVICES
|
||
|
if (g_pols)
|
||
|
g_pols->DestroyLine(this);
|
||
|
#endif
|
||
|
|
||
|
// If an object is being inverted, and nothing else is being inverted,
|
||
|
// delegate to the ObjectMgr. If fIgnoreObj is TRUE we highlight normally
|
||
|
if (cch == 1 && _ped->GetObjectCount() &&
|
||
|
(selAction == selSetNormal || selAction == selSetHiLite))
|
||
|
{
|
||
|
CObjectMgr* pobjmgr = _ped->GetObjectMgr();
|
||
|
|
||
|
rtp.SetCp(cp);
|
||
|
if(rtp.GetChar() == WCH_EMBEDDING)
|
||
|
{
|
||
|
if(pobjmgr)
|
||
|
pobjmgr->HandleSingleSelect(_ped, cp, selAction == selSetHiLite);
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If display is frozen, just update recalc region and move on.
|
||
|
if(_padc)
|
||
|
{
|
||
|
AssertSz(cp >= 0, "CDisplayML::InvertRange: range (cp) goes below"
|
||
|
"zero!!" );
|
||
|
// Make sure these values are bounded.
|
||
|
if(cp > _ped->GetTextLength()) // Don't bother updating region;
|
||
|
return TRUE; // it's out of bounds
|
||
|
|
||
|
if(cp + cch > _ped->GetTextLength())
|
||
|
cch -= cp + cch - _ped->GetTextLength();
|
||
|
|
||
|
_padc->UpdateRecalcRegion(cp, cch, cch);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
if(!WaitForRecalcView()) // Ensure all visible lines are
|
||
|
return FALSE; // recalc'd
|
||
|
|
||
|
_ped->TxGetClientRect(&rcClient);
|
||
|
GetViewRect(rcView, &rcClient);
|
||
|
|
||
|
// Compute first line to invert and where to start on it
|
||
|
if(cp >= _cpFirstVisible)
|
||
|
{
|
||
|
POINTUV pt;
|
||
|
rtp.SetCp(cp);
|
||
|
if(PointFromTp(rtp, NULL, FALSE, pt, NULL, TA_TOP) < 0)
|
||
|
return FALSE;
|
||
|
|
||
|
//We don't use the rp returned from PointFromTp because
|
||
|
//we need the outermost rp for best results. In the future
|
||
|
//we could consider writing code which doesn't invalidate so much.
|
||
|
rp.SetCp(cp, FALSE, 0);
|
||
|
rc.top = pt.v;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cp = _cpFirstVisible;
|
||
|
rp = _iliFirstVisible;
|
||
|
rc.top = rcView.top + _dvpFirstVisible;
|
||
|
}
|
||
|
|
||
|
// Loop on all lines of range
|
||
|
while (cp < cpMost && rc.top < rcView.bottom && rp.IsValid())
|
||
|
{
|
||
|
// Calculate rc.bottom first because rc.top takes into account
|
||
|
// the dy of the first visible on the first loop.
|
||
|
y = rc.top;
|
||
|
y += rp->GetHeight();
|
||
|
rc.bottom = min(y, rcView.bottom);
|
||
|
rc.top = max(rc.top, rcView.top);
|
||
|
|
||
|
//If we are inverting the active end of the selection, draw it offscreen
|
||
|
//to minimize flicker.
|
||
|
if (IN_RANGE(cp - rp.GetIch(), cpActive, cp - rp.GetIch() + rp->_cch) &&
|
||
|
!IsTransparent() && !rp->_fUseOffscreenDC)
|
||
|
{
|
||
|
rp->_fOffscreenOnce = rp->_fUseOffscreenDC = TRUE;
|
||
|
}
|
||
|
|
||
|
cp += rp->_cch - rp.GetIch();
|
||
|
|
||
|
rc.left = rcClient.left;
|
||
|
rc.right = rcClient.right;
|
||
|
|
||
|
_ped->TxInvalidateRect(&rc);
|
||
|
rc.top = rc.bottom;
|
||
|
if(!rp.NextRun())
|
||
|
break;
|
||
|
}
|
||
|
_ped->TxUpdateWindow(); // Make sure window gets repainted
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//=================================== Scrolling =============================
|
||
|
|
||
|
/*
|
||
|
* CDisplay::VScroll(wCode, vPos)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Scroll the view vertically in response to a scrollbar event
|
||
|
*
|
||
|
* @devnote
|
||
|
* --- Use when in-place active only ---
|
||
|
*
|
||
|
* @rdesc
|
||
|
* LRESULT formatted for WM_VSCROLL message
|
||
|
*/
|
||
|
LRESULT CDisplayML::VScroll(
|
||
|
WORD wCode, //@parm Scrollbar event code
|
||
|
LONG vPos) //@parm Thumb position (vPos <lt> 0 for EM_SCROLL behavior)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::VScroll");
|
||
|
|
||
|
LONG cliVisible;
|
||
|
LONG dy = 0;
|
||
|
BOOL fTracking = FALSE;
|
||
|
LONG i;
|
||
|
const LONG iliSave = _iliFirstVisible;
|
||
|
CLine * pli = NULL;
|
||
|
INT dvpSys = GetDvpSystemFont();
|
||
|
LONG vpScroll = _vpScroll;
|
||
|
|
||
|
AssertSz(_ped->_fInPlaceActive, "CDisplay::VScroll() called when not in-place");
|
||
|
|
||
|
if(vPos)
|
||
|
{
|
||
|
// Convert this from 16-bit to 32-bit if necessary.
|
||
|
vPos = ConvertScrollToVPos(vPos);
|
||
|
}
|
||
|
|
||
|
vPos = min(vPos, _dvp);
|
||
|
|
||
|
if(IsInPageView())
|
||
|
{
|
||
|
BOOL fForward;
|
||
|
BOOL fFoundNewPage = FALSE;
|
||
|
LONG ili = _iliFirstVisible;
|
||
|
LONG nLine = Count();
|
||
|
CLine *pli = Elem(_iliFirstVisible);
|
||
|
|
||
|
AssertSz(Elem(_iliFirstVisible)->_fFirstOnPage,
|
||
|
"CDisplayML::VScroll: _iliFirstVisible not top of page");
|
||
|
|
||
|
if(wCode <= SB_PAGEDOWN)
|
||
|
{
|
||
|
fForward = (wCode & 1);
|
||
|
ili += fForward;
|
||
|
while(ili && ili < nLine)
|
||
|
{
|
||
|
if(fForward > 0)
|
||
|
{
|
||
|
vpScroll += pli->GetHeight();
|
||
|
|
||
|
if(ili == nLine - 1)
|
||
|
break;
|
||
|
pli++;
|
||
|
ili++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pli--;
|
||
|
ili--;
|
||
|
vpScroll -= pli->GetHeight();
|
||
|
}
|
||
|
if(pli->_fFirstOnPage)
|
||
|
{
|
||
|
fFoundNewPage = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if(wCode == SB_THUMBTRACK || wCode == SB_THUMBPOSITION)
|
||
|
{
|
||
|
if (vPos + _dvpView >= _dvp) // At last page?
|
||
|
vPos = _dvp;
|
||
|
|
||
|
if(vPos > vpScroll)
|
||
|
{
|
||
|
LONG iliFirst = ili;
|
||
|
LONG vpScrollPage = vpScroll;
|
||
|
|
||
|
if(ili < nLine)
|
||
|
{
|
||
|
while(vpScroll < vPos)
|
||
|
{
|
||
|
vpScroll += pli->GetHeight(); // Advance to vPos
|
||
|
|
||
|
if(ili == nLine - 1)
|
||
|
break;
|
||
|
|
||
|
pli++;
|
||
|
ili++;
|
||
|
|
||
|
if(pli->_fFirstOnPage)
|
||
|
{
|
||
|
fFoundNewPage = TRUE;
|
||
|
vpScrollPage = vpScroll;
|
||
|
iliFirst = ili;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
vpScroll = vpScrollPage; // Move to top of page
|
||
|
ili = iliFirst;
|
||
|
}
|
||
|
else if(vPos < vpScroll)
|
||
|
{ // Go back to vPos
|
||
|
if(!ili)
|
||
|
{
|
||
|
vpScroll = 0;
|
||
|
fFoundNewPage = TRUE;
|
||
|
}
|
||
|
while(vpScroll > vPos && ili)
|
||
|
{
|
||
|
pli--;
|
||
|
ili--;
|
||
|
vpScroll -= pli->GetHeight();
|
||
|
if(pli->_fFirstOnPage)
|
||
|
fFoundNewPage = TRUE;
|
||
|
}
|
||
|
while(!pli->_fFirstOnPage && ili)
|
||
|
{
|
||
|
pli--;
|
||
|
ili--;
|
||
|
vpScroll -= pli->GetHeight();
|
||
|
}
|
||
|
}
|
||
|
AssertSz(Elem(ili)->_fFirstOnPage,
|
||
|
"CDisplayML::VScroll: ili not top of page");
|
||
|
}
|
||
|
if(!fFoundNewPage) // Nothing to scroll, early exit
|
||
|
return MAKELRESULT(0, TRUE);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
switch(wCode)
|
||
|
{
|
||
|
case SB_BOTTOM:
|
||
|
if(vPos < 0)
|
||
|
return FALSE;
|
||
|
WaitForRecalc(_ped->GetTextLength(), -1);
|
||
|
vpScroll = _dvp;
|
||
|
break;
|
||
|
|
||
|
case SB_LINEDOWN:
|
||
|
cliVisible = GetCliVisible();
|
||
|
if(_iliFirstVisible + cliVisible < Count()
|
||
|
&& 0 == _dvpFirstVisible)
|
||
|
{
|
||
|
i = _iliFirstVisible + cliVisible;
|
||
|
pli = Elem(i);
|
||
|
if(IsInOutlineView())
|
||
|
{ // Scan for uncollapsed line
|
||
|
for(; pli->_fCollapsed && i < Count();
|
||
|
pli++, i++);
|
||
|
}
|
||
|
if(i < Count())
|
||
|
dy = pli->GetHeight();
|
||
|
}
|
||
|
else if(cliVisible > 1)
|
||
|
{
|
||
|
pli = Elem(_iliFirstVisible);
|
||
|
dy = _dvpFirstVisible;
|
||
|
// TODO: scan until find uncollapsed line
|
||
|
dy += pli->GetHeight();
|
||
|
}
|
||
|
else
|
||
|
dy = _dvp - _vpScroll;
|
||
|
|
||
|
if(dy >= _dvpView)
|
||
|
dy = dvpSys;
|
||
|
|
||
|
// Nothing to scroll, early exit
|
||
|
if ( !dy )
|
||
|
return MAKELRESULT(0, TRUE);
|
||
|
|
||
|
vpScroll += dy;
|
||
|
break;
|
||
|
|
||
|
case SB_LINEUP:
|
||
|
if(_iliFirstVisible > 0)
|
||
|
{
|
||
|
pli = Elem(_iliFirstVisible - 1);
|
||
|
// TODO: scan until find uncollapsed line
|
||
|
dy = pli->GetHeight();
|
||
|
}
|
||
|
else if(vpScroll > 0)
|
||
|
dy = min(vpScroll, dvpSys);
|
||
|
|
||
|
if(dy > _dvpView)
|
||
|
dy = dvpSys;
|
||
|
vpScroll -= dy;
|
||
|
break;
|
||
|
|
||
|
case SB_PAGEDOWN:
|
||
|
cliVisible = GetCliVisible();
|
||
|
vpScroll += _dvpView;
|
||
|
if(vpScroll < _dvp && cliVisible > 0)
|
||
|
{
|
||
|
// TODO: Scan until find uncollapsed line
|
||
|
dy = Elem(_iliFirstVisible + cliVisible - 1)->GetHeight();
|
||
|
if(dy >= _dvpView)
|
||
|
dy = dvpSys;
|
||
|
|
||
|
else if(dy > _dvpView - dy)
|
||
|
{
|
||
|
// Go at least a line if line is very big
|
||
|
dy = _dvpView - dy;
|
||
|
}
|
||
|
vpScroll -= dy;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SB_PAGEUP:
|
||
|
cliVisible = GetCliVisible();
|
||
|
vpScroll -= _dvpView;
|
||
|
|
||
|
if (vpScroll < 0)
|
||
|
{
|
||
|
// Scroll position can't be negative and we don't
|
||
|
// need to back up to be sure we display a full line.
|
||
|
vpScroll = 0;
|
||
|
}
|
||
|
else if(cliVisible > 0)
|
||
|
{
|
||
|
// TODO: Scan until find uncollapsed line
|
||
|
dy = Elem(_iliFirstVisible)->GetHeight();
|
||
|
if(dy >= _dvpView)
|
||
|
dy = dvpSys;
|
||
|
|
||
|
else if(dy > _dvpView - dy)
|
||
|
{
|
||
|
// Go at least a line if line is very big
|
||
|
dy = _dvpView - dy;
|
||
|
}
|
||
|
|
||
|
vpScroll += dy;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SB_THUMBTRACK:
|
||
|
case SB_THUMBPOSITION:
|
||
|
if(vPos < 0)
|
||
|
return FALSE;
|
||
|
|
||
|
vpScroll = vPos;
|
||
|
fTracking = TRUE;
|
||
|
break;
|
||
|
|
||
|
case SB_TOP:
|
||
|
if(vPos < 0)
|
||
|
return FALSE;
|
||
|
vpScroll = 0;
|
||
|
break;
|
||
|
|
||
|
case SB_ENDSCROLL:
|
||
|
UpdateScrollBar(SB_VERT);
|
||
|
return MAKELRESULT(0, TRUE);
|
||
|
|
||
|
default:
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL fFractional = wCode != SB_PAGEDOWN && wCode != SB_PAGEUP;
|
||
|
LONG vpLimit = _dvp;
|
||
|
|
||
|
if(!IsInPageView() && fFractional)
|
||
|
vpLimit = max(_dvp - _dvpView, 0);
|
||
|
|
||
|
vpScroll = min(vpScroll, vpLimit);
|
||
|
|
||
|
ScrollView(_upScroll, vpScroll, fTracking, fFractional);
|
||
|
|
||
|
// Force position update if we just finished a track
|
||
|
if(wCode == SB_THUMBPOSITION)
|
||
|
UpdateScrollBar(SB_VERT);
|
||
|
|
||
|
// Return how many lines we scrolled
|
||
|
return MAKELRESULT((WORD) (_iliFirstVisible - iliSave), TRUE);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplay::LineScroll(cli, cch)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Scroll view vertically in response to a scrollbar event
|
||
|
*/
|
||
|
void CDisplayML::LineScroll(
|
||
|
LONG cli, //@parm Count of lines to scroll vertically
|
||
|
LONG cch) //@parm Count of characters to scroll horizontally
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::LineScroll");
|
||
|
|
||
|
//Make sure the line to scroll to is valid
|
||
|
if (cli + _iliFirstVisible >= Count())
|
||
|
{
|
||
|
// change line count enough to display the last line
|
||
|
cli = Count() - _iliFirstVisible;
|
||
|
}
|
||
|
|
||
|
// Get the absolute vpScroll position by adding the difference of the line
|
||
|
// we want to go to and the current _vpScroll position
|
||
|
LONG dvpScroll = CalcVLineScrollDelta(cli, FALSE);
|
||
|
if(dvpScroll < 0 || _dvp - (_vpScroll + dvpScroll) > _dvpView - dvpScroll)
|
||
|
ScrollView(_upScroll, _vpScroll + dvpScroll, FALSE, FALSE);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::FractionalScrollView (vDelta)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Allow view to be scrolled by fractional lines.
|
||
|
*/
|
||
|
void CDisplayML::FractionalScrollView ( LONG vDelta )
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::FractionalScrollView");
|
||
|
|
||
|
if ( vDelta)
|
||
|
ScrollView(_upScroll, min(vDelta + _vpScroll, max(_dvp - _dvpView, 0)), FALSE, TRUE);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::ScrollToLineStart(iDirection)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* If the view is scrolled so that only a partial line is at the
|
||
|
* top, then scroll the view so that the entire view is at the top.
|
||
|
*/
|
||
|
void CDisplayML::ScrollToLineStart(
|
||
|
LONG iDirection) //@parm the direction in which to scroll (negative
|
||
|
// means down the screen
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::ScrollToLineStart");
|
||
|
|
||
|
// This code originally lined things up on a line. However, it doesn't work
|
||
|
// very well with big objects especially at the end of the document. I am
|
||
|
// leaving the call here in case we discover problems later. (a-rsail).
|
||
|
|
||
|
#if 0
|
||
|
// If _dvpFirstVisible is zero, then we're aligned on a line, so
|
||
|
// nothing more to do.
|
||
|
|
||
|
if(_dvpFirstVisible)
|
||
|
{
|
||
|
LONG vpScroll = _vpScroll + _dvpFirstVisible;
|
||
|
|
||
|
if(iDirection <= 0)
|
||
|
{
|
||
|
vpScroll += Elem(_iliFirstVisible)->_dvp;
|
||
|
}
|
||
|
|
||
|
ScrollView(_upScroll, vpScroll, FALSE, TRUE);
|
||
|
}
|
||
|
#endif // 0
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::CalcVLineScrollDelta (cli, fFractionalFirst)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Given a count of lines, positive or negative, calc the number
|
||
|
* of vertical units necessary to scroll the view to the start of
|
||
|
* the current line + the given count of lines.
|
||
|
*/
|
||
|
LONG CDisplayML::CalcVLineScrollDelta (
|
||
|
LONG cli,
|
||
|
BOOL fFractionalFirst )
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CalcVLineScrollDelta");
|
||
|
|
||
|
LONG vpScroll = 0;
|
||
|
|
||
|
if(fFractionalFirst && _dvpFirstVisible) // Scroll partial for 1st.
|
||
|
{
|
||
|
Assert(_dvpFirstVisible <= 0); // get jonmat
|
||
|
if(cli < 0)
|
||
|
{
|
||
|
cli++;
|
||
|
vpScroll = _dvpFirstVisible;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cli--;
|
||
|
vpScroll = Elem(_iliFirstVisible)->GetHeight() + _dvpFirstVisible;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(cli > 0)
|
||
|
{
|
||
|
// Scrolling down
|
||
|
cli = min(cli, Count() - _iliFirstVisible - 1);
|
||
|
|
||
|
if (!fFractionalFirst && (0 == cli))
|
||
|
{
|
||
|
// If we are scrolling down and on the last line but we haven't scrolled to
|
||
|
// the very bottom, then do so now.
|
||
|
AssertSz(0 == vpScroll,
|
||
|
"CDisplayML::CalcVLineScrollDelta last line & scroll");
|
||
|
vpScroll = _dvp - _vpScroll;
|
||
|
|
||
|
// Limit scroll length to approximately 3 lines.
|
||
|
vpScroll = min(vpScroll, 3 * GetDvpSystemFont());
|
||
|
}
|
||
|
}
|
||
|
else if(cli < 0)
|
||
|
{
|
||
|
// Scrolling up
|
||
|
cli = max(cli, -_iliFirstVisible);
|
||
|
|
||
|
// At the top.
|
||
|
if (!fFractionalFirst && (0 == cli))
|
||
|
{
|
||
|
// Make sure that we scroll back so first visible is 0.
|
||
|
vpScroll = _dvpFirstVisible;
|
||
|
|
||
|
// Limit scroll length to approximately 3 lines.
|
||
|
vpScroll = max(vpScroll, -3 * GetDvpSystemFont());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(cli)
|
||
|
vpScroll += VposFromLine(this, _iliFirstVisible + cli) - VposFromLine(this, _iliFirstVisible);
|
||
|
return vpScroll;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::ScrollView(upScroll, vpScroll, fTracking, fFractionalScroll)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Scroll view to new x and y position
|
||
|
*
|
||
|
* @devnote
|
||
|
* This method tries to adjust the y scroll pos before
|
||
|
* scrolling to display complete line at top. x scroll
|
||
|
* pos is adjusted to avoid scrolling all text off the
|
||
|
* view rectangle.
|
||
|
*
|
||
|
* Must be able to handle vpScroll <gt> pdp->dvp and vpScroll <lt> 0
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE if actual scrolling occurred,
|
||
|
* FALSE if no change
|
||
|
*/
|
||
|
BOOL CDisplayML::ScrollView (
|
||
|
LONG upScroll, //@parm New x scroll position
|
||
|
LONG vpScroll, //@parm New y scroll position
|
||
|
BOOL fTracking, //@parm TRUE indicates we are tracking scrollbar thumb
|
||
|
BOOL fFractionalScroll)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::ScrollView");
|
||
|
BOOL fTryAgain = TRUE;
|
||
|
LONG dupMax;
|
||
|
LONG dup = 0;
|
||
|
LONG dvp = 0;
|
||
|
RECTUV rcClient, rcClip;
|
||
|
CTxtSelection *psel = _ped->GetSelNC();
|
||
|
COleObject *pipo;
|
||
|
BOOL fRestoreCaret = FALSE;
|
||
|
LONG iliFirstVisible = _iliFirstVisible;
|
||
|
|
||
|
AssertSz(_ped->_fInPlaceActive, "CDisplayML::ScrollView() called when not in-place");
|
||
|
|
||
|
//For scrolling purposes, we clip to rcView's top and bottom, but rcClient's left and right
|
||
|
_ped->TxGetClientRect(&rcClient);
|
||
|
GetViewRect(rcClip, &rcClient);
|
||
|
rcClip.left = rcClient.left;
|
||
|
rcClip.right = rcClient.right;
|
||
|
|
||
|
if(upScroll == -1)
|
||
|
upScroll = _upScroll;
|
||
|
if(vpScroll == -1)
|
||
|
vpScroll = _vpScroll;
|
||
|
|
||
|
// Determine vertical scrolling pos
|
||
|
while(1)
|
||
|
{
|
||
|
BOOL fNothingBig = TRUE;
|
||
|
LONG vFirst;
|
||
|
LONG dvFirst;
|
||
|
LONG cpFirst;
|
||
|
LONG iliFirst;
|
||
|
LONG vpHeight;
|
||
|
LONG iliT;
|
||
|
|
||
|
vpScroll = min(vpScroll, GetMaxVpScroll());
|
||
|
vpScroll = max(0, vpScroll);
|
||
|
dvp = 0;
|
||
|
|
||
|
// Ensure all visible lines are recalced
|
||
|
if(!WaitForRecalcView())
|
||
|
return FALSE;
|
||
|
|
||
|
// Compute new first visible line
|
||
|
iliFirst = LineFromVpos(this, vpScroll, &vFirst, &cpFirst);
|
||
|
if(IsInPageView())
|
||
|
{
|
||
|
//REVIEW (keithcu) EBOOKS bug 424. Does this need to be here, or
|
||
|
//should the logic be somewhere else? Also, would it be better to round
|
||
|
//rather than to always round down?
|
||
|
CLine *pli = Elem(iliFirst);
|
||
|
for(; !pli->_fFirstOnPage && iliFirst; iliFirst--)
|
||
|
{
|
||
|
pli--; // Back up to previous line
|
||
|
vFirst -= pli->GetHeight();
|
||
|
vpScroll -= pli->GetHeight();
|
||
|
cpFirst -= pli->_cch;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( cpFirst < 0 )
|
||
|
{
|
||
|
// FUTURE (alexgo) this is pretty bogus, we should try to do
|
||
|
// better in the next rel.
|
||
|
|
||
|
TRACEERRORSZ("Display calc hosed, trying again");
|
||
|
InitVars();
|
||
|
_fNeedRecalc = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if(iliFirst < 0)
|
||
|
{
|
||
|
// No line at _vpScroll, use last line instead
|
||
|
iliFirst = max(0, Count() - 1);
|
||
|
cpFirst = _ped->GetTextLength() - Elem(iliFirst)->_cch;
|
||
|
vpScroll = _dvp - Elem(iliFirst)->GetHeight();
|
||
|
vFirst = _vpScroll;
|
||
|
}
|
||
|
if(IsInPageView())
|
||
|
{
|
||
|
AssertSz(Elem(iliFirst)->_fFirstOnPage,
|
||
|
"CDisplayML::ScrollView: _iliFirstVisible not top of page");
|
||
|
if(vpScroll > vFirst) // Tried to scroll beyond start
|
||
|
vpScroll = vFirst; // of last line
|
||
|
goto scrollit;
|
||
|
}
|
||
|
|
||
|
dvFirst = vFirst - vpScroll;
|
||
|
|
||
|
// Figure whether there is a big line
|
||
|
// (more that a third of the view rect)
|
||
|
for(iliT = iliFirst, vpHeight = dvFirst;
|
||
|
vpHeight < _dvpView && iliT < Count();
|
||
|
iliT++)
|
||
|
{
|
||
|
const CLine *pli = Elem(iliT);
|
||
|
if(pli->GetHeight() >= _dvpView / 3)
|
||
|
fNothingBig = FALSE;
|
||
|
vpHeight += pli->GetHeight();
|
||
|
}
|
||
|
|
||
|
// If no big line and first pass, try to adjust
|
||
|
// scrolling pos to show complete line at top
|
||
|
if(!fFractionalScroll && fTryAgain && fNothingBig && dvFirst != 0)
|
||
|
{
|
||
|
fTryAgain = FALSE; // prevent any infinite loop
|
||
|
|
||
|
Assert(dvFirst < 0);
|
||
|
|
||
|
Tracef(TRCSEVINFO, "adjusting scroll for partial line at %d", dvFirst);
|
||
|
// partial line visible at top, try to get a complete line showing
|
||
|
vpScroll += dvFirst;
|
||
|
|
||
|
LONG dvpLine = Elem(iliFirst)->GetHeight();
|
||
|
|
||
|
// Adjust the height of the scroll by the height of the first
|
||
|
// visible line if we are scrolling down or if we are using the
|
||
|
// thumb (tracking) and we are on the last page of the view.
|
||
|
if ((fTracking && vpScroll + _dvpView + dvpLine > _dvp)
|
||
|
|| (!fTracking && _vpScroll <= vpScroll))
|
||
|
{
|
||
|
// Scrolling down so move down a little more
|
||
|
vpScroll += dvpLine;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dvp = 0;
|
||
|
if(vpScroll != _vpScroll)
|
||
|
{
|
||
|
_dvpFirstVisible = dvFirst;
|
||
|
scrollit:
|
||
|
_iliFirstVisible = iliFirst;
|
||
|
_cpFirstVisible = cpFirst;
|
||
|
dvp = _vpScroll - vpScroll;
|
||
|
_vpScroll = vpScroll;
|
||
|
|
||
|
AssertSz(_vpScroll >= 0, "CDisplayML::ScrollView _vpScroll < 0");
|
||
|
AssertNr(VerifyFirstVisible());
|
||
|
if(!WaitForRecalcView())
|
||
|
return FALSE;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
CheckView();
|
||
|
|
||
|
// Determine horizontal scrolling pos.
|
||
|
|
||
|
dupMax = _dupLineMax;
|
||
|
|
||
|
// REVIEW (Victork) Restricting the range of the scroll is not really needed and could even be bad (bug 6104)
|
||
|
|
||
|
upScroll = min(upScroll, dupMax);
|
||
|
upScroll = max(0, upScroll);
|
||
|
|
||
|
dup = _upScroll - upScroll;
|
||
|
if(dup)
|
||
|
_upScroll = upScroll;
|
||
|
|
||
|
// Now perform the actual scrolling
|
||
|
if(IsMain() && (dvp || dup))
|
||
|
{
|
||
|
// Scroll only if scrolling < view dimensions and we are in-place
|
||
|
if(IsActive() && !IsTransparent() &&
|
||
|
dvp < _dvpView && dup < _dupView && !IsInPageView())
|
||
|
{
|
||
|
// FUTURE: (ricksa/alexgo): we may be able to get rid of
|
||
|
// some of these ShowCaret calls; they look bogus.
|
||
|
if (psel && psel->IsCaretShown())
|
||
|
{
|
||
|
_ped->TxShowCaret(FALSE);
|
||
|
fRestoreCaret = TRUE;
|
||
|
}
|
||
|
|
||
|
LONG dxp, dyp;
|
||
|
GetDxpDypFromDupDvp(dup, dvp, GetTflow(), dxp, dyp);
|
||
|
|
||
|
RECT rcxyClip;
|
||
|
RectFromRectuv(rcxyClip, rcClip);
|
||
|
_ped->TxScrollWindowEx(dxp, dyp, NULL, &rcxyClip);
|
||
|
|
||
|
if(fRestoreCaret)
|
||
|
_ped->TxShowCaret(FALSE);
|
||
|
}
|
||
|
else
|
||
|
_ped->TxInvalidateRect(&rcClip);
|
||
|
|
||
|
if(psel)
|
||
|
psel->UpdateCaret(FALSE);
|
||
|
|
||
|
if(!fTracking && dvp)
|
||
|
{
|
||
|
UpdateScrollBar(SB_VERT);
|
||
|
_ped->SendScrollEvent(EN_VSCROLL);
|
||
|
}
|
||
|
if(!fTracking && dup)
|
||
|
{
|
||
|
UpdateScrollBar(SB_HORZ);
|
||
|
_ped->SendScrollEvent(EN_HSCROLL);
|
||
|
}
|
||
|
|
||
|
// FUTURE: since we're now repositioning in place active
|
||
|
// objects every time we draw, this call seems to be
|
||
|
// superfluous (AndreiB)
|
||
|
|
||
|
// Tell object subsystem to reposition any in place objects
|
||
|
if(_ped->GetObjectCount())
|
||
|
{
|
||
|
pipo = _ped->GetObjectMgr()->GetInPlaceActiveObject();
|
||
|
if(pipo)
|
||
|
pipo->OnReposition();
|
||
|
}
|
||
|
}
|
||
|
bool fNotifyPageChange(false);
|
||
|
if(IsInPageView() && iliFirstVisible != _iliFirstVisible)
|
||
|
{
|
||
|
CalculatePage(iliFirstVisible);
|
||
|
fNotifyPageChange = true;
|
||
|
}
|
||
|
|
||
|
// Update the View after state has been updated
|
||
|
if(IsMain() && (dvp || dup))
|
||
|
_ped->TxUpdateWindow();
|
||
|
|
||
|
if(fNotifyPageChange)
|
||
|
GetPed()->TxNotify(EN_PAGECHANGE, NULL);
|
||
|
|
||
|
return dvp || dup;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::GetScrollRange(nBar)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Returns the max part of a scrollbar range for scrollbar <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 = GetMaxVpScroll();
|
||
|
}
|
||
|
else if((_ped->TxGetScrollBars() & WS_HSCROLL) && _fUScrollEnabled)
|
||
|
{
|
||
|
// Scroll range is maximum width.
|
||
|
lRange = max(0, _dupLineMax + _ped->GetCaretWidth());
|
||
|
}
|
||
|
// Since thumb messages are limited to 16-bit, limit range to 16-bit
|
||
|
lRange = min(lRange, _UI16_MAX);
|
||
|
return lRange;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::UpdateScrollBar(nBar, fUpdateRange)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Update either the horizontal or the vertical scrollbar and
|
||
|
* figure whether the scrollbar should be visible or not.
|
||
|
*
|
||
|
* @rdesc
|
||
|
* BOOL
|
||
|
*/
|
||
|
BOOL CDisplayML::UpdateScrollBar(
|
||
|
INT nBar, //@parm Which scroll bar : SB_HORZ, SB_VERT
|
||
|
BOOL fUpdateRange) //@parm Should the range be recomputed and updated
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::UpdateScrollBar");
|
||
|
|
||
|
// Note: In the old days we didn't allow autosize & scroll bars, so to keep
|
||
|
// forms working, we need this special logic with respect to autosize.
|
||
|
if (!IsActive() || _fInRecalcScrollBars ||
|
||
|
!_ped->fInOurHost() && _ped->TxGetAutoSize())
|
||
|
{
|
||
|
// No scroll bars unless we are inplace active and we are not in the
|
||
|
// process of updating scroll bars already.
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
const DWORD dwScrollBars = _ped->TxGetScrollBars();
|
||
|
const BOOL fHide = !(dwScrollBars & ES_DISABLENOSCROLL);
|
||
|
BOOL fReturn = FALSE;
|
||
|
BOOL fEnabled = TRUE;
|
||
|
BOOL fEnabledOld;
|
||
|
LONG lScroll;
|
||
|
CTxtSelection *psel = _ped->GetSelNC();
|
||
|
BOOL fShowCaret = FALSE;
|
||
|
|
||
|
// Get scrolling position
|
||
|
if(nBar == SB_VERT)
|
||
|
{
|
||
|
if(!(dwScrollBars & WS_VSCROLL))
|
||
|
return FALSE;
|
||
|
|
||
|
fEnabledOld = _fVScrollEnabled;
|
||
|
if(GetMaxVpScroll() <= _dvpView)
|
||
|
fEnabled = FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(!(dwScrollBars & WS_HSCROLL))
|
||
|
{
|
||
|
// Even if we don't have scrollbars, we may allow horizontal
|
||
|
// scrolling.
|
||
|
if(!_fUScrollEnabled && _dupLineMax > _dupView)
|
||
|
_fUScrollEnabled = !!(dwScrollBars & ES_AUTOHSCROLL);
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
fEnabledOld = _fUScrollEnabled;
|
||
|
if(_dupLineMax <= _dupView)
|
||
|
fEnabled = FALSE;
|
||
|
}
|
||
|
|
||
|
// Don't allow ourselves to be re-entered.
|
||
|
// Be sure to turn this to FALSE on exit
|
||
|
_fInRecalcScrollBars = TRUE;
|
||
|
|
||
|
// !s beforehand because all true values aren't necessarily equal
|
||
|
if(!fEnabled != !fEnabledOld)
|
||
|
{
|
||
|
if(_fDeferUpdateScrollBar)
|
||
|
_fUpdateScrollBarDeferred = TRUE;
|
||
|
else
|
||
|
{
|
||
|
if (nBar == SB_HORZ)
|
||
|
_fUScrollEnabled = fEnabled;
|
||
|
else
|
||
|
_fVScrollEnabled = fEnabled;
|
||
|
}
|
||
|
|
||
|
if(!_fDeferUpdateScrollBar)
|
||
|
{
|
||
|
if(!fHide)
|
||
|
{
|
||
|
// Don't hide scrollbar, just disable
|
||
|
_ped->TxEnableScrollBar(nBar, fEnabled ? ESB_ENABLE_BOTH : ESB_DISABLE_BOTH);
|
||
|
|
||
|
if (!fEnabled)
|
||
|
{
|
||
|
// The scroll bar is disabled. Therefore, all the text fits
|
||
|
// on the screen so make sure the drawing reflects this.
|
||
|
_vpScroll = 0;
|
||
|
_dvpFirstVisible = 0;
|
||
|
_cpFirstVisible = 0;
|
||
|
_iliFirstVisible = 0;
|
||
|
_sPage = 0;
|
||
|
_ped->TxInvalidate();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fReturn = TRUE;
|
||
|
// Make sure to hide caret before showing scrollbar
|
||
|
if(psel)
|
||
|
fShowCaret = psel->ShowCaret(FALSE);
|
||
|
|
||
|
// Hide or show scroll bar
|
||
|
_ped->TxShowScrollBar(nBar, fEnabled);
|
||
|
// The scroll bar affects the window which in turn affects the
|
||
|
// display. Therefore, if word wrap, repaint
|
||
|
_ped->TxInvalidate();
|
||
|
// Needed for bug fix #5521
|
||
|
_ped->TxUpdateWindow();
|
||
|
|
||
|
if(fShowCaret)
|
||
|
psel->ShowCaret(TRUE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set scrollbar range and thumb position
|
||
|
if(fEnabled)
|
||
|
{
|
||
|
if(fUpdateRange && !_fDeferUpdateScrollBar)
|
||
|
_ped->TxSetScrollRange(nBar, 0, GetScrollRange(nBar), FALSE);
|
||
|
|
||
|
if(_fDeferUpdateScrollBar)
|
||
|
_fUpdateScrollBarDeferred = TRUE;
|
||
|
else
|
||
|
{
|
||
|
lScroll = (nBar == SB_VERT)
|
||
|
? ConvertVPosToScrollPos(_vpScroll)
|
||
|
: ConvertUPosToScrollPos(_upScroll);
|
||
|
|
||
|
_ped->TxSetScrollPos(nBar, lScroll, TRUE);
|
||
|
}
|
||
|
}
|
||
|
_fInRecalcScrollBars = FALSE;
|
||
|
return fReturn;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::GetNaturalSize(hdcDraw, hicTarget, dwMode, pwidth, pheight)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Recalculate display to input width & height for TXTNS_FITTOCONTENT[2].
|
||
|
*
|
||
|
* @rdesc
|
||
|
* S_OK - Call completed successfully <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 dvp = 0;
|
||
|
LONG cchText = _ped->GetTextLength();
|
||
|
BOOL fFirstInPara = TRUE;
|
||
|
|
||
|
LONG dulMax = GetWordWrap() ? DXtoLX(widthView) : duMax;
|
||
|
|
||
|
// The following loop generates new lines
|
||
|
do
|
||
|
{ // Stuff text into new line
|
||
|
UINT uiFlags = 0;
|
||
|
|
||
|
// If word wrap is turned on, then we want to break on
|
||
|
// words, otherwise, measure white space, etc.
|
||
|
if(GetWordWrap())
|
||
|
uiFlags = MEASURE_BREAKATWORD;
|
||
|
|
||
|
if(fFirstInPara)
|
||
|
uiFlags |= MEASURE_FIRSTINPARA;
|
||
|
|
||
|
me.SetDulLayout(dulMax);
|
||
|
if(!Measure(me, &liNew, 0, uiFlags))
|
||
|
{
|
||
|
hr = E_FAIL;
|
||
|
goto exit;
|
||
|
}
|
||
|
fFirstInPara = liNew._fHasEOP;
|
||
|
|
||
|
// Keep track of width of widest line
|
||
|
lineWidth = liNew._dup;
|
||
|
if(dwMode == TXTNS_FITTOCONTENT2)
|
||
|
lineWidth += liNew._upStart + me.GetRightIndent();
|
||
|
xWidth = max(xWidth, lineWidth);
|
||
|
dvp += liNew.GetHeight(); // Bump height
|
||
|
|
||
|
} while (me.GetCp() < cchText);
|
||
|
|
||
|
// Add caret size to width to guarantee that text fits. We don't
|
||
|
// want to word break because the caret won't fit when the caller
|
||
|
// tries a window this size.
|
||
|
xWidth += _ped->GetCaretWidth();
|
||
|
|
||
|
*pwidth = xWidth;
|
||
|
*pheight = dvp;
|
||
|
|
||
|
// Restore insets so output reflects true client rect needed
|
||
|
*pwidth += widthAdj;
|
||
|
*pheight += heightAdj;
|
||
|
|
||
|
exit:
|
||
|
SetClientHeight(yOrigHeightClient);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::Clone()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Make a copy of this object
|
||
|
*
|
||
|
* @rdesc
|
||
|
* NULL - failed
|
||
|
* CDisplay *
|
||
|
*
|
||
|
* @devnote
|
||
|
* Caller of this routine is the owner of the new display object.
|
||
|
*/
|
||
|
CDisplay *CDisplayML::Clone() const
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::Clone");
|
||
|
|
||
|
CDisplayML *pdp = new CDisplayML(_ped);
|
||
|
|
||
|
if(pdp)
|
||
|
{
|
||
|
// Initialize our base class
|
||
|
if(pdp->CDisplay::Init())
|
||
|
{
|
||
|
pdp->InitFromDisplay(this);
|
||
|
pdp->_upScroll = _upScroll;
|
||
|
pdp->_fVScrollEnabled = _fVScrollEnabled;
|
||
|
pdp->_fUScrollEnabled = _fUScrollEnabled;
|
||
|
pdp->_fWordWrap = _fWordWrap;
|
||
|
pdp->_cpFirstVisible = _cpFirstVisible;
|
||
|
pdp->_iliFirstVisible = _iliFirstVisible;
|
||
|
pdp->_vpScroll = _vpScroll;
|
||
|
pdp->ResetDrawInfo(this);
|
||
|
|
||
|
if(_pddTarget)
|
||
|
{
|
||
|
// Create a duplicate target device for this object
|
||
|
pdp->SetMainTargetDC(_pddTarget->GetDC(), _dulTarget);
|
||
|
}
|
||
|
|
||
|
// This can't be the active view since it is a clone
|
||
|
// of some view.
|
||
|
pdp->SetActiveFlag(FALSE);
|
||
|
}
|
||
|
}
|
||
|
return pdp;
|
||
|
}
|
||
|
|
||
|
void CDisplayML::DeferUpdateScrollBar()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::DeferUpdateScrollBar");
|
||
|
|
||
|
_fDeferUpdateScrollBar = TRUE;
|
||
|
_fUpdateScrollBarDeferred = FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL CDisplayML::DoDeferredUpdateScrollBar()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::DoDeferredUpdateScrollBar");
|
||
|
|
||
|
_fDeferUpdateScrollBar = FALSE;
|
||
|
if(!_fUpdateScrollBarDeferred)
|
||
|
return FALSE;
|
||
|
|
||
|
_fUpdateScrollBarDeferred = FALSE;
|
||
|
BOOL fHorizontalUpdated = UpdateScrollBar(SB_HORZ, TRUE);
|
||
|
|
||
|
return UpdateScrollBar(SB_VERT, TRUE) || fHorizontalUpdated;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::GetMaxUScroll()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Get the maximum x scroll value
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Maximum x scroll value
|
||
|
*
|
||
|
*/
|
||
|
LONG CDisplayML::GetMaxUScroll() const
|
||
|
{
|
||
|
return _dupLineMax + _ped->GetCaretWidth();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::CreateEmptyLine()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Create an empty line
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE - iff successful
|
||
|
*/
|
||
|
BOOL CDisplayML::CreateEmptyLine()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CreateEmptyLine");
|
||
|
|
||
|
// Make sure that this is being called appropriately
|
||
|
AssertSz(_ped->GetTextLength() == 0,
|
||
|
"CDisplayML::CreateEmptyLine called inappropriately");
|
||
|
|
||
|
CMeasurer me(this); // Create a measurer
|
||
|
CLine * pliNew = Add(1, NULL); // Add one new line
|
||
|
|
||
|
if(!pliNew)
|
||
|
{
|
||
|
_ped->GetCallMgr()->SetOutOfMemory();
|
||
|
TRACEWARNSZ("CDisplayML::CreateEmptyLine unable to add CLine to CLineArray");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// Measure the empty line
|
||
|
me.SetDulLayout(1);
|
||
|
if(!pliNew->Measure(me, MEASURE_BREAKATWORD | MEASURE_FIRSTINPARA))
|
||
|
{
|
||
|
Assert(FALSE);
|
||
|
return FALSE;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::AdjustToDisplayLastLine()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Calculate the vpScroll necessary to get the last line to display
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Updated vpScroll
|
||
|
*
|
||
|
*/
|
||
|
LONG CDisplayML::AdjustToDisplayLastLine(
|
||
|
LONG yBase, //@parm actual vpScroll to display
|
||
|
LONG vpScroll) //@parm proposed amount to scroll
|
||
|
{
|
||
|
LONG iliFirst;
|
||
|
LONG vFirst;
|
||
|
|
||
|
if(yBase >= _dvp)
|
||
|
{
|
||
|
// Want last line to be entirely displayed.
|
||
|
// Compute new first visible line
|
||
|
iliFirst = LineFromVpos(this, vpScroll, &vFirst, NULL);
|
||
|
|
||
|
// Is top line partial?
|
||
|
if(vpScroll != vFirst)
|
||
|
{
|
||
|
// Yes - bump scroll to the next line so the ScrollView
|
||
|
// won't bump the scroll back to display the entire
|
||
|
// partial line since we want the bottom to display.
|
||
|
vpScroll = VposFromLine(this, iliFirst + 1);
|
||
|
}
|
||
|
}
|
||
|
return vpScroll;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::GetResizeHeight()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Calculates height to return for a request resize
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Updated vpScroll
|
||
|
*/
|
||
|
LONG CDisplayML::GetResizeHeight() const
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetResizeHeight");
|
||
|
|
||
|
return CalcScrollHeight(_dvp);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::RebindFirstVisible(fResetCp)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* rebind the first visible line
|
||
|
*
|
||
|
*/
|
||
|
void CDisplayML::RebindFirstVisible(
|
||
|
BOOL fResetCp) //@parm If TRUE, reset cp to 0
|
||
|
{
|
||
|
LONG cp = fResetCp ? 0 : _cpFirstVisible;
|
||
|
|
||
|
// Change first visible entries because CLinePtr::SetCp() and
|
||
|
// YPosFromLine() use them, but they're not valid
|
||
|
_dvpFirstVisible = 0;
|
||
|
_cpFirstVisible = 0;
|
||
|
_iliFirstVisible = 0;
|
||
|
_vpScroll = 0;
|
||
|
|
||
|
// Recompute scrolling position and first visible values after edit
|
||
|
Set_yScroll(cp);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::Set_yScroll(cp)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Set _yScroll corresponding to cp, making sure that in PageView
|
||
|
* the display starts at the top of a page
|
||
|
*/
|
||
|
void CDisplayML::Set_yScroll(
|
||
|
LONG cp) //@parm cp at which to set valid _yScroll
|
||
|
{
|
||
|
// Recompute scrolling position and first visible values after edit
|
||
|
CLinePtr rp(this);
|
||
|
|
||
|
if(!rp.SetCp(cp, FALSE)) // Couldn't get to cp, so find out
|
||
|
cp = rp.CalculateCp(); // cp we got to
|
||
|
|
||
|
_vpScroll = VposFromLine(this, rp);
|
||
|
_cpFirstVisible = cp - rp.GetIch();
|
||
|
_iliFirstVisible = rp;
|
||
|
Sync_yScroll(); // Make sure _yScroll, _sPage, etc.,
|
||
|
} // are valid in PageView
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::Sync_yScroll()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Make sure that in PageView the display starts at the top of a page.
|
||
|
* Notify client if page number changes
|
||
|
*/
|
||
|
void CDisplayML::Sync_yScroll()
|
||
|
{
|
||
|
if(IsInPageView()) // _yScroll must be to line at
|
||
|
{ // top of page
|
||
|
CLine *pli = Elem(_iliFirstVisible);
|
||
|
for(; !pli->_fFirstOnPage && _iliFirstVisible; _iliFirstVisible--)
|
||
|
{
|
||
|
pli--; // Back up to previous line
|
||
|
_vpScroll -= pli->GetHeight();
|
||
|
_cpFirstVisible -= pli->_cch;
|
||
|
}
|
||
|
LONG sPage = _sPage;
|
||
|
if(sPage != CalculatePage(0))
|
||
|
{
|
||
|
_ped->TxInvalidate();
|
||
|
_ped->TxNotify(EN_PAGECHANGE, NULL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::Paginate(ili, fRebindFirstVisible)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Recompute page breaks from ili on
|
||
|
*
|
||
|
* @rdesc
|
||
|
* TRUE if success
|
||
|
*/
|
||
|
BOOL CDisplayML::Paginate (
|
||
|
LONG ili, //@parm Line to redo pagination from
|
||
|
BOOL fRebindFirstVisible) //@parm If TRUE, call RebindFirstVisible()
|
||
|
{
|
||
|
LONG cLine = Count();
|
||
|
|
||
|
if(!IsInPageView() || ili >= cLine || ili < 0)
|
||
|
return FALSE;
|
||
|
|
||
|
LONG iliSave = ili;
|
||
|
CLine * pli = Elem(ili);
|
||
|
|
||
|
// Synchronize to top of current page
|
||
|
for(; ili && !pli->_fFirstOnPage; pli--, ili--)
|
||
|
;
|
||
|
// Guard against widow-orphan changes by backing up an extra page
|
||
|
if(ili && iliSave - ili < 2)
|
||
|
{
|
||
|
for(pli--, ili--; ili && !pli->_fFirstOnPage; pli--, ili--)
|
||
|
;
|
||
|
}
|
||
|
|
||
|
LONG cLinePage = 1; // One line on new page
|
||
|
LONG dvpHeight = pli->GetHeight(); // Height on new page
|
||
|
|
||
|
pli->_fFirstOnPage = TRUE; // First line on page
|
||
|
ili++; // One less line to consider
|
||
|
pli++; // Advance to next line
|
||
|
|
||
|
for(; ili < cLine; ili++, pli++) // Process all lines from ili to EOD
|
||
|
{
|
||
|
dvpHeight += pli->GetHeight(); // Add in current line height
|
||
|
cLinePage++; // One more line on page (maybe)
|
||
|
pli->_fFirstOnPage = FALSE;
|
||
|
|
||
|
CLine *pliPrev = pli - 1; // Point at previous line
|
||
|
|
||
|
if(dvpHeight > _dvpView || pliPrev->_fHasFF || pli->_fPageBreakBefore)
|
||
|
{
|
||
|
cLinePage--;
|
||
|
if(cLinePage > 1 && !pliPrev->_fHasFF) // && fWidowOrphanControl)
|
||
|
{
|
||
|
if(pli->_fHasFF && pli->_cch == 1) // FF line height causing
|
||
|
continue; // eject, so leave it on current page
|
||
|
|
||
|
//If we are in the middle of a wrapped object, bump it to next page
|
||
|
//We do not do widow/orphan if it could divide wrapped objects between pages
|
||
|
if (_ped->IsRich())
|
||
|
{
|
||
|
if (pli->_cObjectWrapLeft || pli->_cObjectWrapRight)
|
||
|
{
|
||
|
CLine *pliOrig = pli;
|
||
|
if (pli->_cObjectWrapLeft && !pli->_fFirstWrapLeft)
|
||
|
{
|
||
|
while (!pli->_fFirstWrapLeft)
|
||
|
pli--;
|
||
|
}
|
||
|
int cLineBack = pliOrig - pli;
|
||
|
pli = pliOrig;
|
||
|
|
||
|
if (pli->_cObjectWrapRight && !pli->_fFirstWrapRight)
|
||
|
{
|
||
|
while (!pli->_fFirstWrapRight)
|
||
|
pli--;
|
||
|
}
|
||
|
cLineBack = max(cLineBack, (int)(pliOrig - pli));
|
||
|
pli = pliOrig;
|
||
|
|
||
|
if (cLineBack < cLinePage) //Don't do this if object is larger than page
|
||
|
{
|
||
|
cLinePage -= cLineBack;
|
||
|
pliPrev -= cLineBack;
|
||
|
pli -= cLineBack;
|
||
|
ili -= cLineBack;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If this line and the previous one are in the same para,
|
||
|
// we might need widow/orphan logic
|
||
|
if (!pli->_fFirstInPara && !pliPrev->_cObjectWrapLeft &&
|
||
|
!pliPrev->_cObjectWrapRight && (cLinePage > 1) )
|
||
|
{
|
||
|
// If this line ends in an EOP bump both to following page
|
||
|
// (widow/orphan), but only if either the line is short, or
|
||
|
// we absolutely know that there will only be one line on
|
||
|
// the page. Do the same if prev line ends in a hyphenated
|
||
|
// word, and the preceding line does not
|
||
|
if (pli->_cchEOP && (pli->_dup < _dupView/2 || ili >= cLine - 1) || // Do we need -2 instead of -1?
|
||
|
pliPrev->_ihyph && ili > 1 && !pliPrev->_fFirstOnPage && !pliPrev[-1]._ihyph)
|
||
|
{
|
||
|
cLinePage--; // Point to previous line
|
||
|
pliPrev--;
|
||
|
pli--;
|
||
|
ili--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Don't end a page with a heading.
|
||
|
if(cLinePage > 1 && pliPrev->_nHeading &&
|
||
|
!pliPrev->_cObjectWrapLeft && !pliPrev->_cObjectWrapRight)
|
||
|
{
|
||
|
cLinePage--;
|
||
|
pliPrev--;
|
||
|
pli--;
|
||
|
ili--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
pli->_fFirstOnPage = TRUE; // Define first line on page
|
||
|
cLinePage = 1; // One line on new page
|
||
|
dvpHeight = pli->GetHeight(); // Current height of new page
|
||
|
}
|
||
|
}
|
||
|
if(fRebindFirstVisible)
|
||
|
RebindFirstVisible();
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::CalculatePage(iliFirst)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Compute page number for _iliFirstVisible starting with iliFirst
|
||
|
*
|
||
|
* @rdesc
|
||
|
* Page number calculated
|
||
|
*/
|
||
|
LONG CDisplayML::CalculatePage (
|
||
|
LONG iliFirst)
|
||
|
{
|
||
|
if(Count() < 2 || !IsInPageView())
|
||
|
{
|
||
|
_sPage = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if(iliFirst < 1)
|
||
|
_sPage = 0;
|
||
|
|
||
|
Assert(iliFirst >= 0 && iliFirst < Count());
|
||
|
|
||
|
LONG iDir = 1;
|
||
|
LONG n = _iliFirstVisible - iliFirst;
|
||
|
CLine *pli = Elem(iliFirst); // Point at next/previous line
|
||
|
|
||
|
if(n < 0)
|
||
|
{
|
||
|
n = -n;
|
||
|
iDir = -1;
|
||
|
}
|
||
|
else
|
||
|
pli++;
|
||
|
|
||
|
for(; n--; pli += iDir)
|
||
|
if(pli->_fFirstOnPage)
|
||
|
{
|
||
|
_sPage += iDir;
|
||
|
if(_sPage < 0)
|
||
|
_sPage = 0;
|
||
|
}
|
||
|
|
||
|
AssertSz(Elem(_iliFirstVisible)->_fFirstOnPage,
|
||
|
"CDisplayML::CalculatePage: _iliFirstVisible not top of page");
|
||
|
|
||
|
return _sPage;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::GetPage(piPage, dwFlags, pcrg)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Get page number for _iliFirstVisible
|
||
|
*
|
||
|
* @rdesc
|
||
|
* HRESULT = !piPage ? E_INVALIDARG :
|
||
|
* IsInPageView() ? NOERROR : E_FAIL
|
||
|
*/
|
||
|
HRESULT CDisplayML::GetPage(
|
||
|
LONG *piPage, //@parm Out parm for page number
|
||
|
DWORD dwFlags, //@parm Flags for which page to use
|
||
|
CHARRANGE *pcrg) //@parm Out parm for CHARRANGE for page
|
||
|
{
|
||
|
if(!piPage)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
*piPage = 0;
|
||
|
|
||
|
if(dwFlags) // No flags defined yet
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
if(!IsInPageView())
|
||
|
return E_FAIL;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
if(_sPage < 20)
|
||
|
{
|
||
|
LONG sPage = _sPage;
|
||
|
CalculatePage(0);
|
||
|
AssertSz(sPage == _sPage, "CDisplayML::GetPage: invalid cached page number");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
*piPage = _sPage;
|
||
|
if(pcrg)
|
||
|
{
|
||
|
pcrg->cpMin = _cpFirstVisible;
|
||
|
GetCliVisible(&pcrg->cpMost, TRUE);
|
||
|
}
|
||
|
return NOERROR;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::SetPage(iPage)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Go to page iPage
|
||
|
*
|
||
|
* @rdesc
|
||
|
* HRESULT
|
||
|
*/
|
||
|
HRESULT CDisplayML::SetPage (
|
||
|
LONG iPage)
|
||
|
{
|
||
|
if(!IsInPageView())
|
||
|
return E_FAIL;
|
||
|
|
||
|
LONG nLine = Count();
|
||
|
|
||
|
if(iPage < 0 || !nLine)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
CLine *pli = Elem(0); // Scroll from page 0
|
||
|
LONG vpScroll = 0;
|
||
|
LONG vpScrollLast = 0;
|
||
|
|
||
|
nLine--; // Decrement nLine so pli++ below will always be valid
|
||
|
for(LONG ili = 0; ili < nLine && iPage; ili++)
|
||
|
{
|
||
|
vpScroll += pli->GetHeight();
|
||
|
|
||
|
pli++;
|
||
|
if(pli->_fFirstOnPage) // Start of new page
|
||
|
{
|
||
|
vpScrollLast = vpScroll;
|
||
|
iPage--; // One less to go
|
||
|
}
|
||
|
}
|
||
|
if(!_iliFirstVisible) // This shouldn't be necessary...
|
||
|
_vpScroll = 0;
|
||
|
|
||
|
ScrollView(_upScroll, vpScrollLast, FALSE, FALSE);
|
||
|
return NOERROR;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::GetCurrentPageHeight()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* Return page height of current page in PageView mode
|
||
|
*
|
||
|
* @rdesc
|
||
|
* page height of current page in PageView mode; else 0;
|
||
|
*/
|
||
|
LONG CDisplayML::GetCurrentPageHeight() const
|
||
|
{
|
||
|
if(!IsInPageView())
|
||
|
return 0;
|
||
|
|
||
|
LONG cLine = Count();
|
||
|
LONG dvp = 0;
|
||
|
LONG i = _iliFirstVisible;
|
||
|
CLine * pli = Elem(i);
|
||
|
|
||
|
do
|
||
|
{
|
||
|
dvp += pli->GetHeight(); // Add in first line's height in any
|
||
|
pli++; // case
|
||
|
i++;
|
||
|
}
|
||
|
while(i < cLine && !pli->_fFirstOnPage);
|
||
|
|
||
|
return dvp;
|
||
|
}
|
||
|
|
||
|
|
||
|
// ================================ DEBUG methods ============================================
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
/*
|
||
|
* CDisplayML::CheckLineArray()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* DEBUG routine that Asserts unless:
|
||
|
* 1) sum of all line counts equals count of characters in story
|
||
|
* 2) sum of all line heights equals height of display galley
|
||
|
*/
|
||
|
void CDisplayML::CheckLineArray() const
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CheckLineArray");
|
||
|
|
||
|
LONG ili = Count();
|
||
|
|
||
|
// If we are marked as needing a recalc or if we are in the process of a
|
||
|
// background recalc, we cannot verify the line array
|
||
|
if(!_fRecalcDone || _fNeedRecalc || !ili)
|
||
|
return;
|
||
|
|
||
|
LONG cchText = _ped->GetTextLength();
|
||
|
|
||
|
if (!cchText)
|
||
|
return;
|
||
|
|
||
|
LONG cp = 0;
|
||
|
BOOL fFirstInPara;
|
||
|
BOOL fPrevLineEOP = TRUE;
|
||
|
LONG dvp = 0;
|
||
|
CLine const *pli = Elem(0);
|
||
|
CTxtPtr tp(_ped, 0);
|
||
|
|
||
|
while(ili--)
|
||
|
{
|
||
|
fFirstInPara = pli->_fFirstInPara;
|
||
|
if(fPrevLineEOP ^ fFirstInPara)
|
||
|
{
|
||
|
tp.SetCp(cp);
|
||
|
AssertSz(fFirstInPara && IsASCIIEOP(tp.GetPrevChar()),
|
||
|
"CDisplayML::CheckLineArray: Invalid first/prev flags");
|
||
|
}
|
||
|
AssertSz(pli->_cch, "CDisplayML::CheckLineArray: cch == 0");
|
||
|
|
||
|
dvp += pli->GetHeight();
|
||
|
cp += pli->_cch;
|
||
|
fPrevLineEOP = pli->_fHasEOP;
|
||
|
pli++;
|
||
|
}
|
||
|
|
||
|
if((cp != cchText) && (cp != _cpCalcMax))
|
||
|
{
|
||
|
Tracef(TRCSEVINFO, "sigma (*this)[]._cch = %ld, cchText = %ld", cp, cchText);
|
||
|
AssertSz(FALSE,
|
||
|
"CDisplayML::CheckLineArray: sigma(*this)[]._cch != cchText");
|
||
|
}
|
||
|
|
||
|
if(dvp != _dvp)
|
||
|
{
|
||
|
Tracef(TRCSEVINFO, "sigma (*this)[]._dvp = %ld, _dvp = %ld", dvp, _dvp);
|
||
|
AssertSz(FALSE,
|
||
|
"CDisplayML::CheckLineArray: sigma(*this)[]._dvp != _dvp");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CDisplayML::DumpLines(
|
||
|
LONG iliFirst,
|
||
|
LONG cli)
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::DumpLines");
|
||
|
|
||
|
LONG cch;
|
||
|
LONG ili;
|
||
|
TCHAR rgch[512];
|
||
|
|
||
|
if(Count() == 1)
|
||
|
wcscpy(rgch, TEXT("1 line"));
|
||
|
else
|
||
|
wsprintf(rgch, TEXT("%d lines"), Count());
|
||
|
|
||
|
#ifdef UNICODE
|
||
|
// TraceTag needs to take UNICODE...
|
||
|
#else
|
||
|
TRACEINFOSZ(TRCSEVINFO, rgch);
|
||
|
#endif
|
||
|
|
||
|
if(cli < 0)
|
||
|
cli = Count();
|
||
|
else
|
||
|
cli = min(cli, Count());
|
||
|
if(iliFirst < 0)
|
||
|
iliFirst = Count() - cli;
|
||
|
else
|
||
|
cli = min(cli, Count() - iliFirst);
|
||
|
|
||
|
for(ili = iliFirst; cli > 0; ili++, cli--)
|
||
|
{
|
||
|
const CLine * const pli = Elem(ili);
|
||
|
|
||
|
wsprintf(rgch, TEXT("Line %d (%ldc%ldw%ldh): \""), ili, pli->_cch,
|
||
|
pli->_dup, pli->GetHeight());
|
||
|
cch = wcslen(rgch);
|
||
|
cch += GetLineText(ili, rgch + cch, CchOfCb(sizeof(rgch)) - cch - 4);
|
||
|
rgch[cch++] = TEXT('\"');
|
||
|
rgch[cch] = TEXT('\0');
|
||
|
#ifdef UNICODE
|
||
|
// TraceTag needs to take UNICODE...
|
||
|
#else
|
||
|
TRACEINFOSZ(TRCSEVINFO, rgch);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::CheckView()
|
||
|
*
|
||
|
* @mfunc
|
||
|
* DEBUG routine that checks coherence between _iliFirstVisible,
|
||
|
* _cpFirstVisible, and _dvpFirstVisible
|
||
|
*/
|
||
|
void CDisplayML::CheckView()
|
||
|
{
|
||
|
TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CheckView");
|
||
|
|
||
|
LONG dvp;
|
||
|
VerifyFirstVisible(&dvp);
|
||
|
|
||
|
if(dvp != _vpScroll + _dvpFirstVisible && !IsInPageView())
|
||
|
{
|
||
|
Tracef(TRCSEVINFO, "sigma CLine._dvp = %ld, CDisplay.vFirstLine = %ld", dvp, _vpScroll + _dvpFirstVisible);
|
||
|
AssertSz(FALSE, "CLine._dvp != VIEW.vFirstLine");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* CDisplayML::VerifyFirstVisible(pHeight)
|
||
|
*
|
||
|
* @mfunc
|
||
|
* DEBUG routine that checks coherence between _iliFirstVisible
|
||
|
* and _cpFirstVisible
|
||
|
*
|
||
|
* @rdesc TRUE if things are hunky dory; FALSE otherwise
|
||
|
*/
|
||
|
BOOL CDisplayML::VerifyFirstVisible(
|
||
|
LONG *pHeight)
|
||
|
{
|
||
|
LONG cchSum;
|
||
|
LONG ili = _iliFirstVisible;
|
||
|
CLine const *pli = Elem(0);
|
||
|
LONG dvp;
|
||
|
|
||
|
for(cchSum = dvp = 0; ili--; pli++)
|
||
|
{
|
||
|
cchSum += pli->_cch;
|
||
|
dvp += pli->GetHeight();
|
||
|
}
|
||
|
|
||
|
if(pHeight)
|
||
|
*pHeight = dvp;
|
||
|
|
||
|
if(cchSum != _cpFirstVisible)
|
||
|
{
|
||
|
Tracef(TRCSEVINFO, "sigma CLine._cch = %ld, CDisplay.cpFirstVisible = %ld", cchSum, _cpMin);
|
||
|
AssertSz(FALSE, "sigma CLine._cch != VIEW.cpMin");
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
#endif // DEBUG
|
||
|
|
||
|
|