/*  
 *	@doc INTERNAL
 *
 *	@module	DISPML.CPP -- CDisplayML class |
 *
 *		This is the Multi-line display engine.  See disp.c for the base class
 *		methods and dispsl.c for the single-line display engine.
 *	
 *	Owner:<nl>
 *		RichEdit 1.0 code: David R. Fulmer
 *		Christian Fortini (initial conversion to C++)
 *		Murray Sargent
 *		Rick Sailor (for most of RE 2.0)
 *
 *	Copyright (c) 1995-1998, Microsoft Corporation. All rights reserved.
 */

#include "_common.h"
#include "_dispml.h"
#include "_edit.h"
#include "_font.h"
#include "_measure.h"
#include "_render.h"
#include "_select.h"
#include "_dfreeze.h"

/*
#include "icecap.h"

class CCapProfile
{
public:
	CCapProfile() { StartProfile(PROFILE_THREADLEVEL, PROFILE_CURRENTID); }
	~CCapProfile() { StopProfile(PROFILE_THREADLEVEL, PROFILE_CURRENTID); }
};
*/
ASSERTDATA

//
//	Invariant support
//
#define DEBUG_CLASSNAME	CDisplayML
#include "_invar.h"

// Timer tick counts for background task
#define cmsecBgndInterval 	300
#define cmsecBgndBusy 		100

// Lines ahead
const LONG cExtraBeforeLazy = 60;

// If we need to calc at least this many characters, then put up a wait
// cursor.  NB!  4096 is not a measured number; it just seemed like a good
// one.
#define NUMCHARFORWAITCURSOR	4096	

#ifndef DEBUG
#define CheckView()
#define	CheckLineArray()
#endif
	

// ===========================  CDisplayML  =====================================================

#ifdef DEBUG
/*
 *	CDisplayML::Invariant
 *
 *	@mfunc	Make sure the display is in a valid state
 *
 *	@rdesc	TRUE if the tests succeeded, FALSE otherwise
 */
BOOL CDisplayML::Invariant(void) const
{
	CDisplay::Invariant();

	return TRUE;
}
#endif // DEBUG

/*
 *	CDisplayML::CalcScrollHeight()
 *
 *	@mfunc	
 *		Calculate the maximum Y scroll position.
 *
 *	@rdesc
 *		Maximum possible scrolling position
 *
 *	@devnote
 *		This routine exists because plain text controls do not have
 *		the auto-EOP and so the scroll height is different than
 *		the height of the control if the text ends in an EOP type
 *		character.
 */
LONG CDisplayML::CalcScrollHeight(LONG yHeight) const
{
	// The max scroll height for plain text controls is calculated
	// differently because they don't have an automatic EOP character.
	if(!_ped->IsRich() && Count())
	{
		// If last character is an EOP, bump scroll height
		CLine *lp = Elem(Count() - 1);	// Get last line in array
		if(lp->_cchEOP)
			yHeight += lp->GetHeight();
	}
	return yHeight;
}

/*
 *	CDisplayML::GetMaxYScroll()
 *
 *	@mfunc	
 *		Calculate the maximum Y scroll position.
 *
 *	@rdesc
 *		Maximum possible scrolling position
 *
 *	@devnote
 *		This routine exists because we may have to come back and modify this 
 *		calculation for 1.0 compatibility. If we do, this routine only needs
 *		to be changed in one place rather than the three at which it is used.
 *
 */
inline LONG CDisplayML::GetMaxYScroll() const
{
	// The following code is turn off because we don't want to support 
	// 1.0 mode unless someone complained about it.  
#if 0		
 	if (_ped->Get10Mode())
	{
		// Ensure last line is always visible
		// (use dy as temp to calculate max scroll)
		yScroll = Elem(max(0, Count() - 1))->_yHeight;

		if(yScroll > _yHeightView)
			yScroll = _yHeightView;

		yScroll = _yHeight - yScroll;
	}
#endif //0

	return CalcScrollHeight(_yHeight);
}

/*
 *	CDisplayML::ConvertScrollToYPos()
 *
 *	@mfunc	
 *		Calculate the real scroll position from the scroll position
 *
 *	@rdesc
 *		Y position from scroll
 *
 *	@devnote
 *		This routine exists because the thumb position messages
 *		are limited to 16-bits so we extrapolate when the Y position
 *		gets greater than that.
 */
LONG CDisplayML::ConvertScrollToYPos(
	LONG yPos)		//@parm Scroll position 
{
	// Get maximum scroll range
	LONG yRange = GetMaxYScroll();

	// Has maximum scroll range exceeded 16-bits?
	if(yRange >= _UI16_MAX)
	{
		// Yes - Extrapolate to "real" yPos		
		yPos = MulDiv(yPos, yRange, _UI16_MAX);
	}
	return yPos;
}

/*
 *	CDisplayML::ConvertYPosToScrollPos()
 *
 *	@mfunc	
 *		Calculate the scroll position from the Y position in the document.
 *
 *	@rdesc
 *		Scroll position from Y position
 *
 *	@devnote
 *		This routine exists because the thumb position messages
 *		are limited to 16-bits so we extrapolate when the Y position
 *		gets greater than that.
 *
 */
inline LONG CDisplayML::ConvertYPosToScrollPos(
	LONG yPos)		//@parm Y position in document
{
	// Get maximum scroll range
	LONG yRange = GetMaxYScroll();

	// Has maximum scroll range exceeded 16-bits?
	if(yRange >= _UI16_MAX)
	{
		// Yes - Extrapolate to "real" yPos		
		yPos = MulDiv(yPos, _UI16_MAX, yRange);
	}
	return yPos;
}

CDisplayML::CDisplayML (CTxtEdit* ped)
  : CDisplay (ped), _pddTarget(NULL)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CDisplayML");

	Assert(!_xWidthMax && !_yHeightMax);

	_fMultiLine = TRUE;
}

CDisplayML::~CDisplayML()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::~CDisplayML");

	delete _pddTarget;
}

/*
 *	CDisplayML::Init()
 *
 *	@mfunc	
 *		Init this display for the screen
 *
 *	@rdesc
 *		TRUE iff initialization succeeded
 */
BOOL CDisplayML::Init()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::Init");

	// Initialize our base class
	if(!CDisplay::Init())
		return FALSE;

	AssertSz(_ped, "CDisplayML::Init(): _ped not initialized in display");
	// Verify allocation zeroed memory out
	Assert(!_yCalcMax && !_xWidth && !_yHeight && !_cpMin);
	Assert(!_fBgndRecalc && !_fVScrollEnabled && !_fHScrollEnabled);

	// The printer view is not main, therefore we do this to make
	// sure scroll bars are not created for print views.
	DWORD dwScrollBars = _ped->TxGetScrollBars();

	if(IsMain() && (dwScrollBars & ES_DISABLENOSCROLL))
	{
		if(dwScrollBars & WS_VSCROLL)
		{
			// This causes wlm to assert on the mac. something about 
			// scrollbar being disabled
			_ped->TxSetScrollRange (SB_VERT, 0, 1, TRUE);
			_ped->TxEnableScrollBar(SB_VERT, ESB_DISABLE_BOTH);
		}

		// Set horizontal scroll range and pos
		// ??? - CF need fixing for windowless case
		if(dwScrollBars & WS_HSCROLL) 
		{
			_ped->TxSetScrollRange (SB_HORZ, 0, 1, TRUE);
			_ped->TxEnableScrollBar(SB_HORZ, ESB_DISABLE_BOTH);
		}
	}

	SetWordWrap(_ped->TxGetWordWrap());
	_cpFirstVisible = _cpMin;
	
	Assert(!_xScroll && !_yScroll && !_iliFirstVisible &&
		   !_cpFirstVisible && !_dyFirstVisible);

    _TEST_INVARIANT_

	return TRUE;
}


//================================  Device drivers  ===================================
/*
 *	CDisplayML::SetMainTargetDC(hdc, xWidthMax)
 *
 *	@mfunc
 *		Sets a target device for this display and updates view 
 *
 *	@devnote
 *		Target device can't be a metafile (can get char width out of a 
 *		metafile)
 *
 *	@rdesc
 *		TRUE if success
 */
BOOL CDisplayML::SetMainTargetDC (
	HDC hdc,			//@parm Target DC, NULL for same as rendering device
	LONG xWidthMax)		//@parm Max line width (not used for screen)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::SetMainTargetDC");

	if(SetTargetDC(hdc))
	{
		// This is here because this is what RE 1.0 did. 
		SetWordWrap(hdc || !xWidthMax);

		// If xWidthMax is greater than zero, then the caller is
		// trying to set the maximum width of the window (for measuring,
		// line breaking, etc.)  However,in order to make our measuring
		// algorithms more reasonable, we force the max size to be
		// *at least* as wide as the width of a character.
		// Note that xWidthMax = 0 means use the view rect width
		_xWidthMax = (xWidthMax <= 0) ? 0 : max(DXtoLX(GetXWidthSys()), 
												xWidthMax);
		// Need to do a full recalc. If it fails, it fails, the lines are
		// left in a reasonable state. No need to call WaitForRecalc()
		// because UpdateView() starts at position zero and we're always
		// calc'd up to there
		CDisplay::UpdateView();

		// Caret/selection has most likely moved
		CTxtSelection *psel = _ped->GetSelNC();
		if(psel) 
			psel->UpdateCaret(FALSE);
		return TRUE;
	}
	return FALSE;
}

// Useful for both main and printing devices. jonmat 6/08/1995
BOOL CDisplayML::SetTargetDC( HDC hdc, LONG dxpInch, LONG dypInch)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::SetTargetDC");

	CDevDesc *pddTarget = NULL;

	// Don't allow metafiles to be set as the target device
	if(hdc && GetDeviceCaps(hdc, TECHNOLOGY) == DT_METAFILE)
		return FALSE;

	if(hdc)
	{
		// Allocate device first to see if we can. We don't want to change
		// our state if this is going to fail.
		pddTarget = new CDevDesc(_ped);
		if(!pddTarget)
			return FALSE;				// We couldn't so we are done
	}

	// Remove any cached information for the old target device
	if(_pddTarget)
	{
		delete _pddTarget;
		_pddTarget = NULL;
	}
	if(hdc)
	{
		_pddTarget = pddTarget;			// Update device because we have one
		_pddTarget->SetDC(hdc, dxpInch, dypInch);
	}
	return TRUE;
}

//=================================  Line recalc  ==============================
/*
 *	CDisplayML::RecalcScrollBars()
 *
 *	@mfunc
 *		Recalculate the scroll bars if the view has changed.
 *
 *
 *	@devnote	There is a possibility of recursion here, so we
 *				need to protect ourselves.
 *
 *	To visualize this, consider two types of characters, 'a' characters 
 *	which are small in height and 'A' 's which are really tall, but the same 
 *	width as an 'a'. So if I have
 *
 *	a a A						<nl>
 *	A							<nl>
 *
 *	I'll get a calced size that's basically 2 * heightof(A).
 *	With a scrollbar, this could wordwrap to 
 *
 *	a a							<nl>
 *	A A							<nl>
 *
 *	which is of calced size heightof(A) + heightof(a); this is
 *	obviously less than the height in the first case.
 */
void CDisplayML::RecalcScrollBars()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcScrollBars");

	if(_fViewChanged)
	{
  		_fViewChanged = FALSE;
		UpdateScrollBar(SB_VERT, TRUE);
    	UpdateScrollBar(SB_HORZ, TRUE);
    }
}

/*
 *	CDisplayML::RecalcLines(fWait)
 *
 *	@mfunc
 *		Recalc all line breaks. 
 *		This method does a lazy calc after the last visible line
 *		except for a bottomless control
 *
 *	@rdesc
 *		TRUE if success
 */
BOOL CDisplayML::RecalcLines (
	BOOL fWait)		//@parm Recalc lines down to _cpWait/_yWait; then be lazy
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcLines");

	LONG		cliWait = cExtraBeforeLazy;			// Extra lines before being lazy
	BOOL		fDone = TRUE;
	BOOL		fFirstInPara = TRUE;
	CLine *		pliNew = NULL;
	LONG		xWidth;
	LONG		yHeight = 0;
    LONG        cchText = _ped->GetTextLength();
	BOOL		fWaitingForFirstVisible = TRUE;
	LONG		yHeightView = _yHeightView;
	LONG		yHeightScrollOld = GetMaxYScroll();
	LONG		yHeightScrollNew;

	Remove(0, -1);							// Remove all old lines from *this
	_yCalcMax = 0;							// Set both maxes to start of text
	_cpCalcMax = 0;

	// Don't stop at bottom of view if we're bottomless and active
	if(!_ped->TxGetAutoSize() && IsActive())
	{
		// Be lazy - don't bother going past visible portion
		_cpWait = -1;
		_yWait = -1;
		fWait = TRUE;
	}

	// Init measurer at cp = 0
	CMeasurer me(this);
	me.SetNumber(0);
 	
	// The following loop generates new lines
	while(me.GetCp() < cchText)
	{
		// Add one new line
		pliNew = Add(1, NULL);
		if (!pliNew)
		{
			_ped->GetCallMgr()->SetOutOfMemory();
			TRACEWARNSZ("Out of memory Recalc'ing lines");
			goto err;
		}

		// Stuff text into new line
		UINT uiFlags = MEASURE_BREAKATWORD | 
						(fFirstInPara ? MEASURE_FIRSTINPARA : 0);

    	Tracef(TRCSEVINFO, "Measuring new line from cp = %d", me.GetCp());

		if(!pliNew->Measure(me, -1, -1, uiFlags))
		{
			Assert(FALSE);
			goto err;
		}

		fFirstInPara = pliNew->_bFlags & fliHasEOP;
		yHeight += pliNew->GetHeight();
		_cpCalcMax = me.GetCp();

		if(fWait)
		{
			// Do we want to do a background recalc? - the answer is yes if
			// three things are true: (1) We have recalc'd beyond the old first
			// visible character, (2) We have recalc'd beyond the visible 
			// portion of the screen and (3) we have gone beyond the next
			// cExtraBeforeLazy lines to make page down go faster.

			if(fWaitingForFirstVisible)
			{
				if(me.GetCp() > _cpFirstVisible)
				{
					_yWait = yHeight + yHeightView;
					fWaitingForFirstVisible = FALSE;
				}
			}
			else if(yHeight > _yWait && cliWait-- <= 0)
			{
				fDone = FALSE;
				break;
			}
		}
	}

	_yCalcMax = yHeight;
	_fRecalcDone = fDone;
    _fNeedRecalc = FALSE;
	yHeightScrollNew = CalcScrollHeight(yHeight);

	if(fDone && (yHeight != _yHeight || yHeightScrollNew != yHeightScrollOld)
		|| yHeightScrollNew > yHeightScrollOld)
	{
		_fViewChanged = TRUE;
	}

	_yHeight = yHeight;
	xWidth = CalcDisplayWidth();
    if(fDone && xWidth != _xWidth || xWidth > _xWidth)
    {
        _xWidth = xWidth;
		_fViewChanged = TRUE;
    }    

	Tracef(TRCSEVINFO, "CDisplayML::RecalcLine() - Done. Recalced down to line #%d", Count());

	if(!fDone)						// if not done, do rest in background
		fDone = StartBackgroundRecalc();

	if(fDone)
	{
		_yWait = -1;
		_cpWait = -1;
		CheckLineArray();
		_fLineRecalcErr = FALSE;
	}

#ifdef DEBUG
	if( 1 )
    {
		_TEST_INVARIANT_
	}
	//Array memory allocation tracking
	{
	void **pv = (void**)((char*)this + sizeof(CDisplay));
	PvSet(*pv);
	}
#endif

	return TRUE;

err:
	TRACEERRORSZ("CDisplayML::RecalcLines() failed");

	if(!_fLineRecalcErr)
	{
		_cpCalcMax = me.GetCp();
		_yCalcMax = yHeight;
		_fLineRecalcErr = TRUE;
		_ped->GetCallMgr()->SetOutOfMemory();
		_fLineRecalcErr = FALSE;			//  fix up CArray & bail
	}
	return FALSE;
}

/*
 *	CDisplayML::RecalcLines(rtp, cchOld, cchNew, fBackground, fWait, pled)
 *
 *	@mfunc
 *		Recompute line breaks after text modification
 *
 *	@rdesc
 *		TRUE if success
 *
 *	@devnote
 *		Most people call this the trickiest piece of code in RichEdit...
 */						     
BOOL CDisplayML::RecalcLines (
	const CRchTxtPtr &rtp,	//@parm Where change happened
	LONG cchOld,			//@parm Count of chars deleted
	LONG cchNew,			//@parm Count of chars added
	BOOL fBackground,		//@parm This method called as background process
	BOOL fWait,				//@parm Recalc lines down to _cpWait/_yWait; then be lazy
	CLed *pled)				//@parm Returns edit impact on lines (can be NULL)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcLines");

	LONG		cchEdit;
	LONG		cchSkip;
	LONG		cliBackedUp = 0;
	LONG		cliWait = cExtraBeforeLazy;	
	BOOL		fDone = TRUE;
	BOOL		fFirstInPara = TRUE;
	LONG		ili;
	CLed		led;
	LONG		lT;							// long Temporary
	CLine *		pliNew;
	CLinePtr	rpOld(this);
	LONG		xWidth;
	LONG		yHeight;
	LONG		yHeightPrev = 0;
    LONG        cchText = _ped->GetTextLength();
    UINT        uiFlags;
	BOOL 		fReplaceResult;
	BOOL		fTryForMatch = TRUE;
	LONG		yHeightScrollOld = GetMaxYScroll();
	LONG		yHeightScrollNew;
	WORD		wNumber = 0;
	CLineArray	rgliNew;
	DWORD		dwBgndTickMax = fBackground ? GetTickCount() + cmsecBgndBusy : 0;

	if(!pled)
		pled = &led;

#if defined(DEBUG) || defined(_RELEASE_ASSERTS_)
	LONG cp = rtp.GetCp();

	if(cp > _cpCalcMax)
		Tracef(TRCSEVERR, "rtp %ld, _cpCalcMax %ld", cp, _cpCalcMax);

	AssertSz(cp <= _cpCalcMax, 
		"CDisplayML::RecalcLines Caller didn't setup RecalcLines()");

	AssertSz(!(fWait && fBackground),
		"CDisplayML::RecalcLines wait and background both true");

	AssertSz(!(fWait && (-1 == _cpWait) && (-1 == _yWait)),
		"CDisplayML::RecalcLines background recalc parms invalid");
#endif

	// We will not use background recalc if this is already a background recalc,
	// or if the control is not active or if this is an auto sized control.
	if(!IsActive() || _ped->TxGetAutoSize())
		fWait = FALSE;

	// Init line pointer on old CLineArray and backup to start of line
	rpOld.RpSetCp(rtp.GetCp(), FALSE);
	cchSkip = rpOld.RpGetIch();
	rpOld.RpAdvanceCp(-cchSkip);			// Point rp at 1st char in line

	ili = rpOld;							// Save line # at change for
	if(ili && (IsInOutlineView() ||			//  numbering. Back up if not
		rtp.GetPF()->IsListNumbered()))		//  first number in list or if
	{										//  in OutlineView (Outline
		ili--;								//  symbol may change)
	}

	// Back up at least one line in case we can now fit more on it
	// If on a line border, e.g., just inserted an EOP, backup 2; else 1
	lT = !cchSkip + 1;

	while(lT-- > 0 && rpOld > 0 && (!rpOld[-1]._cchEOP || ili < rpOld))
	{
		cliBackedUp++;
		rpOld--;
		cchSkip += rpOld->_cch;
	}

	// Init measurer at rtp
	CMeasurer me(this, rtp);

	me.Advance(-cchSkip);					// Point at start of text to measure
	cchEdit = cchNew + cchSkip;				// Number of chars affected by edit
	me.SetNumber(rpOld.GetNumber());		// Initialize list number
	
	// Determine whether we're on first line of paragraph
	if(rpOld > 0)
	{
		fFirstInPara = rpOld[-1]._bFlags & fliHasEOP;
	}

	yHeight = YposFromLine(rpOld);

	// Update first-affected and pre-edit-match lines in pled
	pled->_iliFirst = rpOld;
	pled->_cpFirst	= pled->_cpMatchOld	= me.GetCp();
	pled->_yFirst	= pled->_yMatchOld	= yHeight;
	AssertSz(pled->_yFirst >= 0, "CDisplayML::RecalcLines _yFirst < 0");
	
	Tracef(TRCSEVINFO, "Start recalcing from line #%d, cp=%d", pled->_iliFirst, pled->_cpFirst);

	// In case of error, set both maxes to where we are now
	_yCalcMax = yHeight;
	_cpCalcMax = me.GetCp();

	// If we are past the requested area to recalc and background recalc is
	// allowed, then just go directly to background recalc. If there is no
	// height, we just go a head and calculate some lines anyway. This
	// prevents any weird background recalcs from occuring when it is
	// unnecessary to go into background recalc.
	if(fWait && _yWait > 0 && yHeight > _yWait && me.GetCp() > _cpWait)
	{
		_yHeight = yHeight;
		rpOld.Remove(-1);	// Remove all old lines from here on
		StartBackgroundRecalc();		// Start up the background recalc		
		pled->SetMax(this);
		return TRUE;
	}

    pliNew = NULL;

	// The following loop generates new lines for each line we backed
	// up over and for lines directly affected by edit
	while(cchEdit > 0)
	{
		pliNew = rgliNew.Add(1, NULL);		// Add one new line
		if (!pliNew)
		{
			TRACEWARNSZ("CDisplayML::RecalcLines unable to alloc additional CLine in CLineArray");
			goto errspace;
		}

		// Can't reuse old results if we've got a target device
		// For SPEED: it'd be nice to cache a few values when we do have a
		// target device - a good caching heuristic could halve the measuring
		const LONG cchNonWhite = rpOld.IsValid()
							   ? rpOld->_cch - rpOld->_cchWhite
							   : 0;
		uiFlags = MEASURE_BREAKATWORD | (fFirstInPara ? MEASURE_FIRSTINPARA : 0);

		if (cchSkip > 0 && cchSkip >= cchNonWhite && !IsInOutlineView() && 
			!_ped->fUseLineServices() && (!_pddTarget || !_pddTarget->IsValid()))
		{
			me.NewLine(*rpOld);				// Don't remeasure anything we
			me.Advance(cchNonWhite);		//  already have valid info on
			me._li._cch = cchNonWhite;
			me._li._xWidth = rpOld->_xWidth;

			// Clear out any of the old flags _except_ for tabs and OLE or
			// OffScreen. Note that this algorithm is somewhat bogus; there 
			// is no guarantee that the line still matches the flag state.  
			// However,those flags are simply 'hints'--i.e. the line _may_ 
			// be in that state.  Keeping those flags set will result
			// in a minor slowdown for rendering the line.
			me._li._bFlags &= (fliHasTabs | fliHasOle | fliUseOffScreenDC | fliHasSpecialChars);

			if(rpOld->_bFlags & fliOffScreenOnce)
				me._li._bFlags &= ~fliUseOffScreenDC;
			me._li._cchEOP = 0;
			uiFlags |= MEASURE_DONTINIT;	// CLine part of me already init'd
		}

		// Stuff text into new line
    	Tracef(TRCSEVINFO, "Measuring new line from cp = %d", me.GetCp());

		if(!pliNew->Measure(me, -1, -1, uiFlags))
		{
			Assert(FALSE);
			goto err;
		}

		if(!pliNew->_cch)
		{
			TRACEWARNSZ(
           "CDisplayML::RecalcLines measure returned a zero length line");
			goto errspace;
		}

		fFirstInPara = pliNew->_bFlags & fliHasEOP;
		yHeightPrev	 = yHeight;
		yHeight		+= pliNew->GetHeight();
		cchEdit		-= pliNew->_cch;
		AssertSz(cchEdit + me.GetCp() <= cchText,
			"CDisplayML::RecalcLines: want to measure beyond EOD");

		// Calculate on what line the edit started. We do this because
		// we want to render the first edited line off screen so if
		// the line is being edited via the keyboard we don't clip
		// any characters.
		if(cchSkip > 0)
		{
			// Check whether we backed up and the line we are examining
			// changed at all. Even if it didn't change in outline view
			// have to redraw in case outline symbol changes
			if (cliBackedUp && cchSkip >= pliNew->_cch && 
				pliNew->IsEqual(*rpOld) && !IsInOutlineView())
			{
				// Perfect match, this line was not the first edited.
               	Tracef(TRCSEVINFO, "New line matched old line #%d", (LONG)rpOld);

				cchSkip -= rpOld->_cch;

				// Update first affected line and match in pled
				pled->_iliFirst++;
				pled->_cpFirst	  += rpOld->_cch;
				pled->_cpMatchOld += rpOld->_cch;
				pled->_yFirst	  += rpOld->GetHeight();
				AssertSz(pled->_yFirst >= 0, "CDisplayML::RecalcLines _yFirst < 0");
				pled->_yMatchOld  += rpOld->GetHeight();
				cliBackedUp--;
			
				rgliNew.Clear(AF_KEEPMEM);		// Discard new line
				if(!(rpOld++))					// Next line
					cchSkip = 0;
			}
			else								// No match in the line, so 
				cchSkip = 0;					//  this line is the first to
		}										//  be edited

		if(fBackground && GetTickCount() >= dwBgndTickMax)
		{
			fDone = FALSE;						// took too long, stop for now
			goto no_match;
		}

		if (fWait && yHeight > _yWait && me.GetCp() > _cpWait &&
			cliWait-- <= 0)
		{
			// Not really done, just past region we're waiting for
			// so let background recalc take it from here
			fDone = FALSE;
			goto no_match;
		}
	}											// while(cchEdit > 0) { }

   	Tracef(TRCSEVINFO, "Done recalcing edited text. Created %d new lines", rgliNew.Count());

	// Edit lines have been exhausted.  Continue breaking lines,
	// but try to match new & old breaks

	wNumber = me._wNumber;
	while(me.GetCp() < cchText)
	{
		// Assume there are no matches to try for
		BOOL frpOldValid = FALSE;

		// If we run out of runs, then no match is possible. Therefore, 
		// we only try for a match as long as we have runs.
		if(fTryForMatch)
		{
			// We are trying for a match so assume that there
			// is a match after all
			frpOldValid = TRUE;

			// Look for match in old line break CArray
			lT = me.GetCp() - cchNew + cchOld;
			while (rpOld.IsValid() && pled->_cpMatchOld < lT)
			{
				pled->_yMatchOld  += rpOld->GetHeight();
				pled->_cpMatchOld += rpOld->_cch;

				if(!rpOld.NextRun())
				{
					// No more line array entries so we can give up on
					// trying to match for good.
					fTryForMatch = FALSE;
					frpOldValid = FALSE;
					break;
				}
			} 
		}

		// If perfect match, stop.
		if (frpOldValid && rpOld.IsValid() && pled->_cpMatchOld == lT && 
			rpOld->_cch && me._wNumber == rpOld->_bNumber)
		{
           	Tracef(TRCSEVINFO, "Found match with old line #%d", rpOld.GetLineIndex());

			// Update fliFirstInPara flag in 1st old line that matches.  Note
			// that if the new array doesn't have any lines, we have to look
			// into the line array preceding the current change.
			rpOld->_bFlags |= fliFirstInPara;
			if(rgliNew.Count() > 0) 
			{
				if(!(rgliNew.Elem(rgliNew.Count() - 1)->_bFlags & fliHasEOP))
					rpOld->_bFlags &= ~fliFirstInPara;
			}
			else if( rpOld >= pled->_iliFirst && pled->_iliFirst )
			{
				if(!(rpOld[pled->_iliFirst - rpOld -1]._bFlags & fliHasEOP))
					rpOld->_bFlags &= ~fliFirstInPara;
			}

			pled->_iliMatchOld = rpOld;

			// Replace old lines by new ones
			lT = rpOld - pled->_iliFirst;
			rpOld = pled->_iliFirst;
			if(!rpOld.Replace (lT, &rgliNew))
			{
				TRACEWARNSZ("CDisplayML::RecalcLines unable to alloc additional CLines in rpOld");
				goto errspace;
			}
			frpOldValid = rpOld.ChgRun(rgliNew.Count());
			rgliNew.Clear(AF_KEEPMEM);	 		// Clear aux array

			// Remember information about match after editing
			Assert((cp = rpOld.CalculateCp()) == me.GetCp());
			pled->_yMatchNew	= yHeight;
			pled->_yMatchNewTop = yHeightPrev;
			pled->_iliMatchNew	= rpOld;
			pled->_cpMatchNew	= me.GetCp();

			// Compute height and cp after all matches
			_cpCalcMax = me.GetCp();

			if(frpOldValid && rpOld.IsValid())
			{
				do
				{
					yHeight	   += rpOld->GetHeight();
					_cpCalcMax += rpOld->_cch;
				}
				while( rpOld.NextRun() );
			}

			// Make sure _cpCalcMax is sane after the above update
			AssertSz(_cpCalcMax <= cchText, 
				"CDisplayML::RecalcLines match extends beyond EOF");

			// We stop calculating here.Note that if _cpCalcMax < size 
			// of text, this means a background recalc is in progress.
			// We will let that background recalc get the arrays
			// fully in sync.  

			AssertSz(_cpCalcMax == cchText || _fBgndRecalc,
					"CDisplayML::Match less but no background recalc");

			if(_cpCalcMax != cchText)
			{
				// This is going to be finished by the background recalc
				// so set the done flag appropriately.
				fDone = FALSE;
			}
			goto match;
		}

		// Add a new line
		pliNew = rgliNew.Add(1, NULL);
		if(!pliNew)
		{
			TRACEWARNSZ("CDisplayML::RecalcLines unable to alloc additional CLine in CLineArray");
			goto errspace;
		}

    	Tracef(TRCSEVINFO, "Measuring new line from cp = %d", me.GetCp());

		// Stuff some text into new line
		wNumber = me._wNumber;
		if(!pliNew->Measure(me, -1, -1, MEASURE_BREAKATWORD | 
							(fFirstInPara ? MEASURE_FIRSTINPARA : 0)))
		{
			Assert(FALSE);
			goto err;
		}
		
		fFirstInPara = pliNew->_bFlags & fliHasEOP;
		yHeight += pliNew->GetHeight();

		if(fBackground && GetTickCount() >= (DWORD)dwBgndTickMax)
		{
			fDone = FALSE;			// Took too long, stop for now
			break;
		}

		if(fWait && yHeight > _yWait && me.GetCp() > _cpWait
			&& cliWait-- <= 0)
		{							// Not really done, just past region we're
			fDone = FALSE;			//  waiting for so let background recalc
			break;					//  take it from here
		}
	}								// while(me < cchText) ...

no_match:
	// Didn't find match: whole line array from _iliFirst needs to be changed
	pled->_iliMatchOld	= Count(); 
	pled->_cpMatchOld	= cchText;
	pled->_yMatchNew	= yHeight;
	pled->_yMatchNewTop = yHeightPrev;
	pled->_yMatchOld	= _yHeight;
	_cpCalcMax			= me.GetCp();

	// Replace old lines by new ones
	rpOld = pled->_iliFirst;

	// We store the result from the replace because although it can fail the 
	// fields used for first visible must be set to something sensible whether 
	// the replace fails or not. Further, the setting up of the first visible 
	// fields must happen after the Replace because the lines could have 
	// changed in length which in turns means that the first visible position
	// has failed.

	fReplaceResult = rpOld.Replace(-1, &rgliNew);

	// _iliMatchNew & _cpMatchNew are used for first visible constants so we
	// need to set them to something reasonable. In particular the rendering
	// logic expects _cpMatchNew to be set to the first character of the first
	// visible line. rpOld is used because it is convenient.

	// Note we can't use RpBindToCp at this point because the first visible
	// information is messed up because we may have changed the line that
	// the first visible cp is on. 
	rpOld.BindToCp(me.GetCp());
	pled->_iliMatchNew = rpOld.GetLineIndex();
	pled->_cpMatchNew = me.GetCp() - rpOld.RpGetIch();

	if (!fReplaceResult)
	{
		TRACEERRORSZ("CDisplayML::RecalcLines rpOld.Replace() failed");
		goto errspace;
	}

    // Adjust first affected line if this line is gone
    // after replacing by new lines
    if(pled->_iliFirst >= Count() && Count() > 0)
    {
        Assert(pled->_iliFirst == Count());
        pled->_iliFirst = Count() - 1;
		pliNew = Elem(pled->_iliFirst);
        pled->_yFirst -= pliNew->GetHeight();
		AssertSz(pled->_yFirst >= 0, "CDisplayML::RecalcLines _yFirst < 0");
        pled->_cpFirst -= pliNew->_cch;
    }
    
match:
	_fRecalcDone = fDone;
    _fNeedRecalc = FALSE;
	_yCalcMax = yHeight;

	Tracef(TRCSEVINFO, "CDisplayML::RecalcLine(rtp, ...) - Done. Recalced down to line #%d", Count() - 1);

	// Clear wait fields since we want caller's to set them up.
	_yWait = -1;
	_cpWait = -1;

	if(fDone && fBackground)
	{
		TRACEINFOSZ("Background line recalc done");
		_ped->TxKillTimer(RETID_BGND_RECALC);
		_fBgndRecalc = FALSE;
		_fRecalcDone = TRUE;
	}

	// Determine display height and update scrollbar
	yHeightScrollNew = CalcScrollHeight(yHeight);

	if (_fViewChanged ||
		fDone && (yHeight != _yHeight || yHeightScrollNew != yHeightScrollOld)
		|| yHeightScrollNew > yHeightScrollOld) 
	{
	    //!NOTE:
	    // UpdateScrollBar can cause a resize of the window by hiding or showing
	    // scrollbars.  As a consequence of resizing the lines may get recalculated
	    // therefore updating _yHeight to a new value, something != to yHeight.
		_yHeight = yHeight;
   		UpdateScrollBar(SB_VERT, TRUE);
	}
	else
	    _yHeight = yHeight;     // Guarantee heights agree

	// Determine display width and update scrollbar
	xWidth = CalcDisplayWidth();
    if(_fViewChanged || (fDone && xWidth != _xWidth) || xWidth > _xWidth)
    {
        _xWidth = xWidth;
   		UpdateScrollBar(SB_HORZ, TRUE);
    }    

    _fViewChanged = FALSE;

	// If not done, do the rest in background
	if(!fDone && !fBackground)
		fDone = StartBackgroundRecalc();

	if(fDone)
	{
		CheckLineArray();
		_fLineRecalcErr = FALSE;
	}

#ifdef DEBUG
	if( 1 )
    {
		_TEST_INVARIANT_
	}
#endif // DEBUG

	return TRUE;

errspace:
	_ped->GetCallMgr()->SetOutOfMemory();
	_fNeedRecalc = TRUE;
	_cpCalcMax = _yCalcMax = 0;
	_fLineRecalcErr = TRUE;

err:
	if(!_fLineRecalcErr)
	{
		_cpCalcMax = me.GetCp();
		_yCalcMax = yHeight;
	}

	TRACEERRORSZ("CDisplayML::RecalcLines() failed");

	if(!_fLineRecalcErr)
	{
		_fLineRecalcErr = TRUE;
		_ped->GetCallMgr()->SetOutOfMemory();
		_fLineRecalcErr = FALSE;			//  fix up CArray & bail
	}
	pled->SetMax(this);

	return FALSE;
}

/*
 *	CDisplayML::CalcDisplayWidth()
 *
 *	@mfunc
 *		Calculates width of this display by walking line CArray and
 *		returning widest line.  Used for horizontal scrollbar routines.
 *
 *	@rdesc
 *		Widest line width in display
 */
LONG CDisplayML::CalcDisplayWidth ()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CalcDisplayWidth");

	LONG	ili = Count();
	CLine *	pli;
	LONG	xWidth = 0, lineWidth;

	if(ili)
	{
		// Note: pli++ breaks array encapsulation (pli = Elem(ili) doesn't,
		// but is a bit slower)
		pli = Elem(0);
		for(xWidth = 0; ili--; pli++)
		{
			lineWidth = pli->_xLeft + pli->_xWidth + pli->_xLineOverhang;
			xWidth = max(xWidth, lineWidth);
		}
	}
    return xWidth;
}

/*
 *	CDisplayML::StartBackgroundRecalc()
 *
 *	@mfunc
 *		Starts background line recalc (at _cpCalcMax position)
 *
 *	@rdesc
 *		TRUE if done with background recalc
 */
BOOL CDisplayML::StartBackgroundRecalc()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::StartBackgroundRecalc");

	if(_fBgndRecalc)
		return FALSE;					// Already in background recalc

	AssertSz(_cpCalcMax <= _ped->GetTextLength(),
		"CDisplayML::StartBackgroundRecalc _cpCalcMax > Text Length");

	if(_cpCalcMax == _ped->GetTextLength())
		return TRUE;					// Enough chars are recalc'd

	if(!_ped->TxSetTimer(RETID_BGND_RECALC, cmsecBgndInterval))
	{
		// Could not instantiate a timer so wait for recalculation
		WaitForRecalc(_ped->GetTextLength(), -1);
		return TRUE;
	}

	_fRecalcDone = FALSE;
	_fBgndRecalc = TRUE;
	return FALSE;
}

/*
 *	CDisplayML::StepBackgroundRecalc()
 *
 *	@mfunc
 *		Steps background line recalc (at _cpCalcMax position)
 *		Called by timer proc and also when going inactive.
 */
void CDisplayML::StepBackgroundRecalc()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::StepBackgroundRecalc");

    _TEST_INVARIANT_
	
	if(!_fBgndRecalc)					// Not in background recalc,
		return;							//  so don't do anything

	LONG cch = _ped->GetTextLength() - _cpCalcMax;

	// Don't try recalc when processing OOM or had an error doing recalc or
	// if we are asserting.
#ifdef DEBUG
	if(_fInBkgndRecalc || _fLineRecalcErr)
	{
		if(_fInBkgndRecalc)
			TRACEINFOSZ("avoiding reentrant background recalc");
		else
			TRACEINFOSZ("OOM: not stepping recalc");
		return;
	}
#else
	if(_fInBkgndRecalc || _fLineRecalcErr)
		return;
#endif

	_fInBkgndRecalc = TRUE;
	if(!IsActive())
	{
		// Background recalc is over if we are no longer active	because
		// we can no longer get the information we need for recalculating.
		// But, if we are half recalc'd we need to set ourselves up to 
		// recalc again when we go active.
		InvalidateRecalc();
		cch = 0;
	}

	// Background recalc is over if no more chars or no longer active
	if(cch <= 0)
	{
		TRACEINFOSZ("Background line recalc done");
		_ped->TxKillTimer(RETID_BGND_RECALC);
		_fBgndRecalc = FALSE;
		_fRecalcDone = TRUE;
		_fInBkgndRecalc = FALSE;
		CheckLineArray();
		return;
	}

	CRchTxtPtr tp(_ped, _cpCalcMax);
	RecalcLines(tp, cch, cch, TRUE, FALSE, NULL);

	_fInBkgndRecalc = FALSE;
}

/*
 *	CDisplayML::WaitForRecalc(cpMax, yMax)
 *
 *	@mfunc
 *		Ensures that lines are recalced until a specific character
 *		position or ypos.
 *
 *	@rdesc
 *		success
 */
BOOL CDisplayML::WaitForRecalc(
	LONG cpMax,		//@parm Position recalc up to (-1 to ignore)
	LONG yMax)		//@parm ypos to recalc up to (-1 to ignore)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::WaitForRecalc");

    _TEST_INVARIANT_

	if(IsFrozen() || !_ped->fInplaceActive())
		return TRUE;

	BOOL fReturn = TRUE;
	LONG cch;

	if((yMax  < 0 || yMax  >= _yCalcMax) &&
	   (cpMax < 0 || cpMax >= _cpCalcMax))
    {
    	cch = _ped->GetTextLength() - _cpCalcMax;
    	if(cch > 0 || Count() == 0)
    	{
    		HCURSOR hcur = NULL;
			BOOL fSetCursor = (cch > NUMCHARFORWAITCURSOR);

    		_cpWait = cpMax;
    		_yWait = yMax;
		
			if(fSetCursor)
    			hcur = SetCursor(LoadCursor(0, IDC_WAIT));
    		TRACEINFOSZ("Lazy recalc");
		
    		if(!_cpCalcMax || _fNeedRecalc)
			{
    			fReturn = RecalcLines(TRUE);
				RebindFirstVisible();
				if(!fReturn)
					InitVars();
			}
    		else			
    		{
    			CRchTxtPtr rtp(_ped, _cpCalcMax);
    			fReturn = RecalcLines(rtp, cch, cch, FALSE, TRUE, NULL);
    		}

			if(fSetCursor)
    			SetCursor(hcur);
    	}
		else if(!cch)
		{
			// If there was nothing else to calc, make sure that we think
			// recalc is done.
#ifdef DEBUG
			if( !_fRecalcDone )
			{
				TRACEWARNSZ("For some reason we didn't think background "
					"recalc was done, but it was!!");
			}
#endif // DEBUG
			_fRecalcDone = TRUE;
		}
    }

	// If view rect changed, make sure to update scrollbars
	RecalcScrollBars();

	return fReturn;
}

/*
 *	CDisplayML::WaitForRecalcIli(ili)
 *
 *	@mfunc
 *		Wait until line array is recalculated up to line <p ili>
 *
 *	@rdesc
 *		Returns TRUE if lines were recalc'd up to ili
 */
BOOL CDisplayML::WaitForRecalcIli (
	LONG ili)		//@parm Line index to recalculate line array up to
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::WaitForRecalcIli");

	LONG cchGuess;

	while(!_fRecalcDone && ili >= Count())
	{
		// just go ahead and recalc everything.
		cchGuess = _ped->GetTextLength();
		if(IsFrozen() || !WaitForRecalc(cchGuess, -1))
			return FALSE;
	}
	return ili < Count();
}

/*
 *	CDisplayML::WaitForRecalcView()
 *
 *	@mfunc
 *		Ensure visible lines are completly recalced
 *
 *	@rdesc TRUE iff successful
 */
BOOL CDisplayML::WaitForRecalcView()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::WaitForRecalcView");

	return WaitForRecalc(-1, _yScroll + _yHeightView);
}

/*
 *	CDisplayML::InitLinePtr ( CLinePtr & plp )
 *
 *	@mfunc
 *		Initialize a CLinePtr properly
 */
void CDisplayML::InitLinePtr (
	CLinePtr & plp )		//@parm Ptr to line to initialize
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::InitLinePtr");

    plp.Init( *this );
}

/*
 *	CDisplayML::GetLineText(ili, pchBuff, cchMost)
 *
 *	@mfunc
 *		Copy given line of this display into a character buffer
 *
 *	@rdesc
 *		number of character copied
 */
LONG CDisplayML::GetLineText(
	LONG ili,			//@parm Line to get text of
	TCHAR *pchBuff,		//@parm Buffer to stuff text into
	LONG cchMost)		//@parm Length of buffer
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetLineText");
    
	_TEST_INVARIANT_

	CTxtPtr tp (_ped, 0);

	// FUTURE (alexgo, ricksa): This is called from EM_GETLINE whose parameter
	// is a WPARAM which is unsigned we need to fix the type of ili.

	if(ili >= 0 && (ili < Count() || WaitForRecalcIli(ili)))
	{
		cchMost = min(cchMost, Elem(ili)->_cch);
		if(cchMost > 0)
		{
			tp.SetCp(CpFromLine(ili, NULL));
			return tp.GetText(cchMost, pchBuff);
		}
	}
	*pchBuff = TEXT('\0');
	return 0;
}

/*
 *	CDisplayML::LineCount
 *
 *	@mfunc	returns the number of lines in this control.  Note that for plain
 *			text mode, we will add on an extra line of the last character is
 *			a CR.  This is for compatibility with MLE
 *
 *	@rdesc	LONG
 */
LONG CDisplayML::LineCount() const
{
	LONG cLine = Count();

	if (!_ped->IsRich() && (!cLine || 	   // If plain text with no lines
		 Elem(cLine - 1)->_cchEOP))		   //  or last line ending with a CR,
	{									   //  then inc line count
		cLine++;
	}
	return cLine;
}

// ================================  Line info retrieval  ====================================

/*
 *	CDisplayML::YposFromLine(ili)
 *
 *	@mfunc
 *		Computes top of line position
 *
 *	@rdesc
 *		top position of given line (relative to the first line)
 */
LONG CDisplayML::YposFromLine(
	LONG ili) 		//@parm Line we're interested in
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::YposFromLine");
    _TEST_INVARIANT_

	if(!WaitForRecalcIli(ili))			// out of range, use last valid line
	{
		ili = Count() - 1;
		ili = (ili > 0) ? ili : 0;
	}

	LONG	cli	= ili - _iliFirstVisible;
	CLine *	pli = Elem(_iliFirstVisible);
	LONG	yPos = _yScroll + _dyFirstVisible;

	while(cli > 0)
	{
		yPos += pli->GetHeight();
		cli--;
		pli++;
	}
	while(cli < 0)
	{	
		pli--;
		yPos -= pli->GetHeight();
		cli++;
	}

	AssertSz(yPos >= 0, "CDisplayML::YposFromLine height less than 0");

	return yPos;
}

/*
 *	CDisplayML::CpFromLine(ili, pyHeight)
 *
 *	@mfunc
 *		Computes cp at start of given line 
 *		(and top of line position relative to this display)
 *
 *	@rdesc
 *		cp of given line
 */
LONG CDisplayML::CpFromLine (
	LONG ili,		//@parm Line we're interested in (if <lt> 0 means caret line)
	LONG *pyHeight)	//@parm Returns top of line relative to display 
					//  	(NULL if don't want that info)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CpFromLine");

    _TEST_INVARIANT_
						
	LONG cli;
	LONG y = _yScroll + _dyFirstVisible;
	LONG cp = _cpFirstVisible;
	CLine *pli;
	LONG iStart = _iliFirstVisible;

	cli = ili - _iliFirstVisible;
	if(cli < 0 && -cli >= ili)
	{
		// Closer to first line than to first visible line,
		// so start at the first line
		cli = ili;
		y = 0;
		cp = 0;
		iStart = 0;
	}
	else if( cli <= 0 )
	{
		CheckView();
		for(ili = _iliFirstVisible-1; cli < 0; cli++, ili--)
		{
			pli = Elem(ili);
			y -= pli->GetHeight();
			cp -= pli->_cch;
		}
		goto end;
	}

	for(ili = iStart; cli > 0; cli--, ili++)
	{
		pli = Elem(ili);
		if(!IsMain() || !WaitForRecalcIli(ili))
			break;
		y += pli->GetHeight();
		cp += pli->_cch;
	}

end:
	if(pyHeight)
		*pyHeight = y;

	return cp;
}

/*
 *	CDisplayML::LineFromYPos(yPos, pyLine, pcpFirst)
 *
 *	@mfunc
 *		Computes line at given y position. Returns top of line ypos
 *		cp at start of line cp, and line index.
 *
 *	@rdesc
 *		index of line found
 */
LONG CDisplayML::LineFromYpos (
	LONG yPos,			//@parm Ypos to look for (relative to first line)
	LONG *pyLine,		//@parm Returns ypos at top of line /r first line (can be NULL)
	LONG *pcpFirst)		//@parm Returns cp at start of line (can be NULL)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::LineFromYpos");

    _TEST_INVARIANT_

	LONG cpLi;
	LONG dy;
	LONG ili = 0;
	LONG yLi;
	CLine *pli;

	if(!WaitForRecalc(-1, _yScroll))
	{
		yLi = 0;
		cpLi = 0;
		goto done;
	}

	cpLi = _cpFirstVisible;
	ili = _iliFirstVisible;
	yLi = _yScroll + _dyFirstVisible;
	dy = yPos - yLi;
	
	if(dy < 0 && -dy <= _yScroll)
	{
		// Closer to first visible line than to first line:
		// go backwards from first visible line.
		while(yPos < yLi && ili > 0)
		{
			pli = Elem(--ili);
			yLi -= pli->GetHeight();
			cpLi -= pli->_cch;
		}
	}
	else
	{
		if(dy < 0)
		{
			// Closer to first line than to first visible line:
			// so start at first line.
			cpLi = _cpMin;
			yLi = 0;
			ili = 0;
		}
		pli = Elem(ili);
		while(yPos > yLi && ili < Count()-1)
		{
			yLi += pli->GetHeight();
			cpLi += pli->_cch;
			ili++;
			pli++;
		}
		if(yPos < yLi && ili > 0)
		{
			ili--;
			pli--;
			yLi -= pli->GetHeight();
			cpLi -= pli->_cch;
		}
	}

done:
	if(pyLine)
		*pyLine = yLi;

	if(pcpFirst)
		*pcpFirst = cpLi;

	return ili;
}

/*
 *	CDisplayML::LineFromCp(cp, fAtEnd)
 *
 *	@mfunc
 *		Computes line containing given cp.
 *
 *	@rdesc
 *		index of line found, -1 if no line at that cp.
 */
LONG CDisplayML::LineFromCp(
	LONG cp,		//@parm cp to look for
	BOOL fAtEnd)	//@parm If true, return previous line for ambiguous cp
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::LineFromCp");
    
	_TEST_INVARIANT_

	CLinePtr rp(this);
	
	if(!WaitForRecalc(cp, -1) || !rp.RpSetCp(cp, fAtEnd))
		return -1;

	return (LONG)rp;
}


//==============================  Point <-> cp conversion  ==============================
/*
 *	CDisplayML::CpFromPoint(pt, prcClient, ptp, prp, fAllowEOL, pHit, pdx)
 *
 *	@mfunc
 *		Determine cp at given point
 *
 *	@devnote
 *      --- Use when in-place active only ---
 *
 *	@rdesc
 *		Computed cp, -1 if failed
 */
LONG CDisplayML::CpFromPoint(
	POINT		pt,			//@parm Point to compute cp at (client coords)
	const RECT *prcClient,	//@parm Client rectangle (can be NULL if active).
	CRchTxtPtr * const ptp,	//@parm Returns text pointer at cp (may be NULL)
	CLinePtr * const prp,	//@parm Returns line pointer at cp (may be NULL)
	BOOL		fAllowEOL,	//@parm Click at EOL returns cp after CRLF
	HITTEST *	phit,		//@parm Out parm for hit-test value
	CDispDim *	pdispdim,	//@parm Out parm for display dimensions
	LONG	   *pcpActual)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CpFromPoint");

    _TEST_INVARIANT_

	LONG		cp;
	LONG		cch = 0;
    RECT        rcView;
	HITTEST		hit = HT_Nothing;

	GetViewRect(rcView, prcClient);

	// Get line under hit
	int y = pt.y;
	pt.y += _yScroll - rcView.top;

	LONG yLine;
	LONG ili = LineFromYpos(pt.y, &yLine, &cp);
    if(ili < 0)
        return -1;

	if(y >= rcView.top && y <= rcView.bottom)
	{
	    pt.y -= yLine;

		CLine *pli = Elem(ili);

		AssertSz(pli || !ili, "CDisplayML::CpFromPoint invalid line pointer");

		if(pli)
		{
			CMeasurer me(this);					// Create measurer
			me.SetCp(cp);

			// Transform to galley coordinates

			// Adjust coordinate relative to where the view starts from.
			pt.x -= rcView.left;

			// Is x coordinate within the view?
			if (pt.x >= 0)
			{
				// Adjust by the scroll value
				pt.x += _xScroll;
			}

			// Get character in line
			cch = pli->CchFromXpos(me, pt, pdispdim, &hit, pcpActual);
			// Don't allow click at EOL to select EOL marker and take into
			// account single line edits as well
			if(!fAllowEOL && cch == pli->_cch && pli->_cchEOP)
			{
				// Adjust position on line by amount backed up. OK for
				// me._rpCF and me._rpPF to get out of sync with me._rpTX,
				// since they're not needed for me.GetCp().
				cch += me._rpTX.BackupCpCRLF();
			}
			cp = me.GetCp();
		}
	}
	if(ptp)
        ptp->SetCp(cp);
	if(prp)
		prp->RpSet(ili, cch);
	if (phit)
		*phit = hit;

	return cp;	
}

/*
 *	CDisplayML::PointFromTp(rtp, prcClient, fAtEnd, pt, prp, taMode)
 *
 *	@mfunc
 *		Determine coordinates at given tp
 *
 *	@devnote
 *      --- Use when in-place active only ---
 *
 *	@rdesc
 *		line index at cp, -1 if error
 */
LONG CDisplayML::PointFromTp(
	const CRchTxtPtr &rtp,	//@parm Text ptr to get coordinates at
	const RECT *prcClient,	//@parm Client rectangle (can be NULL if active).
	BOOL		fAtEnd,		//@parm Return end of prev line for ambiguous cp
	POINT &		pt,			//@parm Returns point at cp in client coords
	CLinePtr * const prp,	//@parm Returns line pointer at tp (may be null)
	UINT		taMode,		//@parm Text Align mode: top, baseline, bottom
	CDispDim *	pdispdim)	//@parm Out parm for display dimensions
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::PointFromTp");

    _TEST_INVARIANT_

	LONG	 dy = 0;
	RECT	 rcView;
	CLinePtr rp(this);

    if(!WaitForRecalc(rtp.GetCp(), -1) || !rp.RpSetCp(rtp.GetCp(), fAtEnd))
		return -1;

    AssertSz(_ped->_fInPlaceActive || prcClient, 
		"CDisplayML::PointFromTp() called with invalid client rect");

    GetViewRect(rcView, prcClient);

	pt.x = rcView.left - _xScroll;
	pt.y = YposFromLine(rp) - _yScroll + rcView.top;

	CMeasurer me(this, rtp);
	me.Advance(-rp.RpGetIch());		// Backup to start of line		
	me.NewLine(*rp);				// Measure from there to where we are

	LONG xCalc = rp->XposFromCch(me, rp.RpGetIch(), taMode, pdispdim, &dy);

	if(pt.x + xCalc <= rcView.right || !GetWordWrap() || GetTargetDev())
	{
		// Width is in view or there is no wordwrap so just
		// add the length to the point.
		pt.x += xCalc;
	}
	else
	{
		// Remember we ignore trailing spaces at the end of the line in
		// the width, therefore the x value that MeasureText finds can 
		// be greater than the width in the line so we truncate to the 
		// previously calculated width which will ignore the spaces.
		pt.x += rp->_xLeft + rp->_xWidth; // We *don't* worry about xLineOverhang here
	}

	pt.y += dy;

	if(prp)
		*prp = rp;
	return rp;
}


//====================================  Rendering  =======================================

/*
 *	CDisplayML::Render(rcView, rcRender)
 *
 *	@mfunc	
 *		Searches paragraph boundaries around a range
 */
void CDisplayML::Render (
	const RECT &rcView,		//@parm View RECT
	const RECT &rcRender)	//@parm RECT to render (must be container in
							//		client rect)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::Render");

    _TEST_INVARIANT_
								
    LONG	cp;
	BOOL	fLinesToRender = TRUE;
	LONG	lCount = Count();
	CTxtSelection *psel = _ped->GetSelNC();
	POINT	pt;
	LONG	yBottomOfRender;
	LONG	yHeightForBitmap = 0;
	LONG	yHeightForLine;
    LONG	yLine;
	LONG	yRenderHeight = rcRender.top + _yScroll - rcView.top;

    if(psel)
		psel->ClearCchPending();
        
	// Review (murrays) this routine is called twice from disp.cpp (once for
	// rendering and once for transparent hit testing) and once from edit.cpp
	// (for printing).  Only the display rendering needs to notify the 
	// update and it does it there.  So the following is either undesired or
	// redundant, i.e., should be deleted.
	// Fire event "updating"
	//if(IsMain())
	//	_ped->TxNotify(EN_UPDATE, NULL);

	// Calculate line and cp to start display at
   	LONG ili = LineFromYpos(rcRender.top + _yScroll - rcView.top, &yLine, &cp);
	CLine *pli = Elem(ili);
	LONG yLi = pli ? pli->GetHeight() : 0;		// Null == some forms^3 empty control
	yLi = max(yLi, 0);

	if(yRenderHeight > yLine + yLi)
		fLinesToRender = FALSE;

	// Calculate point where text will start being displayed
   	pt.x = rcView.left - _xScroll;
   	pt.y = rcView.top  - _yScroll + yLine;
	yBottomOfRender = BottomOfRender(rcView, rcRender);

	// We only need check for whether we want to offscreen render if the
	// control is not transparent. Remember if the control is transparent,
	// the rendering of mixed character formats  will work because characters
	// in adjoining runs are only truncated if ExtTextOut is trying to clear
	// the display area at the same time.
	if (!IsMetafile() && IsMain() && !IsTransparent())
	{
		// Initialize height counter to first position to display
		yLi = pt.y;

		// Loop through visible lines until we have examined entire
		// line array or we have exceeded visible height
		CLine *pli = Elem(ili);
		for (LONG iliLoop = ili; 
			iliLoop < lCount && yLi < yBottomOfRender; iliLoop++, pli++)
		{
			if(pli->_fCollapsed)
				continue;
			
			yHeightForLine = pli->_yHeight;	// Get local copy of line height
			if(pli->_bFlags & fliUseOffScreenDC)
				yHeightForBitmap = max(yHeightForLine, yHeightForBitmap);

	        yLi += yHeightForLine;
		}
	}

	// Create renderer
	CRenderer re(this);

	// Prepare renderer
	if(!re.StartRender(rcView, rcRender, yHeightForBitmap))
		return;
	
	// Init renderer at start of first line to render
	re.SetCurPoint(pt);
   	cp = re.SetCp(cp);
    yLi = pt.y;

	if(fLinesToRender)
	{
		// Render each line in update rectangle
		for (; ili < lCount; ili++)
		{
			if (!Elem(ili)->Render(re) ||
				re.GetCurPoint().y >= yBottomOfRender)
			{
				break;
			}
#ifdef DEBUG
			cp  += Elem(ili)->_cch;
			yLi += Elem(ili)->GetHeight();

			// Rich controls with password characters stop at EOPs, 
			// so re.GetCp() may be less than cp.
			AssertSz(_ped->IsRich() && _ped->fUsePassword() || re.GetCp() == cp, 
				"CDisplayML::RenderView() - cp out of sync with line table");
			pt = re.GetCurPoint();
			AssertSz(pt.y == yLi,
				"CDisplayML::RenderView() - y out of sync with line table");
#endif
		}
	}
	re.EndRender();						  // Finish rendering
}


//===================================  View Updating  ===================================

/*
 *	CDisplayML::RecalcView(fUpdateScrollBars)
 *
 *	@mfunc
 *		Recalc all lines breaks and update first visible line
 *
 *	@rdesc
 *		TRUE if success
 */
BOOL CDisplayML::RecalcView(
	BOOL fUpdateScrollBars, RECT* prc)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::RecalcView");

	BOOL fRet = TRUE;
	LONG yHeightOld = _yHeight;
	LONG yScrollHeightOld = GetMaxYScroll();
	LONG xWidthOld = _xWidth;

	// Full recalc lines
	if(!RecalcLines())
	{
		// We're in deep trouble now, the recalc failed. Let's try to get out
		// of this with our head still mostly attached
		InitVars();
		fRet = FALSE;
        goto Done;
	}

	if(!_ped->GetTextLength())			// This is an empty control so
		CreateEmptyLine();				//  create one empty line

    // Force _xScroll = 0 if x scroll range is smaller than the view width
    if(_xWidth <= _xWidthView)
        _xScroll = 0;

	RebindFirstVisible();

	CheckView();

	// We only need to resize if the size needed to display the object has 
	// changed.
	if (yHeightOld != _yHeight || yScrollHeightOld != GetMaxYScroll() ||
		xWidthOld  != _xWidth)
	{
		if(FAILED(RequestResize()))
			_ped->GetCallMgr()->SetOutOfMemory();
		else if (prc && _ped->_fInOurHost)/*bug fix# 5830, forms3 relies on old behavior*/
			_ped->TxGetClientRect(prc);
	}

Done:

    // Now update scrollbars
	if(fUpdateScrollBars)
		RecalcScrollBars();

    return fRet;
}

/*
 *	CDisplayML::UpdateView(&rtp, cchOld, cchNew)
 *
 *	@mfunc
 *		Recalc lines and update the visible part of the display 
 *		(the "view") on the screen.
 *
 *	@devnote
 *      --- Use when in-place active only ---
 *
 *	@rdesc
 *		TRUE if success
 */
BOOL CDisplayML::UpdateView(
	const CRchTxtPtr &rtp,	//@parm Text ptr where change happened
	LONG cchOld,				//@parm Count of chars deleted
	LONG cchNew)				//@parm Count of chars inserted
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::UpdateView");

	BOOL fReturn = TRUE;
	BOOL fRecalcVisible = TRUE;
	RECT rcClient;
    RECT rcView;
	CLed led;
	CTxtSelection *psel = _ped->GetSelNC();
	LONG cpStartOfUpdate = rtp.GetCp();
	BOOL fNeedViewChange = FALSE;
	LONG yHeightOld = _yHeight;
	LONG yScrollHeightOld = GetMaxYScroll();
	LONG xWidthOld = _xWidth;
	LONG yScrollOld = _yScroll;
	LONG cpNewFirstVisible;

	if(_fNoUpdateView)
		return fReturn;

	AssertSz(_ped->_fInPlaceActive, "CDisplayML::UpdateView(...) called when inactive");

	if(rtp.GetCp() > _cpCalcMax || _fNeedRecalc)
	{
		// We haven't even calc'ed this far, so don't bother with updating
		// here.  Background recalc will eventually catch up to us.
		return TRUE;
	}

	AssertSz(rtp.GetCp() <= _cpCalcMax, "CDisplayML::UpdateView(...) - rtp > _cpCaclMax");

	_ped->TxGetClientRect(&rcClient);
	GetViewRect(rcView, &rcClient);

	if(psel && !psel->PuttingChar())
		psel->ClearCchPending();

	DeferUpdateScrollBar();

	// In general, background recalc should not start until both the scroll 
	// position is beyond the visible view and the cp is beyond the first visible 
	// character. However, for the recalc we will only wait on the height. 
	// Later calls to WaitForRecalc will wait on cpFirstVisible if that is 
	// necessary.
	_yWait = _yScroll + _yHeightView;
	_cpWait = -1;

	if(!RecalcLines(rtp, cchOld, cchNew, FALSE, TRUE, &led))
	{
		// We're in trouble now, the recalc failed. Let's try to get
		// out of this with our head still mostly attached
		InitVars();
		fRecalcVisible = TRUE;
		fReturn = FALSE;
		_ped->TxInvalidateRect (NULL, FALSE);
		fNeedViewChange = TRUE;
		goto Exit;
	}

	if(!_ped->GetTextLength())
	{
		if(LineCount())					// There are currently elements in
			Clear(AF_DELETEMEM);		//  line array, so zap them

		// This is an empty control so create one empty line
		CreateEmptyLine();
	}

	if(_xWidth <= _xWidthView)
    {
		// x scroll range is smaller than the view width, force x scrolling position = 0
		// we have to redraw all when this means scrolling back to home.
		// Problem lines are lines with trailing spaces crossing _xWidthView. UpdateCaret forces redraw
		// only when such lines are growing, misses shrinking.
		
		if (_xScroll != 0)
			{
			_ped->TxInvalidateRect(NULL, FALSE);			//REVIEW: find a smaller rectange?
			}
			
		_xScroll = 0;

    }

	if(led._yFirst >= _yScroll + _yHeightView)
	{
		// Update is after view: don't do anything
		fRecalcVisible = FALSE;
		AssertNr(VerifyFirstVisible());
		goto finish;
	}
	else if(led._yMatchNew <= _yScroll + _dyFirstVisible &&
			led._yMatchOld <= _yScroll + _dyFirstVisible &&
			_yScroll < _yHeight)
	{
		if (_yHeight != 0)
		{
			// Update is entirely before view: just update scroll position
			// but don't touch the screen
			_yScroll += led._yMatchNew - led._yMatchOld;
			_iliFirstVisible += led._iliMatchNew - led._iliMatchOld;
			_iliFirstVisible = max(_iliFirstVisible, 0);

			_cpFirstVisible += led._cpMatchNew - led._cpMatchOld;
			_cpFirstVisible = min(_ped->GetTextLength(), _cpFirstVisible);
			_cpFirstVisible = max(0, _cpFirstVisible);
			fRecalcVisible = FALSE;
		}
		else
		{
			// Odd outline case. Height of control can be recalc'd to zero due 
			// when outline mode collapses all lines to 0. Example of how to 
			// do this is tell outline to collapse to heading 1 and there is none.
			_yScroll = 0;
			_iliFirstVisible = 0;
			_cpFirstVisible = 0;
		}

		AssertNr(VerifyFirstVisible());
	}
	else
	{
		// Update overlaps visible view
		RECT rc = rcClient;
        RECT rcUpdate;

		// Do we need to resync the first visible?  Note that this if check
		// is mostly an optmization; we could decide to _always_ recompute
		// this _iliFirstVisible if we wanted to.
		if ( cpStartOfUpdate <= _cpFirstVisible || 
			led._iliMatchOld <= _iliFirstVisible ||
			led._iliMatchNew <= _iliFirstVisible ||
			led._iliFirst <= _iliFirstVisible )
		{
			// Edit overlaps the first visible. We try to maintain
			// approximately the same place in the file visible.
			cpNewFirstVisible = _cpFirstVisible;

			if(_iliFirstVisible - 1 == led._iliFirst)
			{
				// Edit occurred on line before visible view. Most likely
				// this means that the first character got pulled back to
				// the previous line so we want that line to be visible.
				cpNewFirstVisible = led._cpFirst;
			}

			// Change first visible entries because CLinePtr::RpSetCp() and
			// YPosFromLine() use them, but they're not valid
			_dyFirstVisible = 0;
			_cpFirstVisible = 0;
			_iliFirstVisible = 0;
			_yScroll = 0;

			// With certain formatting changes, it's possible for 
			// cpNewFirstVisible to be less that what's been calculated so far 
			// in RecalcLines above. Wait for things to catch up.

			WaitForRecalc(cpNewFirstVisible, -1);

			// TODO: make following more efficient (work around rp.CalculateCp()
			// all the way from zero)
			// Recompute scrolling position and first visible values after edit
		    CLinePtr rp(this);
   			rp.RpSetCp(cpNewFirstVisible, FALSE);
   			_yScroll = YposFromLine(rp);
   			_cpFirstVisible = rp.CalculateCp() - rp.RpGetIch();
   			_iliFirstVisible = rp;
		}
		AssertNr(VerifyFirstVisible());

		// Is there a match in the display area? - this can only happen if the
		// old match is on the screen and the new match will be on the screen
		if (led._yMatchOld < yScrollOld + _yHeightView &&
			led._yMatchNew < _yScroll + _yHeightView)
		{
			// We have a match inside visible view
			// Scroll the part that is below the old y pos of the match
			// or invalidate if the new y of the match is now below the view
			rc.top = rcView.top + (INT) (led._yMatchOld - yScrollOld);
			if(rc.top < rc.bottom)
			{
				// Calculate difference between new and old screen positions
				const INT dy = (INT) ((led._yMatchNew - _yScroll)) 
					- (led._yMatchOld - yScrollOld);

				if(dy)
				{
					if(!IsTransparent())
					{
    					_ped->TxScrollWindowEx(0, dy, &rc, &rcView, NULL, &rcUpdate, 0);
		    			_ped->TxInvalidateRect(&rcUpdate, FALSE);
						fNeedViewChange = TRUE;

    					if(dy < 0)
	    				{
		    				rc.top = rc.bottom + dy;
			    			_ped->TxInvalidateRect(&rc, FALSE);
							fNeedViewChange = TRUE;
				    	}
    				}
                    else
                    {
						// Adjust rect since we don't scroll in transparent
						// mode
						RECT	rcInvalidate = rc;
   						rcInvalidate.top += dy;

                        _ped->TxInvalidateRect(&rcInvalidate, FALSE);
						fNeedViewChange = TRUE;
                    }
				}
			}
			else
			{
				rc.top = rcView.top + led._yMatchNew - _yScroll;
				_ped->TxInvalidateRect(&rc, FALSE);
				fNeedViewChange = TRUE;
			}

			// Since we found that the new match falls on the screen, we can
			// safely set the bottom to the new match since this is the most
			// that can have changed.
			rc.bottom = rcView.top 
				+ (INT) (max(led._yMatchNew, led._yMatchOld) - _yScroll);
		}

		rc.top = rcView.top + (INT) (led._yFirst - _yScroll);

		// Set first line edited to be rendered using off-screen bitmap
		if (led._iliFirst < Count() && !IsTransparent() &&
			!(Elem(led._iliFirst)->_bFlags & fliUseOffScreenDC))
		{	
			Elem(led._iliFirst)->_bFlags |= (fliOffScreenOnce |	fliUseOffScreenDC);
		}
		
		// Invalidate part of update that is above match (if any)
		_ped->TxInvalidateRect (&rc, FALSE);
		fNeedViewChange = TRUE;
	}

finish:
	if(fRecalcVisible)
	{
		fReturn = WaitForRecalcView();
		if(!fReturn) 
			return FALSE;
	}
	if(fNeedViewChange)
		_ped->GetHost()->TxViewChange(FALSE);

	CheckView();

	// We only need to resize if size needed to display object has changed
	if (yHeightOld != _yHeight || yScrollHeightOld != GetMaxYScroll() ||
		xWidthOld  != _xWidth)
	{
		if(FAILED(RequestResize()))
			_ped->GetCallMgr()->SetOutOfMemory();
	}
	if(DoDeferredUpdateScrollBar())
	{
		if(FAILED(RequestResize()))
			_ped->GetCallMgr()->SetOutOfMemory();
		DoDeferredUpdateScrollBar();
	}

Exit:
	return fReturn;
}

void CDisplayML::InitVars()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::InitVars");

	_yScroll = _xScroll = 0;
	_iliFirstVisible = 0;
	_cpFirstVisible = _cpMin = 0;
	_dyFirstVisible = 0;
}

/*
 *	CDisplayML::GetCliVisible(pcpMostVisible)
 *
 *	@mfunc	
 *		Get count of visible lines and update _cpMostVisible for PageDown()
 *
 *	@rdesc
 *		count of visible lines
 */
LONG CDisplayML::GetCliVisible(
	LONG* pcpMostVisible, 				//@parm Returns cpMostVisible
	BOOL fLastCharOfLastVisible) const 	//@parm Want cp of last visible char
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetCliVisible");

	LONG cli	 = 0;							// Initialize count
	LONG ili	 = _iliFirstVisible;			// Start with 1st visible line
	LONG yHeight = _dyFirstVisible;
    LONG cp;
	LONG cchWhite = 0;

	for(cp = _cpFirstVisible;
		yHeight < _yHeightView && ili < Count();
		cli++, ili++)
	{
		const CLine* pli = Elem(ili);
		yHeight	+= pli->GetHeight();

		if (fLastCharOfLastVisible)
		{
			if (yHeight > _yHeightView)
			{
				// Back up cp to last visible character
				cp -= cchWhite;
				break;
			}

			// Save last lines white space to adjust cp if
			// this is the last fully displayed line.
			cchWhite = pli->_cchWhite;
		}
		cp += pli->_cch;
	}

    if(pcpMostVisible)
        *pcpMostVisible = cp;

	return cli;
}

//==================================  Inversion (selection)  ============================

/*
 *	CDisplayML::InvertRange(cp, cch)
 *
 *	@mfunc
 *		Invert a given range on screen (for selection)
 *
 *	@devnote
 *      --- Use when in-place active only ---
 *
 *	@rdesc
 *		TRUE if success
 */
BOOL CDisplayML::InvertRange (
	LONG cp,					//@parm Active end of range to invert
	LONG cch,					//@parm Signed length of range
	SELDISPLAYACTION selAction)	//@parm Describes what we are doing to the selection
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::InvertRange");

	LONG	 cpMost;
	RECT	 rc;
	RECT     rcView;
	CLinePtr rp(this);
	CRchTxtPtr  rtp(_ped);
	LONG	 y;
	LONG	 cpActive = _ped->GetSel()->GetCp();

    AssertSz(_ped->_fInPlaceActive,
    	"CDisplayML::InvertRange() called when not in-place active");

	if(cch < 0)						// Define cpMost, set cp = cpMin,
	{								//  and cch = |cch|
		cpMost = cp - cch;
		cch = -cch;
	}
	else
	{
		cpMost = cp;
		cp -= cch;
	}

#ifdef LINESERVICES
	if (g_pols)
		g_pols->DestroyLine(this);
#endif

	// If an object is being inverted, and nothing else is being inverted,
	// delegate to the ObjectMgr.  If fIgnoreObj is TRUE we highlight normally
	if (cch == 1 && _ped->GetObjectCount() &&
		(selAction == selSetNormal || selAction == selSetHiLite))
	{
		CObjectMgr* pobjmgr = _ped->GetObjectMgr();

		rtp.SetCp(cp);
		if(rtp.GetChar() == WCH_EMBEDDING)
		{
			if(pobjmgr)
				pobjmgr->HandleSingleSelect(_ped, cp, selAction == selSetHiLite);
			return TRUE;
		}
	}

	// If display is frozen, just update recalc region and move on.
	if(_padc)
	{
		AssertSz(cp >= 0, "CDisplayML::InvertRange: range (cp) goes below"
				"zero!!" );
		// Make sure these values are bounded.
		if(cp > _ped->GetTextLength())	// Don't bother updating region;
			return TRUE;				//  it's out of bounds

		if(cp + cch > _ped->GetTextLength())
			cch -= cp + cch - _ped->GetTextLength();

		_padc->UpdateRecalcRegion(cp, cch, cch);
		return TRUE;
	}

	if(!WaitForRecalcView())			// Ensure all visible lines are
		return FALSE;					//  recalc'd

	GetViewRect(rcView);				// Get view rectangle
	
	// Compute first line to invert and where to start on it
	if(cp >= _cpFirstVisible)
	{
		POINT pt;
		rtp.SetCp(cp);
		if(PointFromTp(rtp, NULL, FALSE, pt, &rp, TA_TOP) < 0)
			return FALSE;

		rc.left = pt.x;
		rc.top = pt.y;
	}
	else
	{
		cp = _cpFirstVisible;
		rp = _iliFirstVisible;
		rc.left = -1;
		rc.top = rcView.top + _dyFirstVisible;
	}				

	// Loop on all lines of range
	while (cp < cpMost && rc.top < rcView.bottom && rp.IsValid())
	{
		// Calculate rc.bottom first because rc.top takes into account
		// the dy of the first visible on the first loop.
		y = rc.top;
		y += rp->GetHeight();
		rc.bottom = min(y, rcView.bottom);
        rc.top = max(rc.top, rcView.top);

		if(rc.left == -1)
			rc.left = rp->_xLeft - _xScroll + rcView.left;

		//If we are inverting the active end of the selection, draw it offscreen
		//to minimize flicker.
		if (IN_RANGE(cp - rp.RpGetIch(), cpActive, cp - rp.RpGetIch() + rp->_cch) && 
			!IsTransparent() && !(rp->_bFlags & fliUseOffScreenDC))
		{	
			rp->_bFlags |= (fliOffScreenOnce | fliUseOffScreenDC);
		}

		cp += rp->_cch - rp.RpGetIch();

        rc.left = rcView.left;
        rc.right = rcView.right;

	    _ped->TxInvalidateRect(&rc, TRUE);
		rc.top = rc.bottom;
		if(!rp.NextRun())
			break;
	}
	_ped->TxUpdateWindow();				// Make sure window gets repainted
	return TRUE;
}


//===================================  Scrolling  =============================

/*
 *	CDisplay::GetYScroll()
 *
 *	@mfunc
 *		Returns vertical scrolling position
 */
LONG CDisplayML::GetYScroll() const
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetYScroll");

    return _yScroll;
}

/*
 *	CDisplay::VScroll(wCode, yPos)
 *
 *	@mfunc
 *		Scroll the view vertically in response to a scrollbar event
 *
 *	@devnote
 *      --- Use when in-place active only ---
 *
 *	@rdesc
 *		LRESULT formatted for WM_VSCROLL message		
 */
LRESULT CDisplayML::VScroll(
	WORD wCode,		//@parm Scrollbar event code
	LONG yPos)		//@parm Thumb position (yPos <lt> 0 for EM_SCROLL behavior)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::VScroll");

	LONG		cliVisible;
	LONG		dy = 0;
	BOOL		fTracking = FALSE;
	LONG		i;
	const LONG	ili = _iliFirstVisible;
	CLine *		pli = NULL;
	INT			yHeightSys = GetYHeightSys();
	LONG		yScroll = _yScroll;
    
    AssertSz(_ped->_fInPlaceActive, "CDisplay::VScroll() called when not in-place");

	if(yPos)
	{
		// Convert this from 16-bit to 32-bit if necessary.
		yPos = ConvertScrollToYPos(yPos);
	}

	yPos = min(yPos, _yHeight);

	switch(wCode)
	{
	case SB_BOTTOM:
		if(yPos < 0)
			return FALSE;
		WaitForRecalc(_ped->GetTextLength(), -1);
		yScroll = _yHeight;
		break;

	case SB_LINEDOWN:
		cliVisible = GetCliVisible();
		if(_iliFirstVisible + cliVisible < Count()
			&& 0 == _dyFirstVisible)
		{
			i = _iliFirstVisible + cliVisible;
			pli = Elem(i);
			if(IsInOutlineView())
			{	// Scan for uncollapsed line
				for(; pli->_fCollapsed && i < Count();
					pli++, i++);
			}
			if(i < Count())
				dy = pli->_yHeight;
		}
		else if(cliVisible > 1)
		{
			pli = Elem(_iliFirstVisible);
			dy = _dyFirstVisible;
			// TODO: scan until find uncollapsed line
			dy += pli->_yHeight;
		}
		else
			dy = _yHeight - _yScroll;

		if(dy >= _yHeightView)
			dy = yHeightSys;

		// Nothing to scroll, early exit
		if ( !dy )
			return MAKELRESULT(0, TRUE); 

		yScroll += dy;
		break;

	case SB_LINEUP:
		if(_iliFirstVisible > 0)
		{
			pli = Elem(_iliFirstVisible - 1);
			// TODO: scan until find uncollapsed line
			dy = pli->_yHeight;
		}
		else if(yScroll > 0)
			dy = min(yScroll, yHeightSys);

		if(dy > _yHeightView)
			dy = yHeightSys;
		yScroll -= dy;
		break;

	case SB_PAGEDOWN:
		cliVisible = GetCliVisible();
		yScroll += _yHeightView;
		if(yScroll < _yHeight && cliVisible > 0)
		{
			// TODO: Scan until find uncollapsed line
			dy = Elem(_iliFirstVisible + cliVisible - 1)->_yHeight;
			if(dy >= _yHeightView)
				dy = yHeightSys;

			else if(dy > _yHeightView - dy)
			{
				// Go at least a line if line is very big
				dy = _yHeightView - dy;
			}
			yScroll -= dy;
		}
		break;

	case SB_PAGEUP:
		cliVisible = GetCliVisible();
		yScroll -= _yHeightView;

		if (yScroll < 0)
		{
			// Scroll position can't be negative and we don't
			// need to back up to be sure we display a full line.
			yScroll = 0;
		}
		else if(cliVisible > 0)
		{
			// TODO: Scan until find uncollapsed line
			dy = Elem(_iliFirstVisible)->_yHeight;
			if(dy >= _yHeightView)
				dy = yHeightSys;

			else if(dy > _yHeightView - dy)
			{
				// Go at least a line if line is very big
				dy = _yHeightView - dy;
			}

			yScroll += dy;
		}
		break;

	case SB_THUMBTRACK:
	case SB_THUMBPOSITION:

		if(yPos < 0)
			return FALSE;

		yScroll = yPos;
		fTracking = TRUE;
		break;

	case SB_TOP:
		if(yPos < 0)
			return FALSE;
		yScroll = 0;
		break;

	case SB_ENDSCROLL:
		UpdateScrollBar(SB_VERT);
		return MAKELRESULT(0, TRUE);

	default:
		return FALSE;
	}
    
	ScrollView(_xScroll, min(yScroll, max(_yHeight - _yHeightView, 0)), fTracking, TRUE);

	// Force position update if we just finished a track
	if(wCode == SB_THUMBPOSITION)
		UpdateScrollBar(SB_VERT);

	// Return how many lines we scrolled
	return MAKELRESULT((WORD) (_iliFirstVisible - ili), TRUE);
}

/*
 *	CDisplay::LineScroll(cli, cch)
 *
 *	@mfunc
 *		Scroll view vertically in response to a scrollbar event
 */
void CDisplayML::LineScroll(
	LONG cli,		//@parm Count of lines to scroll vertically
	LONG cch)		//@parm Count of characters to scroll horizontally
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::LineScroll");
	
	//Make sure the line to scroll to is valid
	if (cli + _iliFirstVisible >= Count())
	{
        // change line count enough to display the last line
		cli = Count() - _iliFirstVisible;
	}

    // Get the absolute yScroll position by adding the difference of the line
    // we want to go to and the current _yScroll position
	LONG dyScroll = CalcYLineScrollDelta(cli, FALSE);
	if(dyScroll < 0 || _yHeight - (_yScroll + dyScroll) > _yHeightView - dyScroll)
		ScrollView(_xScroll, _yScroll + dyScroll, FALSE, FALSE);
}

/*
 *	CDisplayML::FractionalScrollView (yDelta)
 *
 *	@mfunc
 *		Allow view to be scrolled by fractional lines.
 */
void CDisplayML::FractionalScrollView ( LONG yDelta )
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::FractionalScrollView");

	if ( yDelta)
		ScrollView(_xScroll, min(yDelta + _yScroll, max(_yHeight - _yHeightView, 0)), FALSE, TRUE);
}

/*
 *	CDisplayML::ScrollToLineStart(iDirection)
 *
 *	@mfunc
 *		If the view is scrolled so that only a partial line is at the
 *		top, then scroll the view so that the entire view is at the top.
 */
void CDisplayML::ScrollToLineStart(
	LONG iDirection)	//@parm the direction in which to scroll (negative
						// means down the screen
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::ScrollToLineStart");

	// This code originally lined things up on a line. However, it doesn't work
	// very well with big objects especially at the end of the document. I am
	// leaving the call here in case we discover problems later. (a-rsail).

#if 0
	// If _dyFirstVisible is zero, then we're aligned on a line, so
	// nothing more to do.

	if(_dyFirstVisible)
	{
		LONG yScroll = _yScroll + _dyFirstVisible;

		if(iDirection <= 0) 
		{
			yScroll += Elem(_iliFirstVisible)->_yHeight;
		}

		ScrollView(_xScroll, yScroll, FALSE, TRUE);
	}
#endif // 0
}

/*
 *	CDisplayML::CalcYLineScrollDelta (cli, fFractionalFirst)
 *
 *	@mfunc
 *		Given a count of lines, positive or negative, calc the number
 *		of vertical units necessary to scroll the view to the start of
 *		the current line + the given count of lines.
 */
LONG CDisplayML::CalcYLineScrollDelta (
	LONG cli,
	BOOL fFractionalFirst )
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CalcYLineScrollDelta");

	LONG yScroll = 0;

	if(fFractionalFirst && _dyFirstVisible)	// Scroll partial for 1st.
	{
		Assert(_dyFirstVisible <= 0);		// get jonmat
		if(cli < 0)
		{
			cli++;
			yScroll = _dyFirstVisible;
		}
		else
		{
			cli--;
			yScroll = Elem(_iliFirstVisible)->_yHeight + _dyFirstVisible;
		}
	}

	if(cli > 0)
	{
		// Scrolling down
		cli = min(cli, Count() - _iliFirstVisible - 1);

		if (!fFractionalFirst && (0 == cli))
		{
			// If we are scrolling down and on the last line but we haven't scrolled to
			// the very bottom, then do so now.
			AssertSz(0 == yScroll, 
				"CDisplayML::CalcYLineScrollDelta last line & scroll");
			yScroll = _yHeight - _yScroll;

			// Limit scroll length to approximately 3 lines.
			yScroll = min(yScroll, 3 * GetYHeightSys());
		}
	}
	else if(cli < 0)
	{
		// Scrolling up
		cli = max(cli, -_iliFirstVisible);

		// At the top.
		if (!fFractionalFirst && (0 == cli))
		{
			// Make sure that we scroll back so first visible is 0.
			yScroll = _dyFirstVisible;

			// Limit scroll length to approximately 3 lines.
			yScroll = max(yScroll, -3 * GetYHeightSys());
		}
	}

	if(cli)
		yScroll += YposFromLine(_iliFirstVisible + cli) - YposFromLine(_iliFirstVisible);
	return yScroll;
}

/*
 *	CDisplayML::ScrollView(xScroll, yScroll, fTracking, fFractionalScroll)
 *
 *	@mfunc
 *		Scroll view to new x and y position
 *
 *	@devnote 
 *		This method tries to adjust the y scroll pos before
 *		scrolling to display complete line at top. x scroll 
 *		pos is adjusted to avoid scrolling all text off the 
 *		view rectangle.
 *
 *		Must be able to handle yScroll <gt> pdp->yHeight and yScroll <lt> 0
 *
 *	@rdesc
 *		TRUE if actual scrolling occurred, 
 *		FALSE if no change
 */
BOOL CDisplayML::ScrollView (
	LONG xScroll,		//@parm New x scroll position
	LONG yScroll,		//@parm New y scroll position
	BOOL fTracking,		//@parm TRUE indicates we are tracking scrollbar thumb
	BOOL fFractionalScroll)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::ScrollView");
						//		(don't update the scrollbar pos)
	BOOL fTryAgain = TRUE;
	RECT rcUpdate;      // ??? we may want use a region here but ScrollView is 
                        // rarely called with both a xScroll and yScroll value.
	LONG xWidthMax;
	LONG dx = 0;
	LONG dy = 0;
    RECT rcView;
	CTxtSelection *psel = _ped->GetSelNC();
	COleObject *pipo;
	BOOL fRestoreCaret = FALSE;


    AssertSz(_ped->_fInPlaceActive, "CDisplayML::ScrollView() called when not in-place");
    GetViewRect(rcView);
    
	if(xScroll == -1)
        xScroll = _xScroll;
	if(yScroll == -1)
        yScroll = _yScroll;
	
	// Determine vertical scrolling pos
	while(1)
	{
		BOOL fNothingBig = TRUE;
		LONG yFirst;
		LONG dyFirst;
		LONG cpFirst;
		LONG iliFirst;
		LONG yHeight;
		LONG iliT;

		yScroll = min(yScroll, GetMaxYScroll());
		yScroll = max(0, yScroll);
		dy = 0;

		// Ensure all visible lines are recalced
		if(!WaitForRecalcView())
			return FALSE;

		// Compute new first visible line
		iliFirst = LineFromYpos(yScroll, &yFirst, &cpFirst);

		if(cpFirst < 0)
		{
			// FUTURE (alexgo) this is pretty bogus, we should try to do
			// better in the next rel.

			TRACEERRORSZ("Display calc hosed, trying again");
			InitVars();
			_fNeedRecalc = TRUE;
			return FALSE;
		}

		if(iliFirst < 0)
		{
			// No line at _yScroll, use last line instead
			iliFirst = max(0, Count() - 1);
			cpFirst = _ped->GetTextLength() - Elem(iliFirst)->_cch;
			yScroll = _yHeight - Elem(iliFirst)->_yHeight;
			yFirst = _yScroll;
		}
		dyFirst = yFirst - yScroll;
		
		// Figure whether there is a big line
		// (more that a third of the view rect)
		for(iliT = iliFirst, yHeight = dyFirst;
			yHeight < _yHeightView && iliT < Count();
			iliT++)
		{
			const CLine* pli = Elem(iliT);
			if(pli->_yHeight >= _yHeightView / 3)
				fNothingBig = FALSE;
			yHeight += pli->_yHeight;
		}

		// If no big line and first pass, try to adjust 
		// scrolling pos to show complete line at top
		if(!fFractionalScroll && fTryAgain && fNothingBig && dyFirst != 0)
		{
			fTryAgain = FALSE;		// prevent any infinite loop

			Assert(dyFirst < 0);

			Tracef(TRCSEVINFO, "adjusting scroll for partial line at %d", dyFirst);
			// partial line visible at top, try to get a complete line showing
			yScroll += dyFirst;

			LONG yHeightLine = Elem(iliFirst)->_yHeight;

			// Adjust the height of the scroll by the height of the first 
			// visible line if we are scrolling down or if we are using the 
			// thumb (tracking) and we are on the last page of the view.
			if ((fTracking && yScroll + _yHeightView + yHeightLine > _yHeight)
				|| (!fTracking && _yScroll <= yScroll))
			{
				// Scrolling down so move down a little more
				yScroll += yHeightLine;
			}
		}
		else
		{
			dy = 0;
			if(yScroll != _yScroll)
			{
				_iliFirstVisible = iliFirst;
				_dyFirstVisible = dyFirst;
				_cpFirstVisible = cpFirst;
				dy = _yScroll - yScroll;
				_yScroll = yScroll;

				AssertSz(_yScroll >= 0, "CDisplayML::ScrollView _yScroll < 0");
				AssertNr(VerifyFirstVisible());
				if(!WaitForRecalcView())
			        return FALSE;
			}
			break;
		}
	}
	CheckView();

	// Determine horizontal scrolling pos.
	
	xWidthMax = _xWidth;

	// REVIEW (Victork) Restricting the range of the scroll is not really needed and could even be bad (bug 6104)
	
	xScroll = min(xScroll, xWidthMax);
	xScroll = max(0, xScroll);

	dx = _xScroll - xScroll;
	if(dx)
		_xScroll = xScroll;

	// Now perform the actual scrolling
	if(IsMain() && (dy || dx))
	{
		// Scroll only if scrolling < view dimensions and we are in-place
		if(IsActive() && !IsTransparent() && 
		    dy < _yHeightView && dx < _xWidthView)
		{
			// FUTURE: (ricksa/alexgo): we may be able to get rid of 
			// some of these ShowCaret calls; they look bogus.
			if (psel && psel->IsCaretShown())
			{
				_ped->TxShowCaret(FALSE);
				fRestoreCaret = TRUE;
			}
			_ped->TxScrollWindowEx((INT) dx, (INT) dy, NULL, &rcView,
				NULL, &rcUpdate, 0);

			_ped->TxInvalidateRect(&rcUpdate, FALSE);

			if(fRestoreCaret)
				_ped->TxShowCaret(FALSE);
		}
		else
			_ped->TxInvalidateRect(&rcView, FALSE);

		if(psel)
			psel->UpdateCaret(FALSE);

		if(!fTracking && dy)
		{
			UpdateScrollBar(SB_VERT);
			_ped->SendScrollEvent(EN_VSCROLL);
		}
		if(!fTracking && dx)
		{
			UpdateScrollBar(SB_HORZ);
			_ped->SendScrollEvent(EN_HSCROLL);
		}
						
		_ped->TxUpdateWindow();

		// FUTURE: since we're now repositioning in place active 
		// objects every time we draw, this call seems to be 
		// superfluous (AndreiB)

		// Tell object subsystem to reposition any in place objects
		if(_ped->GetObjectCount())
		{
			pipo = _ped->GetObjectMgr()->GetInPlaceActiveObject();
			if(pipo)
				pipo->OnReposition( dx, dy );
		}
	}
	return dy || dx;
}

/*
 *	CDisplayML::GetScrollRange(nBar)
 *
 *	@mfunc
 *		Returns the max part of a scrollbar range for scrollbar <p nBar>
 *
 *	@rdesc
 *		LONG max part of scrollbar range
 */
LONG CDisplayML::GetScrollRange(
	INT nBar) const		//@parm Scroll bar to interrogate (SB_VERT or SB_HORZ)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetScrollRange");

    Assert( IsMain() );

	LONG lRange = 0;
    
    if(nBar == SB_VERT && _fVScrollEnabled)
    {
	    if(_ped->TxGetScrollBars() & WS_VSCROLL)
			lRange = GetMaxYScroll();
    }
	else if((_ped->TxGetScrollBars() & WS_HSCROLL) && _fHScrollEnabled)
	{
		// Scroll range is maximum width plus room for the caret.
		lRange = max(0, _xWidth + dxCaret);
    }
	// Since thumb messages are limited to 16-bit, limit range to 16-bit
	lRange = min(lRange, _UI16_MAX);
	return lRange;
}

/*
 *	CDisplayML::UpdateScrollBar(nBar, fUpdateRange)
 *
 *	@mfunc
 *		Update either the horizontal or the vertical scrollbar and
 *		figure whether the scrollbar should be visible or not.
 *
 *	@rdesc
 *		BOOL
 */
BOOL CDisplayML::UpdateScrollBar(
	INT nBar,				//@parm Which scroll bar : SB_HORZ, SB_VERT
	BOOL fUpdateRange)		//@parm Should the range be recomputed and updated
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::UpdateScrollBar");

	// Note: In the old days we didn't allow autosize & scroll bars, so to keep
	// forms working, we need this special logic with respect to autosize.
	if (!IsActive() || _fInRecalcScrollBars ||
		!_ped->fInOurHost() && _ped->TxGetAutoSize())
	{
		// No scroll bars unless we are inplace active and we are not in the
		// process of updating scroll bars already.
		return TRUE;
	}

	const DWORD dwScrollBars = _ped->TxGetScrollBars();
	const BOOL fHide = !(dwScrollBars & ES_DISABLENOSCROLL);
	BOOL fReturn = FALSE;
	BOOL fEnabled = TRUE;
	BOOL fEnabledOld;
	LONG lScroll;
	CTxtSelection *psel = _ped->GetSelNC();
	BOOL fShowCaret = FALSE;

	// Get scrolling position
	if(nBar == SB_VERT)
	{
		if(!(dwScrollBars & WS_VSCROLL))
			return FALSE;

		fEnabledOld = _fVScrollEnabled;
        if(GetMaxYScroll() <= _yHeightView)
            fEnabled = FALSE;
    }
	else
	{
		if(!(dwScrollBars & WS_HSCROLL))
		{
			// Even if we don't have scrollbars, we may allow horizontal
			// scrolling.
			if(!_fHScrollEnabled && _xWidth > _xWidthView)
				_fHScrollEnabled = !!(dwScrollBars & ES_AUTOHSCROLL);

			return FALSE;
		}

		fEnabledOld = _fHScrollEnabled;
        if(_xWidth <= _xWidthView)
            fEnabled = FALSE;
	}

	// Don't allow ourselves to be re-entered.
	// Be sure to turn this to FALSE on exit
	_fInRecalcScrollBars = TRUE;

	// !s beforehand because all true values aren't necessarily equal
	if(!fEnabled != !fEnabledOld)
	{
		if(_fDeferUpdateScrollBar)
			_fUpdateScrollBarDeferred = TRUE;
		else
		{
			if (nBar == SB_HORZ)
				_fHScrollEnabled = fEnabled;
			else
				_fVScrollEnabled = fEnabled;
		}

		if(!_fDeferUpdateScrollBar)
		{
    		if(!fHide)
			{
				// Don't hide scrollbar, just disable
    			_ped->TxEnableScrollBar(nBar, fEnabled ? ESB_ENABLE_BOTH : ESB_DISABLE_BOTH);

				if (!fEnabled)
				{
					// The scroll bar is disabled. Therefore, all the text fits
					// on the screen so make sure the drawing reflects this.
					_yScroll = 0;
					_dyFirstVisible = 0;
					_cpFirstVisible = 0;
					_iliFirstVisible = 0;
					_ped->TxInvalidateRect(NULL, FALSE);
				}
			}
    		else 
    		{
    			fReturn = TRUE;
    			// Make sure to hide caret before showing scrollbar
    			if(psel)
    				fShowCaret = psel->ShowCaret(FALSE);

    			// Hide or show scroll bar
    			_ped->TxShowScrollBar(nBar, fEnabled);
				// The scroll bar affects the window which in turn affects the 
				// display. Therefore, if word wrap, repaint
				_ped->TxInvalidateRect(NULL, TRUE);
				// Needed for bug fix #5521
				_ped->TxUpdateWindow();

    			if(fShowCaret)
    				psel->ShowCaret(TRUE);
            }
		}
	}
	
	// Set scrollbar range and thumb position
	if(fEnabled)
	{
        if(fUpdateRange && !_fDeferUpdateScrollBar)
			_ped->TxSetScrollRange(nBar, 0, GetScrollRange(nBar), FALSE);
        
		if(_fDeferUpdateScrollBar)
			_fUpdateScrollBarDeferred = TRUE;
		else
		{
			lScroll = (nBar == SB_VERT)
				? ConvertYPosToScrollPos(_yScroll)
				: ConvertXPosToScrollPos(_xScroll);

			_ped->TxSetScrollPos(nBar, lScroll, TRUE);
		}
	}
	_fInRecalcScrollBars = FALSE;
	return fReturn;
}

/*
 *	CDisplayML::GetNaturalSize(hdcDraw, hicTarget, dwMode, pwidth, pheight)
 *
 *	@mfunc
 *		Recalculate display to input width & height for TXTNS_FITTOCONTENT.
 *
 *	@rdesc
 *		S_OK - Call completed successfully <nl>
 */
HRESULT	CDisplayML::GetNaturalSize(
	HDC hdcDraw,		//@parm DC for drawing
	HDC hicTarget,		//@parm DC for information
	DWORD dwMode,		//@parm Type of natural size required
	LONG *pwidth,		//@parm Width in device units to use for fitting 
	LONG *pheight)		//@parm Height in device units to use for fitting
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetNaturalSize");

	HRESULT hr = S_OK;

	// Set the height temporarily so the zoom factor will work out
	LONG yOrigHeightClient = SetClientHeight(*pheight);

	// Adjust height and width by view inset
	LONG widthView  = *pwidth;
	LONG heightView = *pheight;
	GetViewDim(widthView, heightView);

	// Store adjustment so we can restore it to height & width
	LONG widthAdj  = *pwidth  - widthView;
	LONG heightAdj = *pheight - heightView;
 	
	// Init measurer at cp = 0
	CMeasurer me(this);
	CLine liNew;
	LONG xWidth = 0, lineWidth;
	LONG yHeight = 0;
   	LONG cchText = _ped->GetTextLength();
	BOOL fFirstInPara = TRUE;

	LONG xWidthMax = GetWordWrap() ? widthView : -1;

	// The following loop generates new lines
	do 
	{	// Stuff text into new line
		UINT uiFlags = 0;

		// If word wrap is turned on, then we want to break on
		// words, otherwise, measure white space, etc.		
		if(GetWordWrap())
			uiFlags =  MEASURE_BREAKATWORD;

		if(fFirstInPara)
			uiFlags |= MEASURE_FIRSTINPARA;
	
		if(!liNew.Measure(me, -1, xWidthMax, uiFlags))
		{
			hr = E_FAIL;
			goto exit;
		}
		fFirstInPara = liNew._bFlags & fliHasEOP;

		// Keep track of width of widest line
		lineWidth = liNew._xWidth + liNew._xLineOverhang;
		xWidth = max(xWidth, lineWidth);
		yHeight += liNew._yHeight;		// Bump height

	} while (me.GetCp() < cchText);

	// Add caret size to width to guarantee that text fits. We don't
	// want to word break because the caret won't fit when the caller
	// tries a window this size.
	xWidth += dxCaret;

	*pwidth = xWidth;
	*pheight = yHeight;

	// Restore insets so output reflects true client rect needed
	*pwidth += widthAdj;
	*pheight += heightAdj;
		
exit:
	SetClientHeight(yOrigHeightClient);
	return hr;
}

/*
 *	CDisplayML::Clone()
 *
 *	@mfunc
 *		Make a copy of this object
 *
 *	@rdesc
 *		NULL - failed
 *		CDisplay *
 *
 *	@devnote
 *		Caller of this routine is the owner of the new display object.
 */
CDisplay *CDisplayML::Clone() const
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::Clone");

	CDisplayML *pdp = new CDisplayML(_ped);

	if(pdp)
	{
		// Initialize our base class
		if(pdp->CDisplay::Init())
		{
			pdp->InitFromDisplay(this);
			pdp->_xScroll = _xScroll;
			pdp->_fVScrollEnabled = _fVScrollEnabled;
			pdp->_fHScrollEnabled = _fHScrollEnabled;
			pdp->_fWordWrap = _fWordWrap;
			pdp->_cpFirstVisible = _cpFirstVisible;
			pdp->_iliFirstVisible = _iliFirstVisible;
			pdp->_yScroll = _yScroll;
			pdp->ResetDrawInfo(this);

			if(_pddTarget)
			{
				// Create a duplicate target device for this object
				pdp->SetMainTargetDC(_pddTarget->GetDC(), _xWidthMax);
			}

			// This can't be the active view since it is a clone
			// of some view.
			pdp->SetActiveFlag(FALSE);
		}
	}
	return pdp;
}

void CDisplayML::DeferUpdateScrollBar()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::DeferUpdateScrollBar");

	_fDeferUpdateScrollBar = TRUE;
	_fUpdateScrollBarDeferred = FALSE;
}

BOOL CDisplayML::DoDeferredUpdateScrollBar()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::DoDeferredUpdateScrollBar");

	_fDeferUpdateScrollBar = FALSE;
	if(!_fUpdateScrollBarDeferred)
		return FALSE;

	_fUpdateScrollBarDeferred = FALSE;
	BOOL fHorizontalUpdated = UpdateScrollBar(SB_HORZ, TRUE);
    
	return UpdateScrollBar(SB_VERT, TRUE) || fHorizontalUpdated;
}

LONG CDisplayML::GetMaxPixelWidth(void) const
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetMaxPixelWidth");

	return _xWidthMax ? LXtoDX(_xWidthMax) : GetViewWidth();
}

/*
 *	CDisplayML::GetMaxXScroll()
 *
 *	@mfunc
 *		Get the maximum x scroll value
 *
 *	@rdesc
 *		Maximum x scroll value
 *
 */
LONG CDisplayML::GetMaxXScroll() const
{
	return _xWidth + dxCaret;
}

/*
 *	CDisplayML::CreateEmptyLine()
 *
 *	@mfunc
 *		Create an empty line
 *
 *	@rdesc
 *		TRUE - iff successful
 */
BOOL CDisplayML::CreateEmptyLine()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CreateEmptyLine");

	// Make sure that this is being called appropriately
	AssertSz(_ped->GetTextLength() == 0,
		"CDisplayML::CreateEmptyLine called inappropriately");

	CMeasurer me(this);					// Create a measurer
	CLine *	  pliNew = Add(1, NULL);	// Add one new line

	if(!pliNew)
	{
		_ped->GetCallMgr()->SetOutOfMemory();
		TRACEWARNSZ("CDisplayML::CreateEmptyLine unable to add CLine to CLineArray");
		return FALSE;
	}

	// Measure the empty line
	if(!pliNew->Measure(me, -1, -1, MEASURE_BREAKATWORD | MEASURE_FIRSTINPARA))
	{
		Assert(FALSE);
		return FALSE;
	}
	return TRUE;
}

/*
 *	CDisplayML::AdjustToDisplayLastLine()
 *
 *	@mfunc
 *		Calculate the yscroll necessary to get the last line to display
 *
 *	@rdesc
 *		Updated yScroll
 *
 */
LONG CDisplayML::AdjustToDisplayLastLine(
	LONG yBase,			//@parm actual yScroll to display
	LONG yScroll)		//@parm proposed amount to scroll
{
	LONG iliFirst;
	LONG yFirst;

	if(yBase >= _yHeight)
	{
		// Want last line to be entirely displayed.
		// Compute new first visible line
		iliFirst = LineFromYpos(yScroll, &yFirst, NULL);

		// Is top line partial?
		if(yScroll != yFirst)
		{
			// Yes - bump scroll to the next line so the ScrollView
			// won't bump the scroll back to display the entire 
			// partial line since we want the bottom to display.
			yScroll = YposFromLine(iliFirst + 1);
		}
	}
	return yScroll;
}

/*
 *	CDisplayML::GetResizeHeight()
 *
 *	@mfunc
 *		Calculates height to return for a request resize
 *
 *	@rdesc
 *		Updated yScroll
 */
LONG CDisplayML::GetResizeHeight() const
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::GetResizeHeight");

    return CalcScrollHeight(_yHeight);
}

void CDisplayML::RebindFirstVisible()
{
	LONG cp = _cpFirstVisible;

	// Change first visible entries because CLinePtr::RpSetCp() and
	// YPosFromLine() use them, but they're not valid
	_dyFirstVisible = 0;
	_cpFirstVisible = 0;
	_iliFirstVisible = 0;
	_yScroll = 0;

	// Recompute scrolling position and first visible values after edit
    // force _yScroll = 0 if y scroll range is smaller than the view height
    if(_yHeight > _yHeightView)
    {   	
    	CLinePtr rp(this);
   		rp.RpSetCp(cp, FALSE);
   		_yScroll = YposFromLine(rp);
		// TODO: make following more efficient (work around rp.CalculateCp()
		// all the way from zero)
		// We use rp.GetCp() instead of cp, because cp could now be
		// woefully out of date.  RpSetCp will set us to the closest
		// available cp.
   		_cpFirstVisible = rp.CalculateCp() - rp.RpGetIch();
   		_iliFirstVisible = rp;
	}
}


// ================================  DEBUG methods  ============================================

#ifdef DEBUG
/*
 *	CDisplayML::CheckLineArray()
 *
 *	@mfunc	
 *		DEBUG routine that Asserts unless:
 *			1) sum of all line counts equals count of characters in story
 *			2) sum of all line heights equals height of display galley
 */
void CDisplayML::CheckLineArray() const
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CheckLineArray");

	LONG ili = Count();

	// If we are marked as needing a recalc or if we are in the process of a
	// background recalc, we cannot verify the line array 
	if(!_fRecalcDone || _fNeedRecalc || !ili)
		return;

	LONG cchText = _ped->GetTextLength();

	if (!cchText)
		return;

	LONG cp = 0;
	BOOL fFirstInPara;
	BOOL fPrevLineEOP = TRUE;
	LONG yHeight = 0;
	CLine const *pli = Elem(0);
	CRchTxtPtr rtp(_ped);

	while(ili--)
	{
		fFirstInPara = (pli->_bFlags & fliFirstInPara) != 0;
		AssertSz(!(fPrevLineEOP	^ fFirstInPara),
			"CDisplayML::CheckLineArray: Invalid first/prev flags");

		AssertSz(pli->_cch,
			"CDisplayML::CheckLineArray: cch == 0");

		yHeight += pli->GetHeight();
		cp		+= pli->_cch;
		fPrevLineEOP = (pli->_bFlags & fliHasEOP) != 0;
		pli++;
	}

	if((cp != cchText) && (cp != _cpCalcMax))
	{
		Tracef(TRCSEVINFO, "sigma (*this)[]._cch = %ld, cchText = %ld", cp, cchText);
		AssertSz(FALSE,
			"CDisplayML::CheckLineArray: sigma(*this)[]._cch != cchText");
	}

	if(yHeight != _yHeight)
	{
		Tracef(TRCSEVINFO, "sigma (*this)[]._yHeight = %ld, _yHeight = %ld", yHeight, _yHeight);
		AssertSz(FALSE,
			"CDisplayML::CheckLineArray: sigma(*this)[]._yHeight != _yHeight");
	}
}

void CDisplayML::DumpLines(
	LONG iliFirst,
	LONG cli)
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::DumpLines");

	LONG cch;
	LONG ili;
	TCHAR rgch[512];

	if(Count() == 1)
		wcscpy(rgch, TEXT("1 line"));
	else
		wsprintf(rgch, TEXT("%d lines"), Count());
	
#ifdef UNICODE
    // TraceTag needs to take UNICODE...
#else
	TRACEINFOSZ(TRCSEVINFO, rgch);
#endif

	if(cli < 0)
		cli = Count();
	else
		cli = min(cli, Count());
	if(iliFirst < 0)
		iliFirst = Count() - cli;
	else
		cli = min(cli, Count() - iliFirst);

	for(ili = iliFirst; cli > 0; ili++, cli--)
	{
		const CLine * const pli = Elem(ili);

		wsprintf(rgch, TEXT("Line %d (%ldc%ldw%ldh%x): \""), ili, pli->_cch, 
			pli->_xWidth + pli->_xLineOverhang, pli->_yHeight, pli->_bFlags);
		cch = wcslen(rgch);
		cch += GetLineText(ili, rgch + cch, CchOfCb(sizeof(rgch)) - cch - 4);
		rgch[cch++] = TEXT('\"');
		rgch[cch] = TEXT('\0');
#ifdef UNICODE
        // TraceTag needs to take UNICODE...
#else
    	TRACEINFOSZ(TRCSEVINFO, rgch);
#endif
	}
}

/*
 *	CDisplayML::CheckView()
 *
 *	@mfunc	
 *		DEBUG routine that checks coherence between _iliFirstVisible,
 *		_cpFirstVisible, and _dyFirstVisible
 */
void CDisplayML::CheckView()
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayML::CheckView");

	LONG yHeight;
	VerifyFirstVisible(&yHeight);

	if(yHeight != _yScroll + _dyFirstVisible)
	{
		Tracef(TRCSEVINFO, "sigma CLine._yHeight = %ld, CDisplay.yFirstLine = %ld", yHeight, _yScroll + _dyFirstVisible);
		AssertSz(FALSE, "CLine._yHeight != VIEW.yFirstLine");
	}
}

/*
 *	CDisplayML::VerifyFirstVisible(pHeight)
 *
 *	@mfunc
 *		DEBUG routine that checks coherence between _iliFirstVisible
 *		and _cpFirstVisible
 *
 *	@rdesc	TRUE if things are hunky dory; FALSE otherwise
 */
BOOL CDisplayML::VerifyFirstVisible(
	LONG *pHeight)
{
	LONG	cchSum;
	LONG	ili = _iliFirstVisible;
	CLine const *pli = Elem(0);
	LONG	yHeight;

	for(cchSum = yHeight = 0; ili--; pli++)
	{
		cchSum  += pli->_cch;
		yHeight += pli->GetHeight();
	}

	if(pHeight)
		*pHeight = yHeight;

	if(cchSum != _cpFirstVisible)
	{
		Tracef(TRCSEVINFO, "sigma CLine._cch = %ld, CDisplay.cpFirstVisible = %ld", cchSum, _cpMin);
		AssertSz(FALSE, "sigma CLine._cch != VIEW.cpMin");

		return FALSE;
	}
	return TRUE;
}

#endif // DEBUG