/*
 *	@doc INTERNAL
 *
 *	@module	EDIT.C - main part of CTxtEdit |
 *	
 *		See also textserv.cpp (ITextServices and SendMessage interfaces)
 *		and tomDoc.cpp (ITextDocument interface)
 *	
 *	Authors: <nl>
 *		Original RichEdit code: David R. Fulmer <nl>
 *		Christian Fortini, Murray Sargent, Alex Gounares, Rick Sailor,
 *		Jon Matousek
 *	
 *	History: <nl>
 *		12/28/95 jonmat-Added support of Magellan mouse and smooth scrolling.
 *
 *	@devnote
 *		Be sure to set tabs at every four (4) columns.  In fact, don't even
 *		think of doing anything else!
 *
 *	Copyright (c) 1995-2000 Microsoft Corporation. All rights reserved.
 */

#include "_common.h"
#include "_edit.h"
#include "_dispprt.h"
#include "_dispml.h"
#include "_dispsl.h"
#include "_select.h"
#include "_text.h"
#include "_runptr.h"
#include "_font.h"
#include "_measure.h"
#include "_render.h"
#include "_m_undo.h"
#include "_antievt.h"
#include "_rtext.h"
#include "_hyph.h"
#include "_uspi.h"
#include "_urlsup.h"

#ifndef NOLINESERVICES
#include "_ols.h"
#endif

#include "_txtbrk.h"
#include "_clasfyc.h"

#define CONTROL(_ch) (_ch - 'A' + 1)

ASSERTDATA

// This is not public because we don't really want folks using it.
// ITextServices is a private interface.
EXTERN_C const IID IID_ITextServices = { // 8d33f740-cf58-11ce-a89d-00aa006cadc5
	0x8d33f740,
	0xcf58,
	0x11ce,
	{0xa8, 0x9d, 0x00, 0xaa, 0x00, 0x6c, 0xad, 0xc5}
  };

// {13E670F4-1A5A-11cf-ABEB-00AA00B65EA1}
EXTERN_C const GUID IID_ITextHost = 
{ 0x13e670f4, 0x1a5a, 0x11cf, { 0xab, 0xeb, 0x0, 0xaa, 0x0, 0xb6, 0x5e, 0xa1 } };

// {13E670F5-1A5A-11cf-ABEB-00AA00B65EA1}
EXTERN_C const GUID IID_ITextHost2 = 
{ 0x13e670f5, 0x1a5a, 0x11cf, { 0xab, 0xeb, 0x0, 0xaa, 0x0, 0xb6, 0x5e, 0xa1 } };

// this is used internally do tell if a data object is one of our own.
EXTERN_C const GUID IID_IRichEditDO =
{ /* 21bc3b20-e5d5-11cf-93e1-00aa00b65ea1 */
    0x21bc3b20,
    0xe5d5,
    0x11cf,
    {0x93, 0xe1, 0x00, 0xaa, 0x00, 0xb6, 0x5e, 0xa1}
};

// Static data members
DWORD CTxtEdit::_dwTickDblClick;	// time of last double-click
POINT CTxtEdit::_ptDblClick;		// position of last double-click

//HCURSOR CTxtEdit::_hcurCross = 0;	// We don't implement outline drag move
HCURSOR CTxtEdit::_hcurArrow = 0;
HCURSOR CTxtEdit::_hcurHand = 0;
HCURSOR CTxtEdit::_hcurIBeam = 0;
HCURSOR CTxtEdit::_hcurItalic = 0;
HCURSOR CTxtEdit::_hcurSelBar = 0;
HCURSOR CTxtEdit::_hcurVIBeam = 0;
HCURSOR CTxtEdit::_hcurVItalic = 0;

const WCHAR szCRLF[]= TEXT("\r\n");
const WCHAR szCR[]	= TEXT("\r");

WORD	g_wFlags = 0;					// Keyboard controlled flags

/*
 *	GetKbdFlags(vkey, dwFlags)
 *
 *	@func
 *		return bit mask (RSHIFT, LSHIFT, RCTRL, LCTRL, RALT, or LALT)
 *		corresponding to vkey = VK_SHIFT, VK_CONTROL, or VK_MENU and
 *		dwFlags
 *
 *	@rdesc
 *		Bit mask corresponding to vkey and dwFlags
 */
DWORD GetKbdFlags(
	WORD	vkey,		//@parm Virtual key code
	DWORD	dwFlags)	//@parm lparam of WM_KEYDOWN msg
{		
	if(vkey == VK_SHIFT)
		return (LOBYTE(HIWORD(dwFlags)) == 0x36) ? RSHIFT : LSHIFT;

	if(vkey == VK_CONTROL)
		return (HIWORD(dwFlags) & KF_EXTENDED) ? RCTRL : LCTRL;

	Assert(vkey == VK_MENU);

	return (HIWORD(dwFlags) & KF_EXTENDED) ? RALT : LALT;
}

LONG TwipsToHalfPoints(
	LONG x)
{
	return x > 0 ? (x + 5)/10 : 0;		// Convert twips to half points
}

LONG TwipsToQuarterPoints(
	LONG x)
{
	return x > 0 ? (x + 3)/5 : 0;		// Convert twips to quarter points
}

LONG CheckTwips(
	LONG x)
{
	return x > 0 ? min(x, 255) : 0;
}


///////////////// CTxtEdit Creation, Initialization, Destruction ///////////////////////////////////////

/*
 *	CTxtEdit::CTxtEdit()
 *
 *	@mfunc
 *		constructor
 */
CTxtEdit::CTxtEdit(
	ITextHost2 *phost,
	IUnknown * punk)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CTxtEdit");

	_unk.Init();
	_punk = (punk) ? punk : &_unk;
	_ldte.Init(this);
	_phost	  = phost;
	_cpAccelerator = -1;					// Default to no accelerator

	// Initialize _iCF and _iPF to something bogus
	Set_iCF(-1);
	Set_iPF(-1);

	// Initialize local maximum text size to window default
	_cchTextMost = cInitTextMax;

	// This actually counts the number of active ped
	W32->AddRef();
}

/*
 *	CTxtEdit::~CTxtEdit()
 *
 *	@mfunc
 *		Destructor
 */
CTxtEdit::~CTxtEdit ()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::~CTxtEdit");

	Assert(!_fMButtonCapture);				// Need to properly transition
											//  Magellan mouse if asserts!
	_fSelfDestruct = TRUE;					// Tell the Call Mgr not to
											//  call this any more
	// Flush clipboard first
	_ldte.FlushClipboard();

	if(_pDocInfo)							// Do this before closing
	{										//  down internal structures
		CloseFile(TRUE);					// Close any open file
		delete _pDocInfo;					// Delete document info
		_pDocInfo = NULL;
	}

	if(_pdetecturl)
		delete _pdetecturl;

#ifndef NOCOMPLEXSCRIPTS
	if (_pbrk)
		delete _pbrk;
#endif

	if(_pobjmgr)
		delete _pobjmgr;

	// Release our reference to selection object
	if(_psel)
		_psel->Release();

	// Delete undo and redo managers
	if(_pundo)
		_pundo->Destroy();

#ifndef NOPRIVATEMESSAGE
	if (_pMsgNotify)
		delete _pMsgNotify;
#endif

	// Release message filter.
	// Note that the attached message filter must have released this document
	// Otherwise we will never get here.
	if (_pMsgFilter)
	{
		_pMsgFilter->Release();
		_pMsgFilter = 0;
	}

	if(_predo)
		_predo->Destroy();

	ReleaseFormats(Get_iCF(), Get_iPF());	// Release default formats

	delete _pdp;						// Delete displays
	_pdp = NULL;						// Break any further attempts to
										//  use display
	delete _pdpPrinter;

	if (_fHost2)
	{
		// We are in a windows host - need to deal with the shutdown
		// problem where the window can be destroyed before text
		// services is.
		if (!_fReleaseHost)
		{
			((ITextHost2*)_phost)->TxFreeTextServicesNotification();
		}
		else
		{
			// Had to keep host alive so tell it we are done with it.
			_phost->Release();
		}
	}
			
	W32->Release();
}

void CTxtEdit::TxInvalidateRect(const RECT *prc)
{
	_phost->TxInvalidateRect(prc, FALSE);
}

void CTxtEdit::TxInvalidateRect(const RECTUV *prcuv)
{
	CMeasurer me(_pdp);
	RECT rc;
	_pdp->RectFromRectuv(rc, *prcuv);
	_phost->TxInvalidateRect(&rc, FALSE);
}

/*
 *	CTxtEdit::Init (prcClient)
 *
 *	@mfunc
 *		Initializes this CTxtEdit. Called by CreateTextServices()
 *
 *	@rdesc
 *		Return TRUE if successful
 */

BOOL CTxtEdit::Init (
	const RECT *prcClient)		//@parm Client RECT
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::Init");

	CCharFormat 		CF;
	DWORD				dwBits = 0;
	DWORD				dwMask;
	LONG				iCF, iPF;
	CParaFormat 		PF;
	CCallMgr			callmgr(this);

	static BOOL fOnce = FALSE;
	if (!fOnce)
	{
		CLock lock;
		fOnce =	TRUE;
		_fnpPropChg[ 0] = &CTxtEdit::OnRichEditChange;			// TXTBIT_RICHTEXT			
		_fnpPropChg[ 1] = &CTxtEdit::OnTxMultiLineChange;		// TXTBIT_MULTILINE		
		_fnpPropChg[ 2] = &CTxtEdit::OnTxReadOnlyChange;		// TXTBIT_READONLY			
		_fnpPropChg[ 3] = &CTxtEdit::OnShowAccelerator;			// TXTBIT_SHOWACCELERATOR			
		_fnpPropChg[ 4] = &CTxtEdit::OnUsePassword;				// TXTBIT_USEPASSWORD			
		_fnpPropChg[ 5] = &CTxtEdit::OnTxHideSelectionChange;	// TXTBIT_HIDESELECTION			
		_fnpPropChg[ 6] = &CTxtEdit::OnSaveSelection;			// TXTBIT_SAVESELECTION			
		_fnpPropChg[ 7] = &CTxtEdit::OnAutoWordSel;				// TXTBIT_AUTOWORDSEL			
		_fnpPropChg[ 8] = &CTxtEdit::OnTxVerticalChange;		// TXTBIT_VERTICAL			
		_fnpPropChg[ 9] = &CTxtEdit::NeedViewUpdate;			// TXTBIT_SELECTIONBAR			
		_fnpPropChg[10] = &CTxtEdit::OnWordWrapChange;			// TXTBIT_WORDWRAP 			
		_fnpPropChg[11] = &CTxtEdit::OnAllowBeep;				// TXTBIT_ALLOWBEEP				
		_fnpPropChg[12] = &CTxtEdit::OnDisableDrag;    			// TXTBIT_DISABLEDRAG			
		_fnpPropChg[13] = &CTxtEdit::NeedViewUpdate;			// TXTBIT_VIEWINSETCHANGE			
		_fnpPropChg[14] = &CTxtEdit::OnTxBackStyleChange;		// TXTBIT_BACKSTYLECHANGE			
		_fnpPropChg[15] = &CTxtEdit::OnMaxLengthChange;			// TXTBIT_MAXLENGTHCHANGE			
		_fnpPropChg[16] = &CTxtEdit::OnScrollChange;			// TXTBIT_SCROLLBARCHANGE			
		_fnpPropChg[17] = &CTxtEdit::OnCharFormatChange;		// TXTBIT_CHARFORMATCHANGE 			
		_fnpPropChg[18] = &CTxtEdit::OnParaFormatChange;		// TXTBIT_PARAFORMATCHANGE		
		_fnpPropChg[19] = &CTxtEdit::NeedViewUpdate;			// TXTBIT_EXTENTCHANGE			
		_fnpPropChg[20] = &CTxtEdit::OnClientRectChange;		// TXTBIT_CLIENTRECTCHANGE			
	}

	// Set up default CCharFormat and CParaFormat
	if (TxGetDefaultCharFormat(&CF, dwMask) != NOERROR ||
		TxGetDefaultParaFormat(&PF)			!= NOERROR ||
		FAILED(GetCharFormatCache()->Cache(&CF, &iCF)) ||
		FAILED(GetParaFormatCache()->Cache(&PF, &iPF)))
	{
		return FALSE;
	}

	GetTabsCache()->Release(PF._iTabs);
	Set_iCF(iCF);								// Save format indices
	Set_iPF(iPF);

	// Load mouse cursors (but only for first instance)
	if(!_hcurArrow)
	{
		_hcurArrow = LoadCursor(0, IDC_ARROW);
		if(!_hcurHand)
		{
			if (W32->_dwMajorVersion < 5)
				_hcurHand	= LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_HAND));
			else
				_hcurHand	= LoadCursor(0, IDC_HAND);
		}
		if(!_hcurIBeam)							// Load cursor
			_hcurIBeam	= LoadCursor(0, IDC_IBEAM);
		if(!_hcurItalic)
			_hcurItalic	= LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_ITALIC));
		if(!_hcurSelBar)
			_hcurSelBar = LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_SELBAR));
		if(!_hcurVItalic)
			_hcurVItalic = LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_VITALIC));
		if(!_hcurVIBeam)
			_hcurVIBeam	= LoadCursor(hinstRE, MAKEINTRESOURCE(CUR_VIBEAM));
	}

#ifdef DEBUG
	// The host is going to do some checking on richtext vs. plain text.
	_fRich = TRUE;
#endif // DEBUG

	if(_phost->TxGetPropertyBits (TXTBITS |		// Get host state flags
		TXTBIT_MULTILINE | TXTBIT_SHOWACCELERATOR,	//  that we cache or need
		&dwBits) != NOERROR)						//  for display setup
	{
		return FALSE;
	}												// Cache bits defined by
	_dwFlags = dwBits & TXTBITS;					//  TXTBITS mask

	if ((dwBits & TXTBIT_SHOWACCELERATOR) &&		// They want accelerator,
		FAILED(UpdateAccelerator()))				//  so let's get it
	{
		return FALSE;
	}		

	_fTransparent = TxGetBackStyle() == TXTBACK_TRANSPARENT;
	if(dwBits & TXTBIT_MULTILINE)					// Create and initialize
		_pdp = new CDisplayML(this);				//  display
	else
		_pdp = new CDisplaySL(this);
	Assert(_pdp);

	if(!_pdp || !_pdp->Init())
		return FALSE;

	_fUseUndo  = TRUE;
	_fAutoFont = TRUE;
	_fDualFont = TRUE;
	_f10DeferChangeNotify = 0;

	// Set whether we are in our host or not
	ITextHost2 *phost2;
	if(_phost->QueryInterface(IID_ITextHost2, (void **)&phost2)	== NOERROR)
	{
		// We assume that ITextHost2 means this is our host
		phost2->Release();
		_fHost2 = TRUE;
	}
	else								// Get maximum from our host
		_phost->TxGetMaxLength(&_cchTextMost);

	// Add EOP iff Rich Text
	if(IsRich())
	{
		// We should _not_ be in 10 compatibility mode yet.
		// If we transition into 1.0 mode, we'll add a CRLF
		// at the end of the document.
		SetRichDocEndEOP(0);
	}

#ifndef NOLINESERVICES
	// Allow for win.ini control over use of line services
	if (W32->fUseLs())
	{
		OnSetTypographyOptions(TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
	}

#ifndef NOCOMPLEXSCRIPTS
	if (W32->GetDigitSubstitutionMode() != DIGITS_NOTIMPL)
		OrCharFlags(FDIGITSHAPE);		// digit substitution presents

	// Initialize the BiDi property
	// It is set to true if OS is BiDi (the system default LCID is a BiDi language)
	// or if the current keyboard code page is a BiDi code page
	// or if system.ini says we should do it.
	if (W32->OnBiDiOS() ||
		IsBiDiCharRep(GetKeyboardCharRep(0xFFFFFFFF)) ||
		W32->fUseBiDi())
	{
		OrCharFlags(FRTL);
	}

	_fAutoKeyboard = IsBiDi() && IsBiDiKbdInstalled();	
#endif // NOCOMPLEXSCRIPTS

#endif

	return TRUE;
}


///////////////////////////// CTxtEdit IUnknown ////////////////////////////////

/*
 *	CTxtEdit::QueryInterface (riid, ppv)
 *
 *	@mfunc
 *		IUnknown method
 *
 *	@rdesc
 *		HRESULT = (if success) ? NOERROR : E_NOINTERFACE
 *
 *	@devnote
 *		This interface is aggregated. See textserv.cpp for discussion.
 */
HRESULT CTxtEdit::QueryInterface(
	REFIID riid,
	void **ppv)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::QueryInterface");

	return _punk->QueryInterface(riid, ppv);
}

/*
 *	CTxtEdit::AddRef()
 *
 *	@mfunc
 *		IUnknown method
 *
 *	@rdesc
 *		ULONG - incremented reference count
 */
ULONG CTxtEdit::AddRef(void)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::AddRef");

	return _punk->AddRef();
}

/*
 *	CTxtEdit::Release()
 *
 *	@mfunc
 *		IUnknown method
 *
 *	@rdesc
 *		ULONG - decremented reference count
 */
ULONG CTxtEdit::Release(void)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::Release");

	return _punk->Release();
}

////////////////////////// Undo Management  //////////////////////////////

/*
 *	CTxtEdit::CreateUndoMgr (cUndoLim, flags)
 *
 *	@mfunc
 *		Creates an undo stack
 *
 *	@rdesc
 *		Ptr to new IUndoMgr 
 */
IUndoMgr *CTxtEdit::CreateUndoMgr(
	LONG	cUndoLim,			//@parm Size limit
	USFlags flags)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CreateUndoMgr");

	if(!_fUseUndo)
		return NULL;

	IUndoMgr *pmgr = new CUndoStack(this, cUndoLim, flags);
	if(!pmgr)
		return NULL;

	if(!pmgr->GetUndoLimit())
	{
		// The undo stack failed to initialize properly (probably
		// lack of memory). Trash it and return NULL.
		pmgr->Destroy();
		return NULL;
	}
	// We may be asked to create a new undo/redo manager
	// before we are completely done with initialization.
	// We need to clean up memory we have already allocated.
	if(flags & US_REDO)
	{
		if(_predo)
			_predo->Destroy();
		_predo = pmgr;
	}
	else
	{
		if(_pundo)
			_pundo->Destroy();
		_pundo = pmgr;
	}
	return pmgr;
}

/*
 *	CTxtEdit::HandleUndoLimit (cUndoLim)
 *
 *	@mfunc
 *		Handles the EM_SETUNDOLIMIT message
 *
 *	@rdesc	
 *		Actual limit to which things were set.
 */
LRESULT CTxtEdit::HandleSetUndoLimit(
	LONG cUndoLim) 		//@parm	Requested limit size
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::HandleSetUndoLimit");

	if (cUndoLim == tomSuspend ||			// This option really just
		cUndoLim == tomResume)				//  suspends undo, i.e.,
	{										//  doesn't discard existing	
		_fUseUndo = (cUndoLim == tomResume);//  antievents	
		return _pundo ? _pundo->GetUndoLimit() : 0;							
	}

	if(cUndoLim < 0)
		cUndoLim = DEFAULT_UNDO_SIZE;

	if(!cUndoLim)
	{
		_fUseUndo = FALSE;
		if(_pundo)
		{
			_pundo->Destroy();
			_pundo = NULL;
		}
		if(_predo)
		{
			_predo->Destroy();
			_predo = NULL;
		}
	}
	else if(!_pundo)
	{
		_fUseUndo = TRUE;
		// Don't worry about return value; if it's NULL, we're
		// in the same boat as if the API wasn't called (so later
		// on, we might try to allocate the default).
		CreateUndoMgr(cUndoLim, US_UNDO);
	}
	else
	{
		cUndoLim = _pundo->SetUndoLimit(cUndoLim);

		// Setting the undo limit on the undo stack will return to
		// us the actual amount set.  Try to set the redo stack to 
		// the same size.  If it can't go that big, too bad.
		if(_predo)
			_predo->SetUndoLimit(cUndoLim);
	}
	return cUndoLim;
}

/*
 *	CTxtEdit::HandleSetTextMode(mode)
 *
 *	@mfunc	handles setting the text mode
 *
 *	@rdesc	LRESULT; 0 (NOERROR) on success, OLE failure code on failure.
 *
 *	@devnote	the text mode does not have to be fully specified; it
 *			is sufficient to merely specify the specific desired behavior.
 *
 *			Note that the edit control must be completely empty for this
 *			routine to work.
 */
LRESULT CTxtEdit::HandleSetTextMode(
	DWORD mode)			//@parm the desired mode
{
	LRESULT lres = 0;

	// First off, we must be completely empty
	if (GetAdjustedTextLength() || 
		_pundo && _pundo->CanUndo() ||
		_predo && _predo->CanUndo())
	{
		return E_UNEXPECTED;
	}

	// These bits are considered one at a time; thus the absence of
	// any bits does _NOT_ imply any change in behavior.

	// TM_RICHTEXT && TM_PLAINTEXT are mutually exclusive; they cannot
	// be both set.  Same goes for TM_SINGLELEVELUNDO / TM_MULTILEVELUNDO
	// and TM_SINGLECODEPAGE / TM_MULTICODEPAGE
	if((mode & (TM_RICHTEXT | TM_PLAINTEXT)) == (TM_RICHTEXT | TM_PLAINTEXT) ||
	   (mode & (TM_SINGLELEVELUNDO | TM_MULTILEVELUNDO)) ==
			(TM_SINGLELEVELUNDO | TM_MULTILEVELUNDO) ||
	   (mode & (TM_SINGLECODEPAGE | TM_MULTICODEPAGE)) ==
			(TM_SINGLECODEPAGE | TM_MULTICODEPAGE))
	{
		lres = E_INVALIDARG;
	}
	else if((mode & TM_PLAINTEXT) && IsRich())
		lres = OnRichEditChange(FALSE);

	else if((mode & TM_RICHTEXT) && !IsRich())
		lres = OnRichEditChange(TRUE);

	if(!lres)
	{
		if(mode & TM_SINGLELEVELUNDO)
		{
			if(!_pundo)
				CreateUndoMgr(1, US_UNDO);

			if(_pundo)
			{
				// We can 'Enable' single level mode as many times
				// as we want, so no need to check for it before hand.
				lres = ((CUndoStack *)_pundo)->EnableSingleLevelMode();
			}
			else
				lres = E_OUTOFMEMORY;
		}
		else if(mode & TM_MULTILEVELUNDO)
		{
			// If there's no undo stack, no need to do anything,
			// we're already in multi-level mode
			if(_pundo && ((CUndoStack *)_pundo)->GetSingleLevelMode())
				((CUndoStack *)_pundo)->DisableSingleLevelMode();
		}

		if(mode & TM_SINGLECODEPAGE)
			_fSingleCodePage = TRUE;

		else if(mode & TM_MULTICODEPAGE)
			_fSingleCodePage = FALSE;
	}

	// We don't want this marked modified after this operation to make us
	// work better in dialog boxes.
	_fModified = FALSE;

	return lres;
}

/*
 *	CTxtEdit::HandleSetTextFlow(mode)
 *
 *	@mfunc	handles setting the text flow
 *
 *	@rdesc	LRESULT; 0 (NOERROR) on success, 1 (S_FALSE) for invalide mode
 *
 */
LRESULT CTxtEdit::HandleSetTextFlow(
	DWORD mode)			//@parm the desired mode
{
	TRACEBEGIN(TRCSUBSYSTS, TRCSCOPEINTERN, "CTxtEdit::HandleSetTextFlow");

	if (!IN_RANGE(0, mode, 3) || !_pdp)
		return S_FALSE;

	if (mode == _pdp->GetTflow())		// No change
		return NOERROR;

	// We pretend like something actually happened.
	GetCallMgr()->SetChangeEvent(CN_GENERIC);

	_pdp->SetTflow(mode);

	TxShowScrollBar(SB_HORZ, _pdp->IsUScrollEnabled());
	TxShowScrollBar(SB_VERT, _pdp->IsVScrollEnabled());

	NeedViewUpdate(TRUE);
	return NOERROR;
	
}

extern ICustomTextOut *g_pcto;
/*
 *	CTxtEdit::GetCcs()
 *
 *	@mfunc
 *		Fetches a CCcs for a specific CCharFormat
 *
 *	@rdesc
 *		Ptr to CCcs
 */
CCcs* CTxtEdit::GetCcs(
	const CCharFormat *const pCF, 
	const LONG	dvpInch, 
	DWORD		dwFlags, 
	HDC			hdc)
{
	//Note, don't do ClearType for metafiles or printing.
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetCcs");

	CCharFormat CF = *pCF;
	if (g_pcto && FUseCustomTextOut())
		CF._dwEffects |= CFE_CUSTOMTEXTOUT;

#ifndef NODRAFTMODE
	// Use draft mode font only for displays
	if (_fDraftMode && (!hdc || GetDeviceCaps(hdc, TECHNOLOGY) == DT_RASDISPLAY))
	{
		SHORT iFont;
		SHORT yHeight;
		QWORD qwFontSig;
		COLORREF crColor;

		if (W32->GetDraftModeFontInfo(iFont, yHeight, qwFontSig, crColor))
		{
			// Only hammer the name if the charset is OK
			if (FontSigFromCharRep(CF._iCharRep) & qwFontSig)
				CF._iFont = iFont;
			// Hammer the size always
			CF._yHeight = yHeight;
		}
	}
#endif

	if (dwFlags == -1)
		dwFlags = _pdp->GetTflow();

	if (_fUseAtFont)
		dwFlags |= FGCCSUSEATFONT;

	return fc().GetCcs(&CF, dvpInch, dwFlags, hdc);
}


CHyphCache *g_phc;
/*
 *	CTxtEdit::GetHyphCache()
 *
 *	@mfunc
 *		returns a pointer to the CHyphCache class (creating it if necessary)
 *
 *	@rdesc
 *		Ptr to CHyphCache class
 */
CHyphCache* CTxtEdit::GetHyphCache(void)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetHyphCache");
	if (!g_phc)
		g_phc = new CHyphCache();

	return g_phc;
}

////////////////////////// Uniscribe Interface //////////////////////////////
#ifndef NOCOMPLEXSCRIPTS
/*
 *	GetUniscribe()
 *
 *	@mfunc
 *		returns a pointer to the Uniscribe interface object
 *
 *	@rdesc
 *		Ptr to Uniscribe interface
 */
extern BOOL g_fNoUniscribe;
CUniscribe* GetUniscribe()
{
	if (g_pusp)
		return g_pusp;

	if (g_fNoUniscribe)
		return NULL;

	//Attempt to create the Uniscribe object, but make sure the
	//OS is valid and that we can load the uniscribe DLL.
	int cScripts;
	//Find out if OS is valid, or if delay-load fails
	if (!IsSupportedOS() || FAILED(ScriptGetProperties(NULL, &cScripts)))
	{
		g_fNoUniscribe = TRUE;
		return NULL;
	}

	if (!g_pusp)
		g_pusp = new CUniscribe();

	AssertSz(g_pusp, "GetUniscribe(): Create Uniscribe object failed");
	return g_pusp;
}
#endif // NOCOMPLEXSCRIPTS

////////////////////////// Notification Manager //////////////////////////////

/*
 *	CTxtEdit::GetNotifyMgr()
 *
 *	@mfunc
 *		returns a pointer to the notification manager (creating it if necessary)
 *
 *	@rdesc
 *		Ptr to notification manager
 */
CNotifyMgr *CTxtEdit::GetNotifyMgr()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetNotifyMgr");

	return &_nm;
}


////////////////////////// Object Manager ///////////////////////////////////

/*
 *	CTxtEdit::GetObjectMgr()
 *
 *	@mfunc
 *		returns a pointer to the object manager (creating if necessary)
 *
 *	@rdesc
 *		pointer to the object manager
 */
CObjectMgr *CTxtEdit::GetObjectMgr()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetObjectMgr");

	if(!_pobjmgr)
		_pobjmgr = new CObjectMgr();

	return _pobjmgr;
}


////////////////////////////// Properties - Selection ////////////////////////////////


LONG CTxtEdit::GetSelMin() const
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSelMin");

	return _psel ? _psel->GetCpMin() : 0;
}

LONG CTxtEdit::GetSelMost() const
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSelMost");

	return _psel ? _psel->GetCpMost() : 0;
}

		
////////////////////////////// Properties - Text //////////////////////////////////////

LONG CTxtEdit::GetTextRange(
	LONG	cpFirst,
	LONG	cch,
	WCHAR *	pch)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetTextRange");

#ifdef DEBUG
	const LONG cchAsk = cch;
#endif
	CTxtPtr	tp(this, cpFirst);
	LONG	cchAdj = GetAdjustedTextLength();

	if(--cch < 0 || cpFirst > cchAdj)
		return 0;

	cch = min(cch, cchAdj - cpFirst);
	if(cch > 0)
	{
		cch = tp.GetPlainText(cch, pch, cpFirst + cch, FALSE, FALSE);
		Assert(cch >= 0);
	}
	pch[cch] = TEXT('\0');

#ifdef DEBUG
	if(cch != cchAsk - 1)
		Tracef(TRCSEVINFO, "CTxtEdit::GetTextRange: only got %ld out of %ld", cch, cchAsk - 1);
#endif

	return cch;
}

/*
 *	CTxtEdit::GetTextEx (pgt, pch)
 *
 *	@mfunc
 *		Grabs text according to various params
 *
 *	@rdesc
 *		Count of bytes gotten
 */
LONG CTxtEdit::GetTextEx(
	GETTEXTEX *pgt,		//@parm Info on what to get
	WCHAR *	   pch)		//@parm Where to put the text
{
	LONG	cb;
	LONG	cch;
	LONG	cchGet = GetAdjustedTextLength();
	LONG	cpMin  = 0;
	LONG	cpMost = tomForward;
	WCHAR *	pchUse = pch;
	CTempWcharBuf twcb;

	if(pgt->flags & GT_SELECTION)			// Get selected text
	{
		cch = GetSel()->GetRange(cpMin, cpMost);
		cchGet = min(cch, cchGet - cpMin);	// Don't include final EOP
	}

	if(pgt->codepage == (unsigned)-1)	// Use default codepage
		pgt->codepage = GetDefaultCodePage(EM_GETTEXTEX);

	if(pgt->cb == (unsigned)-1)			// Client says its buffer is big enuf
	{									
		pgt->cb = cchGet + 1;
		if(W32->IsFECodePage(pgt->codepage) || pgt->codepage == 1200)
			pgt->cb += cchGet;
		else if(pgt->codepage == CP_UTF8 && (_qwCharFlags & ~FASCII))
			pgt->cb *= (_qwCharFlags & FABOVEX7FF) ? 3 : 2;
	}

	// Allocate a big buffer; make sure that we have
	// enough room for lots of CRLFs if necessary
	if(pgt->flags & GT_USECRLF)
		cchGet *= 2;

	if(pgt->codepage != 1200)
	{
		// If UNICODE, copy straight to client's buffer;
		// else, copy to temp buffer and translate cases first
		pchUse = twcb.GetBuf(cchGet + 1);
		if (pch)
			*((char *)pch) = '\0';		// In case something fails
	}
	else						// Be sure to leave room for NULL terminator
		cchGet = min(UINT(pgt->cb/2 - 1), (UINT)cchGet);

	// Now grab the text 
	if(pgt->flags & GT_NOHIDDENTEXT)
	{
		CRchTxtPtr rtp(this, cpMin);
		cch = rtp.GetPlainText(cchGet, pchUse, cpMost, FALSE, pgt->flags & GT_USECRLF);
	}
	else
	{
		CTxtPtr tp(this, cpMin);
		if(pgt->flags & GT_RAWTEXT)
			cch = tp.GetText(cchGet, pchUse);
		else
			cch = tp.GetPlainText(cchGet, pchUse, cpMost, FALSE, pgt->flags & GT_USECRLF);
	}
	pchUse[cch] = L'\0';

	// If we're just doing UNICODE, return number of chars written
	if(pgt->codepage == 1200)
		return cch;

	// Oops, gotta translate to ANSI
	cb = WideCharToMultiByte(pgt->codepage, 0, pchUse, cch + 1, (char *)pch, 
			pgt->cb, pgt->lpDefaultChar, pgt->lpUsedDefChar);

	// Don't count NULL terminator for compatibility with WM_GETTEXT
	return cb ? cb - 1 : 0;
}
			
/*
 *	CTxtEdit::GetTextLengthEx (pgtl)
 *
 *	@mfunc
 *		Calculates text length in various ways.
 *
 *	@rdesc
 *		Text length calculated in various ways
 *
 *	@comm
 *		This function returns an API cp that may differ from the
 *		corresponding internal Unicode cp.
 */
LONG CTxtEdit::GetTextLengthEx(
	GETTEXTLENGTHEX *pgtl)	//@parm Info describing how to calculate length
{
	LONG	cchUnicode = GetAdjustedTextLength();
	LONG	cEOP = 0;
	DWORD	dwFlags = pgtl->flags;
	GETTEXTEX gt;

	if(pgtl->codepage == (unsigned)-1)
		pgtl->codepage = GetDefaultCodePage(EM_GETTEXTLENGTHEX);

	// Make sure the flags are defined appropriately
	if ((dwFlags & GTL_CLOSE)    && (dwFlags & GTL_PRECISE) ||
		(dwFlags & GTL_NUMCHARS) && (dwFlags & GTL_NUMBYTES))
	{
		TRACEWARNSZ("Invalid flags for EM_GETTEXTLENGTHEX");
		return E_INVALIDARG;
	}

	// Note in the following if statement, the second part of the
	// and clause will always be TRUE. At some point in the future
	// fUseCRLF and Get10Mode may become independent, in which case
	// the code below will automatically work without change.
	// NEW with 4.0: 1.0 mode gets text as is, so don't add count for CRs.
	// (RichEdit 1.0 only inserts Enters as CRLFs; it doesn't "cleanse"
	// other text insertion strings)
	if((dwFlags & GTL_USECRLF) && !fUseCRLF() && !Get10Mode())
	{
		// Important facts for 1.0 mode (REMARK: this is out of date):
		//
		// (1) 1.0 mode implies that the text is stored with fUseCRLF true.
		// fUseCRLF means that the EOP mark can either be a CR or a
		// CRLF - see CTxtRange::CleanseAndReplaceRange for details. 
		//
		// (2) 1.0 mode has an invariant that the count of text returned 
		// by this call should be enough to hold all the text returned by
		// WM_GETTEXT.
		//
		// (3) The WM_GETEXT call for 1.0 mode will return a buffer in 
		// which all EOPs that consist of a CR are replaced by CRLF.
		//
		// Therefore, for 1.0 mode, we must count all EOPs that consist
		// of only a CR and add addition return character to count the
		// LF that will be added into any WM_GETEXT buffer.

		// For 2.0 mode, the code is much easier, just count up all
		// CRs and bump count of each one by 1.

		CTxtPtr tp(this, 0);
		LONG	Results;

		while(tp.FindEOP(tomForward, &Results))
		{
			// If EOP consists of 1 char, add 1 since is returned by a CRLF.
			// If it consists of 2 chars, add 0, since it's a CRLF and is
			// returned as such.
			if(tp.GetCp() > cchUnicode)		// Don't add correction for
				break;						//  final CR (if any)
			if (!(Results & FEOP_CELL) &&	// CELL gets xlated into TAB,
				tp.GetPrevChar() != FF)		//  and FF into FF,
			{								//  i.e., single chars
				Results &= 3;				// Get advance cch
				if(Results)
					cEOP += 2 - Results;	// Add in xtra if lone CR or LF	
			}
			AssertSz(IN_RANGE(1, Results & 3, 2) || !Results && tp.GetCp() == cchUnicode,
				"CTxtEdit::GetTextLengthEx: CRCRLF found in backing store");
		}
		cchUnicode += cEOP;
	}

	// If we're just looking for the number of characters or if it's an
	// 8-bit codepage in RE 1.0 mode, we've already got the count.
	if ((dwFlags & GTL_NUMCHARS) || !dwFlags ||
		Get10Mode() && Is8BitCodePage(pgtl->codepage))
	{
		return cchUnicode;
	}

	// Hmm, they're looking for number of bytes, but don't care about 
	// precision, just multiply by two.  If neither PRECISE or CLOSE is
	// specified, default to CLOSE. Note if the codepage is UNICODE and
	// asking for number of bytes, we also just multiply by 2.
	if((dwFlags & GTL_CLOSE) || !(dwFlags & GTL_PRECISE) ||
		pgtl->codepage == 1200)
	{
		return cchUnicode *2;
	}

	// In order to get a precise answer, we need to convert (which is slow!).
	gt.cb = 0;
	gt.flags = (pgtl->flags & GT_USECRLF);
	gt.codepage = pgtl->codepage;
	gt.lpDefaultChar = NULL;
	gt.lpUsedDefChar = NULL;

	return GetTextEx(&gt, NULL);
}

/*
 *	CTxtEdit::GetDefaultCodePage (msg)
 *
 *	@mfunc
 *		Return codepage to use for converting the text in RichEdit20A text
 *		messages.
 *
 *	@rdesc
 *		Codepage to use for converting the text in RichEdit20A text messages.
 */
LONG CTxtEdit::GetDefaultCodePage(
	UINT msg)
{
	LONG CodePage = GetACP();

	// FUTURE: For backward compatibility in Office97, We always use ACP for all these 
	// languages. Need review in the future when the world all moves to Unicode.
	if (W32->IsBiDiCodePage(CodePage) || CodePage == CP_THAI || CodePage == CP_VIETNAMESE || 
		W32->IsFECodePage(CodePage) || _fSingleCodePage || msg == EM_GETCHARFORMAT || 
		msg == EM_SETCHARFORMAT)
	{
		return CodePage;
	}
	
	if(Get10Mode())
		return CodePageFromCharRep(GetCharFormat(-1)->_iCharRep);

	return CodePageFromCharRep(GetKeyboardCharRep());
}

//////////////////////////////  Properties - Formats  //////////////////////////////////

/*
 *	CTxtEdit::HandleStyle (pCFTarget, pCF, dwMask, dwMask2)
 *
 *	@mfunc
 *		If pCF specifies a style choice, initialize pCFTarget with the
 *		appropriate style, apply pCF, and return NOERROR.  Else return
 *		S_FALSE or an error
 *
 *	@rdesc
 *		HRESULT = (pCF specifies a style choice) ? NOERROR : S_FALSE or error code
 */
HRESULT CTxtEdit::HandleStyle(
	CCharFormat *pCFTarget,		//@parm Target CF to receive CF style content
	const CCharFormat *pCF,		//@parm Source CF that may specify a style
	DWORD		 dwMask,		//@parm CHARFORMAT2 mask
	DWORD		 dwMask2)		//@parm Second mask
{
	if(pCF->fSetStyle(dwMask, dwMask2))
	{
		// FUTURE: generalize to use client style if specified
		*pCFTarget = *GetCharFormat(-1);
		pCFTarget->ApplyDefaultStyle(pCF->_sStyle);
		return pCFTarget->Apply(pCF, dwMask, dwMask2);
	}
	return S_FALSE;
}

/*
 *	CTxtEdit::HandleStyle (pPFTarget, pPF)
 *
 *	@mfunc
 *		If pPF specifies a style choice, initialize pPFTarget with the
 *		appropriate style, apply pPF, and return NOERROR.  Else return
 *		S_FALSE or an error
 *
 *	@rdesc
 *		HRESULT = (pPF specifies a style choice) ? NOERROR : S_FALSE or error code
 */
HRESULT CTxtEdit::HandleStyle(
	CParaFormat *pPFTarget,		//@parm Target PF to receive PF style content
	const CParaFormat *pPF,		//@parm Source PF that may specify a style
	DWORD		dwMask,			//@parm Mask to use in setting CParaFormat
	DWORD		dwMask2)		//@parm Mask for internal flags
{
	if(pPF->fSetStyle(dwMask, dwMask2))
	{
		// FUTURE: generalize to use client style if specified
		*pPFTarget = *GetParaFormat(-1);
		pPFTarget->ApplyDefaultStyle(pPF->_sStyle);
		return pPFTarget->Apply(pPF, dwMask, dwMask2);
	}
	return S_FALSE;
}

//////////////////////////// Mouse Commands /////////////////////////////////

HRESULT CTxtEdit::OnTxLButtonDblClk(
	INT		x,			//@parm Mouse x coordinate
	INT		y,			//@parm Mouse y coordinate
	DWORD	dwFlags)	//@parm Mouse message wparam
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxLButtonDblClk");

	BOOL			fEnterParaSelMode = FALSE;
	HITTEST			Hit;
	CTxtSelection *	psel = GetSel();
	const POINT		ptxy = {x, y};
	POINTUV			pt;

	AssertSz(psel, "CTxtEdit::OnTxLButtonDblClk() - No selection object !");

    if (StopMagellanScroll())
        return S_OK;
    
	_dwTickDblClick = GetTickCount();
	_ptDblClick.x = x;
	_ptDblClick.y = y;

	TxUpdateWindow();		// Repaint window to show any exposed portions

	if(!_fFocus)
	{
		TxSetFocus();					// Create and display caret
		return S_OK;
	}

	_pdp->PointuvFromPoint(pt, ptxy);

	// Find out what the cursor is pointing at
	_pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, &Hit); 

	if(Hit == HT_Nothing)
		return S_OK;

	if(Hit == HT_OutlineSymbol)
	{
		CTxtRange rg(*psel);
		rg.ExpandOutline(0, FALSE);
		return S_OK;
	}

	if(Hit == HT_LeftOfText)
		fEnterParaSelMode = TRUE;

	_fWantDrag = FALSE;					// just to be safe

	// If we are over a link, let the client have a chance to process
	// the message
	if(Hit == HT_Link && HandleLinkNotification(WM_LBUTTONDBLCLK, (WPARAM)dwFlags, MAKELPARAM(x, y)))
		return S_OK;

	if(dwFlags & MK_CONTROL)
		return S_OK;

	// Mark mouse down
	_fMouseDown = TRUE;

	if(_pobjmgr && _pobjmgr->HandleDoubleClick(this, pt, dwFlags))
	{
		// The object subsystem handled everything
		_fMouseDown = FALSE;
		return S_OK;
	}

	// Update the selection
	if(fEnterParaSelMode)
		psel->SelectUnit(pt, tomParagraph);
	else
		psel->SelectWord(pt);

	return S_OK;
}

HRESULT CTxtEdit::OnTxLButtonDown(
	INT		x,			//@parm Mouse x coordinate
	INT		y,			//@parm Mouse y coordinate
	DWORD	dwFlags)	//@parm Mouse message wparam
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxLButtonDown");

	BOOL		fEnterLineSelMode = FALSE;
	BOOL		fShift = dwFlags & MK_SHIFT;
	HITTEST		Hit;
	POINTUV		pt;
	const POINT ptxy = {x, y};
	COleObject *pobj;
	BOOL		fMustThaw = FALSE;

	const BOOL fTripleClick = GetTickCount() < _dwTickDblClick + W32->GetDCT() &&
				abs(x - _ptDblClick.x) <= W32->GetCxDoubleClk() &&
				abs(y - _ptDblClick.y) <= W32->GetCyDoubleClk();

    if (StopMagellanScroll())
        return S_OK;

    _pdp->PointuvFromPoint(pt, ptxy);

	// If click isn't inside view, just activate, don't select
	if(!_fFocus)					// Sets focus if not already
	{
		// We may be removing an existing selection, so freeze
		// display to avoid flicker
		_pdp->Freeze();
		fMustThaw = TRUE;
		TxSetFocus();				// creates and displays caret
	}

	// Grab selection object
	CTxtSelection * const psel = GetSel();
	AssertSz(psel,"CTxtEdit::OnTxLButtonDown - No selection object !");

	// Find out what cursor is pointing at
	_pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, &Hit); 

	if(Hit == HT_LeftOfText)
	{
		// Shift click in sel bar treated as normal click
		if(!fShift)
		{
			// Control selbar click and triple selbar click
			// are select all
			if((dwFlags & MK_CONTROL) || fTripleClick)
			{
				psel->SelectAll();
				goto cancel_modes;
			}
			fEnterLineSelMode = TRUE;
			if(!GetAdjustedTextLength() && !_pdp->IsMultiLine())
			{
				const CParaFormat *pPF = psel->GetPF();
				// Can't see selected para mark when flushed right, so
				// leave selection as an insertion point
				if(pPF->_bAlignment == PFA_RIGHT && !pPF->IsRtlPara())
					fEnterLineSelMode = FALSE;
			}
		}
	}
	else if(Hit == HT_Nothing)
		goto cancel_modes;

	else if(!fShift)
		psel->CancelModes();

	// Let client have a chance to handle this message if we are over a link
	if(Hit == HT_Link && HandleLinkNotification(WM_LBUTTONDOWN, (WPARAM)dwFlags, 
			MAKELPARAM(x, y)))
	{
		goto cancel_modes;
	}

	_fMouseDown = TRUE;						// Flag mouse down
	if(!fShift && _pobjmgr)
	{
		// Deactivate anybody active, etc.
		ClickStatus status = _pobjmgr->HandleClick(this, pt);
		if(status == CLICK_OBJSELECTED)
		{
			// The object subsystem will handle resizing.
			// if not a resize we will signal start of drag
			pobj = _pobjmgr->GetSingleSelect();

			// Because HandleClick returned true, pobj better be non-null.
			Assert(pobj);

            if (!pobj->HandleResize(ptxy))
				_fWantDrag = !_fDisableDrag;

			goto cancel_modes;
		}
		else if(status == CLICK_OBJDEACTIVATED)
			goto cancel_modes;
	}

	_fCapture = TRUE;						// Capture the mouse
	TxSetCapture(TRUE);

	// Check for start of drag and drop
	if(!fTripleClick && !fShift && psel->PointInSel(pt, NULL, Hit) && !_fDisableDrag)
	{	    
		// Assume we want a drag. If we don't CmdLeftUp() needs
		//  this to be set anyway to change the selection
		_fWantDrag = TRUE;
		goto cancel_modes;
	}

	if(fShift)								// Extend selection from current
	{										//  active end to click
		psel->InitClickForAutWordSel(pt);
		psel->ExtendSelection(pt);			
	}
	else if(fEnterLineSelMode)				// Line selection mode: select line
		psel->SelectUnit(pt, tomLine);
	else if(fTripleClick || Hit == HT_OutlineSymbol) // paragraph selection mode
		psel->SelectUnit(pt, tomParagraph);
	else
	{
	    if (Get10Mode())
	        _f10DeferChangeNotify = 1;
		psel->SetCaret(pt);
		_mousePt = ptxy;
	}
        
	if(fMustThaw)
		_pdp->Thaw();

	return S_OK;

cancel_modes:
	psel->CancelModes();

	if(_fWantDrag)
	{
		TxSetTimer(RETID_DRAGDROP, W32->GetDragDelay());
		_mousePt = ptxy;
		_bMouseFlags = (BYTE)dwFlags;
		_fDragged = FALSE;
	}

	if(fMustThaw)
		_pdp->Thaw();
		
	return S_OK;
}

HRESULT CTxtEdit::OnTxLButtonUp(
	INT		x,				//@parm Mouse x coordinate
	INT		y,				//@parm Mouse y coordinate
	DWORD	dwFlags,		//@parm Mouse message wparam
	int	    ffOptions)      //@parm Mouse options, see _edit.h for details
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxLButtonUp");
    
	CheckRemoveContinuousScroll();

	// Remove capture before test for mouse down since we wait till
	// we get the mouse button up message to release capture since Forms
	// wants it that way.
	if(_fCapture && (ffOptions & LB_RELEASECAPTURE))
	{
		TxSetCapture(FALSE);
		_fCapture = FALSE;
	}

	// We were delaying selection change.  So send it now...
    if (DelayChangeNotification() && (ffOptions & LB_FLUSHNOTIFY))
    {
        AssertSz(Get10Mode(), "Flag should only be set in 10 mode");
        _f10DeferChangeNotify = 0;
        GetCallMgr()->SetSelectionChanged();        
    }

	if(!_fMouseDown)
	{
		// We noticed the mouse was no longer down earlier so we don't
		// need to do anything.
		return S_OK;
	}

	const BOOL fSetSel = !!_fWantDrag;
	const POINT ptxy = {x, y};
	POINTUV	pt;
	_pdp->PointuvFromPoint(pt, ptxy);

	// Cancel Auto Word Sel if on
	CTxtSelection * const psel = GetSel();
	AssertSz(psel,"CTxtEdit::OnLeftUp() - No selection object !");

	psel->CancelModes(TRUE);

	// Reset flags
	_fMouseDown = FALSE;
	_fWantDrag = FALSE;
	_fDragged = FALSE;
	TxKillTimer(RETID_DRAGDROP);
	if(IsInOutlineView())
		psel->Update(FALSE);

	// Let the client handle this message if we are over a
	// link area
	if(HandleLinkNotification(WM_LBUTTONUP, (WPARAM)dwFlags, 
			MAKELPARAM(x, y)))
	{
		return NOERROR;
	}

	// If we were in drag & drop, put caret under mouse
	if(fSetSel)
	{
		CObjectMgr* pobjmgr = GetObjectMgr();

		// If we were on an object, don't deselect it by setting the caret
		if(pobjmgr && !pobjmgr->GetSingleSelect())
		{
			psel->SetCaret(pt, TRUE);
			if(!_fFocus)
				TxSetFocus();		// create and display caret
		}
	}
	return S_OK;
}

HRESULT CTxtEdit::OnTxRButtonUp(
	INT		x,			//@parm Mouse x coordinate
	INT		y,			//@parm Mouse y coordinate
	DWORD	dwFlags,	//@parm Mouse message wparam
	int     ffOptions)  //@parm option flag
{
	const POINT ptxy = {x, y};
	POINTUV		pt;
	CTxtSelection * psel;
	SELCHANGE selchg;
	HMENU hmenu = NULL;
	IOleObject * poo = NULL;
	COleObject * pobj = NULL;
	IUnknown * pUnk = NULL;
	IRichEditOleCallback * precall = NULL;
	_pdp->PointuvFromPoint(pt, ptxy);

	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxRButtonUp");

	// Make sure we have the focus
	if(!_fFocus)
		TxSetFocus();

	if(_fWantDrag)
	{
		_fDragged = FALSE;
		_fWantDrag = FALSE;
		TxKillTimer(RETID_DRAGDROP);
	}
		
	// Grab selection object
	psel = GetSel();
	psel->SetSelectionInfo(&selchg);

	// We need a pointer to the first object, if any, in the selection.
	if(_pobjmgr)
	{
		//If the point is in the selection we need to find out if there
		//are any objects in the selection.  If the point is not in a
		//selection but it is on an object, we need to select the object.
		if(psel->PointInSel(pt) || (ffOptions & RB_FORCEINSEL))
		{
			pobj = _pobjmgr->GetFirstObjectInRange(selchg.chrg.cpMin,
				selchg.chrg.cpMost);
		}
		else
		{
			//Select the object
			if(_pobjmgr->HandleClick(this, pt) == CLICK_OBJSELECTED)
			{
				pobj = _pobjmgr->GetSingleSelect();
				// Because HandleClick returned true, pobj better be non-null.
				Assert(pobj!=NULL);
				//Refresh our information about the selection
				psel = GetSel();
				psel->SetSelectionInfo(&selchg);
			}
		}
		precall = _pobjmgr->GetRECallback();
	}

	if(pobj)
		pUnk = pobj->GetIUnknown();

	if(pUnk)
		pUnk->QueryInterface(IID_IOleObject, (void **)&poo);

	if(precall)
		precall->GetContextMenu(selchg.seltyp, poo, &selchg.chrg, &hmenu);

	if(hmenu)
	{
		HWND hwnd, hwndParent;
		POINT ptscr;

		if(TxGetWindow(&hwnd) == NOERROR)
		{
			if(!(ffOptions & RB_NOSELCHECK) && !psel->PointInSel(pt) && 
				!psel->GetCch() && !(ffOptions & RB_FORCEINSEL))
				psel->SetCaret(pt);
			ptscr.x = ptxy.x;
			ptscr.y = ptxy.y;
			ClientToScreen(hwnd, &ptscr);

			hwndParent = GetParent(hwnd);
			if(!hwndParent)
				hwndParent = hwnd;

			TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON,
				ptscr.x, ptscr.y, 0, hwndParent, NULL);
		}
		DestroyMenu(hmenu);
	}

	if(poo)
		poo->Release();
	
	return precall ? S_OK : S_FALSE;
}

HRESULT CTxtEdit::OnTxRButtonDown(
	INT		x,			//@parm Mouse x coordinate
	INT		y,			//@parm Mouse y coordinate
	DWORD	dwFlags)	//@parm Mouse message wparam
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxRButtonDown");

    if (StopMagellanScroll())
        return S_OK;
    
	CTxtSelection *	psel = GetSel();
	const POINT		ptxy = {x, y};
	POINTUV			pt;

	_pdp->PointuvFromPoint(pt, ptxy);
	psel->CancelModes();

	if(psel->PointInSel(pt) && !_fDisableDrag)
	{
		_fWantDrag = TRUE;

		TxSetTimer(RETID_DRAGDROP, W32->GetDragDelay());
		_mousePt = ptxy;
		_bMouseFlags = (BYTE)dwFlags;
		_fDragged = FALSE;
		return S_OK;
	}
	return S_FALSE;
}

HRESULT CTxtEdit::OnTxMouseMove(
	INT		x,				//@parm Mouse x coordinate
	INT		y,				//@parm Mouse y coordinate
	DWORD	dwFlags,		//@parm Mouse message wparam
	IUndoBuilder *publdr)	//@parm Undobuilder to receive antievents
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxMouseMove");

	if(!_fFocus)
		return S_OK;

	RECT rcxy;
	TxGetClientRect(&rcxy);
	if(_fWantDrag || _fCapture)
	{
		LONG nDragMinDist = W32->GetDragMinDist() + 3;
		int  dx = _mousePt.x > x ? _mousePt.x - x : x - _mousePt.x;
		int  dy = _mousePt.y > y ? _mousePt.y - y : y - _mousePt.y;

		if(dx < nDragMinDist && dy < nDragMinDist)
		{
			if(!_fCapture || x > 0 && x < rcxy.right && y > 0 && y < rcxy.bottom)
			{
				_bMouseFlags = (BYTE)dwFlags;
				return S_OK;
			}
		}
		_fDragged = _fWantDrag;
	}
	_mousePt.x = x;									// Remember for scrolling
	_mousePt.y = y;									//  speed, and dir calc.

	// RichEdit 1.0 allows the client to process mouse moves itself if
	// we are over a link (but _not_ doing drag drop).
	if(HandleLinkNotification(WM_MOUSEMOVE, 0, MAKELPARAM(x, y)))
		return NOERROR;

	// If we think mouse is down and it really is, do special processing
	if (GetAsyncKeyState(VK_LBUTTON) < 0 ||
		GetAsyncKeyState(VK_RBUTTON) < 0)
	{
		CTxtSelection * const psel = GetSel();
		AssertSz(psel,"CTxtEdit::OnMouseMove: No selection object !");

		if(_fWantDrag && !_fUsePassword &&
		   !IsProtected(_fReadOnly ? WM_COPY : WM_CUT, dwFlags, MAKELONG(x,y)))
		{
			TxKillTimer(RETID_DRAGDROP);
			_ldte.StartDrag(psel, publdr);
			// The mouse button may still be down, but drag drop is over
			// so we need to _think_ of it as up.
			_fMouseDown = FALSE;

			// Similarly, OLE should have nuked the capture for us, but
			// just in case something failed, release the capture.
			TxSetCapture(FALSE);
			_fCapture = FALSE;
		}
		else if(_fMouseDown)						// We think mouse is down
		{											//  and it is
			POINTUV pt;
			POINT	ptxy = {x, y};
			if(x >= rcxy.right && x < rcxy.right + 5)
				ptxy.x += 5;
			_pdp->PointuvFromPoint(pt, ptxy);
			if(_ldte.fInDrag())						// Only drag scroll if a drag
				_pdp->DragScroll(&_mousePt);		//  operation is in progress
			psel->ExtendSelection(pt);				// Extend the selection
			CheckInstallContinuousScroll();			// Install srolling timer
		}
	}
#ifndef NOMAGELLAN
	else if (!(GetAsyncKeyState(VK_MBUTTON) < 0) && !mouse.IsAutoScrolling())
	{
		if(_fMButtonCapture)						// Ensure we aren't autoscrolling
			OnTxMButtonUp (x, y, dwFlags);			//  via intellimouse

		if(_fMouseDown)
		{
			// Although we thought the mouse was down, at this moment it
			// clearly is not. Therefore, we pretend we got a mouse up
			// message and clear our state to get ourselves back in sync 
			// with what is really happening.
			OnTxLButtonUp(x, y, dwFlags, LB_RELEASECAPTURE);
		}
	}
#endif

	// Either a drag was started or the mouse button was not down. In either
	// case, we want no longer to start a drag so we set the flag to false.
	_fWantDrag = FALSE;
	return S_OK;
}

/*
 *	OnTxMButtonDown (x, y, dwFlags)
 *
 *	@mfunc
 *		The user pressed the middle mouse button, setup to do
 *		continuous scrolls, which may in turn initiate a timer
 *		for smooth scrolling.
 *
 *	@rdesc
 *		HRESULT = S_OK
 */
HRESULT CTxtEdit::OnTxMButtonDown (
	INT		x,			//@parm Mouse x coordinate
	INT		y,			//@parm Mouse y coordinate
	DWORD	dwFlags)	//@parm Mouse message wparam
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxMButtonDown");

#if !defined(NOMAGELLAN)
	POINT	pt = {x,y};

	if(!_fFocus)
		TxSetFocus();
		
	if(!StopMagellanScroll() && mouse.MagellanStartMButtonScroll(*this, pt))
	{
		TxSetCapture(TRUE);

		_fCapture			= TRUE;							// Capture the mouse
		_fMouseDown			= TRUE;
		_fMButtonCapture	= TRUE;
	}
#endif

	return S_OK;
}

/*
 *	CTxtEdit::OnTxMButtonUp (x, y, dwFlags)
 *
 *	@mfunc
 *		Remove timers and capture associated with a MButtonDown
 *		message.
 *
 *	@rdesc
 *		HRESULT = S_OK
 */
HRESULT CTxtEdit::OnTxMButtonUp (
	INT		x,			//@parm Mouse x coordinate
	INT		y,			//@parm Mouse y coordinate
	DWORD	dwFlags)	//@parm Mouse message wparam
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxMButtonUp");

#if !defined(NOMAGELLAN)
    if (mouse.ContinueMButtonScroll(this, x, y))
        return S_OK;

    StopMagellanScroll();
    
#else

	if(_fCapture)
		TxSetCapture(FALSE);

	_fCapture			= FALSE;
	_fMouseDown			= FALSE;
	_fMButtonCapture	= FALSE;
	
#endif

	return S_OK;
}


/*
 *	CTxtEdit::StopMagellanScroll()
 *
 *	@mfunc
 *		Stops the intellimouse autoscrolling and returns
 *      us back into a normal state
 *
 *  BOOL = TRUE if auto scrolling was turned off : FALSE
 *          Autoscrolling was never turned on
 */
 BOOL CTxtEdit::StopMagellanScroll ()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::StopMagellanScroll");

#if !defined(NOMAGELLAN)
    if (!mouse.IsAutoScrolling())
        return FALSE;
        
	mouse.MagellanEndMButtonScroll(*this);

	if(_fCapture)
		TxSetCapture(FALSE);

	_fCapture			= FALSE;
	_fMouseDown			= FALSE;
	_fMButtonCapture	= FALSE;
	return TRUE;
#else
    return FALSE;
#endif
}


/*
 *	CTxtEdit::CheckInstallContinuousScroll ()
 *
 *	@mfunc
 *		There are no events that inform the app on a regular
 *		basis that a mouse button is down. This timer notifies
 *		the app that the button is still down, so that scrolling can
 *		continue.
 */
void CTxtEdit::CheckInstallContinuousScroll ()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CheckInstallContinuousScroll");

	if(!_fContinuousScroll && TxSetTimer(RETID_AUTOSCROLL, cmsecScrollInterval))
		_fContinuousScroll = TRUE;
}

/*
 *	CTxtEdit::CheckRemoveContinuousScroll ()
 *
 *	@mfunc
 *		The middle mouse button, or drag button, is up
 *		remove the continuous scroll timer.
 */
void CTxtEdit::CheckRemoveContinuousScroll ()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CheckRemoveContinuousScroll");

	if(_fContinuousScroll)
	{
		TxKillTimer(RETID_AUTOSCROLL);
		_fContinuousScroll = FALSE;
	}
}

/*
 *	OnTxTimer(idTimer)
 *
 *	@mfunc
 *		Handle timers for doing background recalc and scrolling.
 *
 *	@rdesc
 *		HRESULT = (idTimer valid) ? S_OK : S_FALSE
 */
HRESULT CTxtEdit::OnTxTimer(
	UINT idTimer)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxTimer");

	switch (idTimer)
	{
		case RETID_BGND_RECALC:
			_pdp->StepBackgroundRecalc();
			break;

#if !defined(NOMAGELLAN)
		case RETID_MAGELLANTRACK:
			mouse.TrackUpdateMagellanMButtonDown(*this, _mousePt);
			break;
#endif
		case RETID_AUTOSCROLL:						// Continuous scrolling.
			OnTxMouseMove(_mousePt.x, _mousePt.y,	// Do a select drag scroll.
						  0, NULL);
			break;

#if !defined(NOMAGELLAN)
		case RETID_SMOOTHSCROLL:					// Smooth scrolling
			if(_fMButtonCapture)					// HACK, only 1 timer!
			{										// delivered on Win95
													// when things get busy.
				mouse.TrackUpdateMagellanMButtonDown(*this, _mousePt);	
			}
			if(_pdp->IsSmoothVScolling())			// Test only because of
				_pdp->SmoothVScrollUpdate();		//  above HACK!!
		break;
#endif
		case RETID_DRAGDROP:
			TxKillTimer(RETID_DRAGDROP);
			if (_fWantDrag && _fDragged && !_fUsePassword &&
				!IsProtected(_fReadOnly ? WM_COPY : WM_CUT,
				             _bMouseFlags, MAKELONG(_mousePt.x,_mousePt.y)))
			{
				IUndoBuilder *	publdr;
				CGenUndoBuilder undobldr(this, UB_AUTOCOMMIT, &publdr);
				_ldte.StartDrag(GetSel(), publdr);
				_fWantDrag = FALSE;
				_fDragged = FALSE;
				TxSetCapture(FALSE);
				_fCapture = FALSE;
			}
			break;

		default:
			return S_FALSE;
	}
	return S_OK;
}


/////////////////////////// Keyboard Commands ////////////////////////////////

/*
 *	CTxtEdit::OnTxKeyDown(vkey, dwFlags, publdr)
 *
 *	@mfunc
 *		Handle WM_KEYDOWN message
 *
 *	@rdesc
 *		HRESULT with the following values:
 *
 *		S_OK				if key was understood and consumed
 *		S_MSG_KEY_IGNORED	if key was understood, but not consumed
 *		S_FALSE				if key was not understood or just looked at
 *									and in any event not consumed
 */
HRESULT CTxtEdit::OnTxKeyDown(
	WORD		  vkey,		//@parm Virtual key code
	DWORD		  dwFlags,	//@parm lparam of WM_KEYDOWN msg
	IUndoBuilder *publdr)	//@parm Undobuilder to receive antievents
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxKeyDown");

	if(IN_RANGE(VK_SHIFT, vkey, VK_MENU))
	{
		SetKeyboardFlag(GetKbdFlags(vkey, dwFlags));
		return S_FALSE;
	}

	BOOL  fAlt	 = GetKeyboardFlag(ALT, VK_MENU);
	BOOL  fCtrl  = GetKeyboardFlag(CTRL, VK_CONTROL);
	BOOL  fShift = GetKeyboardFlag(SHIFT, VK_SHIFT);

	BOOL  fRet	 = FALSE;			// Converted to HRESULT on return
	LONG  nDeadKey = 0;

	if(fCtrl & fShift)						// Signal NonCtrl/Shift keydown
		SetKeyboardFlag(LETAFTERSHIFT);		//  while Ctrl&Shift are down

	// Handle Hebrew caps and LRM/RLM
#ifndef NOCOMPLEXSCRIPTS
	if (IsBiDi())
	{
		if(IsBiDiCharRep(GetKeyboardCharRep(0xFFFFFFFF)))
		{
			_fHbrCaps = FALSE;
			if(IsRich() && W32->UsingHebrewKeyboard())
			{
				WORD wCapital = GetKeyState(VK_CAPITAL);
				_fHbrCaps = ((wCapital & 1) ^ fShift) &&
							!(wCapital & 0x0080) &&
					     	IN_RANGE('A', vkey, 'Z');
				if(_fHbrCaps)
					W32->ActivateKeyboard(ANSI_INDEX);
	        }
		}
		
		if(vkey == VK_BACK && fShift && W32->OnWin9x())
		{
			// Shift+Backspace generates a LRM | RLM on a BiDi keyboard.
			// Consequently, we must eat the Backspace lest it delete text.
			W32->_fLRMorRLM = 1;
			return S_OK;
		}
	}
#endif

	// If dragging or Alt key down, just look for ESCAPE. Note: if Alt key is
	// down, we should never come here (would generate WM_SYSKEYDOWN message).
	if(_fMouseDown)
	{	    
		if(vkey == VK_ESCAPE)
		{
		    // Turn-off autoscroll.
		    if (StopMagellanScroll())
		        return S_OK;
		    
			POINT pt;
			// Cancel drag select or drag & drop
			GetCursorPos(&pt);
			OnTxLButtonUp(pt.x, pt.y, 0, LB_RELEASECAPTURE | LB_FLUSHNOTIFY);
			return S_OK;
		}
		return OnTxSpecialKeyDown(vkey, dwFlags, publdr);
	}
	
	CTxtSelection * const psel = GetSel();
	AssertSz(psel,"CTxtEdit::OnKeyDown() - No selection object !");

	if(fCtrl)
	{
		if(OnTxSpecialKeyDown(vkey, dwFlags, publdr) == S_OK)
			return S_OK;

		if(fAlt)						// This following code doesn't handle
			return S_FALSE;				//  use Ctrl+Alt, which happens for
										//  AltGr codes (no WM_SYSKEYDOWN)

		// Shift must not be pressed for these.
		if(!fShift)
		{
			switch(vkey)
			{
			case 'E':
			case 'J':
			case 'R':
			case 'L':
			{
				if(!IsRich() || !IsntProtectedOrReadOnly(WM_KEYDOWN, vkey, dwFlags))
					return S_FALSE;

				CParaFormat PF;
				PF._bAlignment = PFA_LEFT;
				if (vkey == 'E')
					PF._bAlignment = PFA_CENTER;
				else if (vkey == 'J')
					PF._bAlignment = PFA_FULL_INTERWORD;
				else if (vkey == 'R')
					PF._bAlignment = PFA_RIGHT;
					
				psel->SetParaFormat(&PF, publdr, PFM_ALIGNMENT,	PFM2_PARAFORMAT);
				break;
			}
			case '1':
			case '2':
			case '5':
			{
				if(!IsRich() || !IsntProtectedOrReadOnly(WM_KEYDOWN, vkey, dwFlags))
					return S_FALSE;

				CParaFormat PF;
				PF._bLineSpacingRule = tomLineSpaceMultiple;
				PF._dyLineSpacing = (vkey - '0') * 20;
				if (vkey == '5')
					PF._dyLineSpacing = 30;

				psel->SetParaFormat(&PF, publdr, PFM_LINESPACING, 0);				
				break;
			}
			default:
				break;
			}
		}

		switch(vkey)
		{
		case VK_TAB:
			return OnTxChar(VK_TAB, dwFlags, publdr);

		case VK_CLEAR:
		case VK_NUMPAD5:
		case 'A':						// Ctrl-A => pselect all
			psel->SelectAll();
			break;

		//Toggle Subscript
		case 187: // = 
		{
			if(!IsRich())
				return S_FALSE;
			ITextFont *pfont;
			psel->GetFont(&pfont);
			if (pfont)
			{
				pfont->SetSubscript(tomToggle);
				pfont->Release();
			}
		}
		break;

		case 'C':						// Ctrl-C => copy
CtrlC:		CutOrCopySelection(WM_COPY, 0, 0, NULL);
			break;

		case 'V':						// Ctrl-V => paste
CtrlV:		if(IsntProtectedOrReadOnly(WM_PASTE, 0, 0))
			{
				PasteDataObjectToRange(NULL, (CTxtRange *)psel, 0, NULL, 
					publdr, PDOR_NONE);
			}
			break;

		case 'X':						// Ctrl-X => cut
CtrlX:		CutOrCopySelection(WM_CUT, 0, 0, publdr);
			break;

		case 'Z':						// Ctrl-Z => undo
			PopAndExecuteAntiEvent(_pundo, 0);
			break;

		case 'Y':						// Ctrl-Y => redo
			PopAndExecuteAntiEvent(_predo, 0);
			break;

#if defined(DEBUG) && !defined(NOFULLDEBUG)
			void RicheditDebugCentral(void);
		case 191:
			RicheditDebugCentral();
			break;
#endif

#if defined(DOGFOOD)
		case '1':						// Shift+Ctrl+1 => start Aimm
			// Activate AIMM by posting a message to RE (Shift+Ctrl+; for now)
			if (fShift && _fInOurHost)
			{
				HWND	hWnd;

				TxGetWindow( &hWnd );

				if (hWnd)
					PostMessage(hWnd, EM_SETEDITSTYLE, SES_USEAIMM, SES_USEAIMM);
			}
			break;
#endif

		case VK_CONTROL:
			goto cont;

// English keyboard defines
#define VK_APOSTROPHE	0xDE
#define VK_GRAVE		0xC0
#define VK_SEMICOLON	0xBA
#define VK_COMMA		0xBC
#define VK_HYPHEN		0xBD

		// REVIEW: restrict VK_HYPHEN to English keyboard?
		case VK_HYPHEN:
			return OnTxChar(fShift ? NBHYPHEN : SOFTHYPHEN, dwFlags, publdr);

		case VK_SPACE:
			if(!fShift)
				goto cont;
			return OnTxChar(NBSPACE, dwFlags, publdr);

		case VK_APOSTROPHE:
			if(fShift)
				g_wFlags ^= KF_SMARTQUOTES;
			else
				nDeadKey = ACCENT_ACUTE;
			break;

		case VK_GRAVE:
			nDeadKey = fShift ? ACCENT_TILDE : ACCENT_GRAVE;
			break;

		case VK_SEMICOLON:
			nDeadKey = ACCENT_UMLAUT;
			break;

		case '6':
			if(!fShift)
				goto cont;
			nDeadKey = ACCENT_CARET;
			break;

		case VK_COMMA:
			nDeadKey = ACCENT_CEDILLA;
			break;

		default:
			goto cont;
		}
		if(nDeadKey)
		{
			// Since deadkey choices vary a bit according to keyboard, we
			// only enable them for English. French, German, Italian, and
			// Spanish keyboards already have a fair amount of accent
			// capability.
			if(PRIMARYLANGID(GetKeyboardLayout(0)) == LANG_ENGLISH)
				SetDeadKey((WORD)nDeadKey);
			else goto cont;
		}
		return S_OK;
	}

cont:	
	switch(vkey)
	{
	case VK_BACK:
	case VK_F16:
		if(_fReadOnly)
		{	
			Beep();
			fRet = TRUE;
		}
		else if(IsntProtectedOrReadOnly(WM_KEYDOWN, VK_BACK, dwFlags))
		{
			fRet = psel->Backspace(fCtrl, publdr);
		} 
		break;

	case VK_INSERT:								// Ins
		if(fShift)								// Shift-Ins
			goto CtrlV;							// Alias for Ctrl-V
		if(fCtrl)								// Ctrl-Ins
			goto CtrlC;							// Alias for Ctrl-C

		if(!_fReadOnly)							// Ins
			_fOverstrike = !_fOverstrike;		// Toggle Ins/Ovr
		fRet = TRUE;
		break;

	case VK_LEFT:								// Left arrow
	case VK_RIGHT:								// Right arrow
		fRet = (vkey == VK_LEFT) ^ (psel->GetPF()->IsRtlPara() != 0)
			 ? psel->Left (fCtrl, fShift)
			 : psel->Right(fCtrl, fShift);
		break;

	case VK_UP:									// Up arrow
		fRet = psel->Up(fCtrl, fShift);
		break;

	case VK_DOWN:								// Down arrow
		fRet = psel->Down(fCtrl, fShift);
		break;

	case VK_HOME:								// Home
		fRet = psel->Home(fCtrl, fShift);
		break;

	case VK_END:								// End
		fRet = psel->End(fCtrl, fShift);
		break;

	case VK_PRIOR:								// PgUp
		// If SystemEditMode and control is single-line, do nothing
		if(!_fSystemEditMode || _pdp->IsMultiLine())
			fRet = psel->PageUp(fCtrl, fShift);
		break;

	case VK_NEXT:								// PgDn
		// If SystemEditMode and control is single-line, do nothing
		if(!_fSystemEditMode || _pdp->IsMultiLine())
			fRet = psel->PageDown(fCtrl, fShift);
		break;

	case VK_DELETE:								// Del
		if(fShift)								// Shift-Del
			goto CtrlX;							// Alias for Ctrl-X

		if(IsntProtectedOrReadOnly(WM_KEYDOWN, VK_DELETE, dwFlags))
			psel->Delete(fCtrl, publdr);
		fRet = TRUE;
		break;

	case CONTROL('J'):							// Ctrl-Return gives Ctrl-J
	case VK_RETURN:								//  (LF), treat it as return
		// If we are in 1.0 mode we need to handle <CR>'s on WM_CHAR
		if(!Get10Mode())
		{
			fRet = InsertEOP(dwFlags, fShift, publdr);
			if(!fRet)
				return S_FALSE;
		}
		break;

	default:
		return S_FALSE;
	}

	return fRet ? S_OK : S_MSG_KEY_IGNORED;
}

/*
 *	CTxtEdit::InsertEOP(dwFlags, fShift, publdr)
 *
 *	@mfunc
 *		Handle inserting EOPs with check for hyperlinks
 *
 *	@rdesc
 *		HRESULT
 */
BOOL CTxtEdit::InsertEOP(
	DWORD		  dwFlags,	//@parm lparam of WM_KEYDOWN msg
	BOOL		  fShift,	//@parm TRUE if Shift key depressed
	IUndoBuilder *publdr)	//@parm Undobuilder to receive antievents
{ 
	if(!_pdp->IsMultiLine())
	{
		if (!_fSystemEditMode)
			Beep();
		return FALSE;
	}
	TxSetCursor(0, NULL);

	BOOL fInLink = FALSE;
	if(!fShift)
		HandleLinkNotification(WM_CHAR, 0, 0, &fInLink);

	if(!fInLink && IsntProtectedOrReadOnly(WM_CHAR, VK_RETURN, dwFlags))
		_psel->InsertEOP(publdr, (fShift && IsRich() ? VT : 0));

	return TRUE;
}

/*
 *	CTxtEdit::CutOrCopySelection(msg, wparam, lparam, publdr)
 *
 *	@mfunc
 *		Handle WM_COPY message and its keyboard hotkey aliases
 *
 *	@rdesc
 *		HRESULT
 */
HRESULT CTxtEdit::CutOrCopySelection(
	UINT   msg,				//@parm Message (WM_CUT or WM_COPY)
	WPARAM wparam,			//@parm Message wparam for protection check
	LPARAM lparam,			//@parm Message lparam for protection check
	IUndoBuilder *publdr)	//@parm Undobuilder to receive antievents
{
	Assert(msg == WM_CUT || msg == WM_COPY);

	if(!_fUsePassword && IsntProtectedOrReadOnly(msg, wparam, lparam))
	{
		CTxtSelection *psel = GetSel();
		BOOL fCopy = msg == WM_COPY;
		LONG lStreamFormat = psel->CheckTableSelection(fCopy, TRUE, NULL, RR_NO_LP_CHECK)
						   ? SFF_WRITEXTRAPAR : 0;
		return fCopy
			? _ldte.CopyRangeToClipboard((CTxtRange *)psel, lStreamFormat)
			: _ldte.CutRangeToClipboard((CTxtRange *)psel, lStreamFormat, publdr);
	}
	return NOERROR;
}

#define ENGLISH_UK	 MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK)
#define ENGLISH_EIRE MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_EIRE)

/*
 *	CTxtEdit::OnTxSpecialKeyDown(vkey, dwFlags, publdr)
 *
 *	@mfunc
 *		Handle WM_KEYDOWN message for outline mode
 *
 *	@rdesc
 *		HRESULT with the following values:
 *
 *		S_OK				if key was understood and consumed
 *		S_MSG_KEY_IGNORED	if key was understood, but not consumed
 *		S_FALSE				if key was not understood (and not consumed)
 */
HRESULT CTxtEdit::OnTxSpecialKeyDown(
	WORD		  vkey,				//@parm Virtual key code
	DWORD		  dwFlags,			//@parm lparam of WM_KEYDOWN msg
	IUndoBuilder *publdr)			//@parm Undobuilder to receive antievents
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxSpecialKeyDown");

	HRESULT	hr = S_FALSE;					// Key not understood yet
	DWORD	dwKbdFlags = GetKeyboardFlags();
	BOOL	fUpdateFormat = TRUE;

	if(!(dwKbdFlags & (CTRL | ALT)))		// All hot keys here have at
		return S_FALSE;						//  least Ctrl or Alt

	if(((dwKbdFlags & ALT) || vkey != 'C' && vkey != 'V' && vkey != 'X') &&
	   !IsntProtectedOrReadOnly(WM_KEYDOWN, VK_BACK, dwFlags, FALSE))
	{
		return S_FALSE;
	}

	CTxtSelection * const psel = GetSel();
	if(dwKbdFlags & ALT && dwKbdFlags & CTRL)
	{
		// AltGr generates LCTRL | RALT, so don't match hot keys with
		// that combination
		if(dwKbdFlags & LCTRL && dwKbdFlags & RALT)
			return S_FALSE;

		if(vkey == 'E')
		{
			LANGID lid = LANGIDFROMLCID(GetKeyboardLayout(0));
			static const LANGID rgLangID[] =
			{
				ENGLISH_UK, ENGLISH_EIRE, LANG_POLISH, LANG_PORTUGUESE,
				LANG_HUNGARIAN, LANG_VIETNAMESE
			};
			for(LONG i = ARRAY_SIZE(rgLangID); i--; )
			{
				// Don't insert Euro if lid matches any LIDs or PLIDs in rgLangID
				if(lid == rgLangID[i] || PRIMARYLANGID(lid) == rgLangID[i])
					return S_FALSE;
			}
			if(psel->PutChar(EURO, _fOverstrike, publdr))
			{
				SetKeyboardFlag(HOTEURO);	// Setup flag to eat the next WM_CHAR w/ EURO
                hr = S_OK;
			}
		}
		else if(dwKbdFlags & SHIFT)
			switch(vkey)
			{
#ifdef ENABLE_OUTLINEVIEW
			// FUTURE: OutlineView hot keys postponed (see below)
			case 'N':						// Alt-Ctrl-N => Normal View
				hr = SetViewKind(VM_NORMAL);
				break;	
			case 'O':						// Alt-Ctrl-O => Outline View
				hr = SetViewKind(VM_OUTLINE);
				break;
#endif
			case VK_F12:					// Shift-Alt-Ctrl-F12 (in case Alt-X taken)
				hr = psel->HexToUnicode(publdr);
				break;

	#if defined(DEBUG) && !defined(NOFULLDEBUG)
			case VK_F10:					// Shift-Alt-Ctrl-F10
				OnDumpPed();
				break;

			case VK_F11:					// Shift-Alt-Ctrl-F11
				if (W32->fDebugFont())
					psel->DebugFont();
				break;
	#endif
			}
		return hr;
	}

	AssertSz(psel, "CTxtEdit::OnTxSpecialKeyDown() - No selection object !");
	CTxtRange rg(*psel);

	if(!IsRich() || !_pdp->IsMultiLine() || !(dwKbdFlags & SHIFT))
		return S_FALSE;

	if(dwKbdFlags & ALT)							// Alt+Shift hot keys
	{
		// NB: Alt and Shift-Alt with _graphics_ characters generate a
		// WM_SYSCHAR, which see

#ifdef ENABLE_OUTLINEVIEW
		// FUTURE: These are Outline related hot keys.  We will postpone these features
		// since we have several bugs related to these hot keys
		// Bug 5687, 5689, & 5691		
		switch(vkey)
		{
		case VK_LEFT:								// Left arrow
		case VK_RIGHT:								// Right arrow
			hr = rg.Promote(vkey == VK_LEFT ? 1 : -1, publdr);
			psel->Update_iFormat(-1);
			psel->Update(FALSE);
			break;

		case VK_UP:									// Up arrow
		case VK_DOWN:								// Down arrow
			hr = MoveSelection(vkey == VK_UP ? -1 : 1, publdr);
			psel->Update(TRUE);
			break;
		}
#endif
		return hr;
	}

	Assert(dwKbdFlags & CTRL && dwKbdFlags & SHIFT);

	// Ctrl+Shift hot keys
	switch(vkey)
	{

#ifdef ENABLE_OUTLINEVIEW
	// FUTUTRE: These are Outline related hot keys.  We will postpone these features
	// since we have several bugs related to these hot keys
	// Bug 5687, 5689, & 5691	
	case 'N':						// Demote to Body
		hr = rg.Promote(0, publdr);
		break;
#endif

	//Toggle superscript
	case 187: // = 
	{
		ITextFont *pfont;
		psel->GetFont(&pfont);
		if (pfont)
		{
			pfont->SetSuperscript(tomToggle);
			pfont->Release();
			hr = S_OK;
			fUpdateFormat = FALSE;
		}
		break;
	}

	case 'A':
	{
		ITextFont *pfont;
		psel->GetFont(&pfont);
		if (pfont)
		{
			pfont->SetAllCaps(tomToggle);
			pfont->Release();
			hr = S_OK;
			fUpdateFormat = FALSE;
		}
		break;
	}

	case 'L':						// Cycle numbering style
	{
		CParaFormat PF;
		DWORD dwMask = PFM_NUMBERING | PFM_OFFSET;

		PF._wNumbering = psel->GetPF()->_wNumbering + 1;
		PF._wNumbering %= tomListNumberAsUCRoman + 1;
		PF._dxOffset = 0;
		if(PF._wNumbering)
		{
			dwMask |= PFM_NUMBERINGSTYLE | PFM_NUMBERINGSTART;
			PF._wNumberingStyle = PFNS_PERIOD;
			PF._wNumberingStart = 1;
			PF._dxOffset = 360;
		}
		hr = psel->SetParaFormat(&PF, publdr, dwMask, 0);
		break;
	}
#define VK_RANGLE	190
#define VK_LANGLE	188

	case VK_RANGLE:					// '>' on US keyboards
	case VK_LANGLE:					// '<' on US keyboards
		hr = OnSetFontSize(vkey == VK_RANGLE ? 1 : -1, 0, publdr)
		   ? S_OK : S_FALSE;
		fUpdateFormat = (hr == S_FALSE);
		break;
	}

	if(hr != S_FALSE)
	{
		if (fUpdateFormat)
			psel->Update_iFormat(-1);
		psel->Update(FALSE);
	}
	return hr;
}				

/*
 *	CTxtEdit::OnTxChar (vkey, dwFlags, publdr)
 *
 *	@mfunc
 *		Handle WM_CHAR message
 *
 *	@rdesc
 *		HRESULT with the following values:
 *
 *		S_OK				if key was understood and consumed
 *		S_MSG_KEY_IGNORED	if key was understood, but not consumed
 *		S_FALSE				if key was not understood (and not consumed)
 */
HRESULT CTxtEdit::OnTxChar(
	DWORD		  vkey,		//@parm Translated key code
	DWORD		  dwFlags,	//@parm lparam of WM_KEYDOWN msg
	IUndoBuilder *publdr)	//@parm Undobuilder to receive antievents
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxChar");

	// Reset Alt key state if needed
	if (!(HIWORD(dwFlags) & KF_ALTDOWN))
		ResetKeyboardFlag(ALT);

	DWORD dwKbdFlags = GetKeyboardFlags();
	DWORD dwFlagsPutChar = _fOverstrike | KBD_CHAR;
	if(dwKbdFlags & ALTNUMPAD)
	{
		DWORD Number = GetKeyPadNumber();
		if(Number >= 256 || vkey >= 256)
			vkey = Number;
		ResetKeyboardFlag(ALTNUMPAD | ALT0);
		dwFlagsPutChar &= ~KBD_CHAR;		// Need font binding
	}

	if (_fMouseDown || vkey == VK_ESCAPE ||	// Ctrl-Backspace generates VK_F16
		vkey == VK_BACK || vkey==VK_F16)	// Eat it since we process it
	{										//  in WM_KEYDOWN
		return S_OK;
	}

	CTxtSelection * const psel = GetSel();
	AssertSz(psel,
		"CTxtEdit::OnChar() - No selection object !");

	if(_fReadOnly && vkey != 3)				// Don't allow input if read only,
	{										//  but allow copy (Ctrl-C)
		if(vkey >= ' ')
			Beep();
		return S_MSG_KEY_IGNORED;
	}

	if(vkey >= ' ' || vkey == VK_TAB)
	{
		TxSetCursor(0, NULL);
		if(IsntProtectedOrReadOnly(WM_CHAR, vkey, dwFlags))
		{
			LONG nDeadKey = GetDeadKey();
			if(nDeadKey)
			{
				DWORD ch	  = vkey | 0x20;		// Convert to lower case
				BOOL fShift	  = vkey != ch;			//  (if ASCII letter)
				//							   a   b	c	d	 e	 f  g  h    i	j
				const static WORD chOff[] = {0xDF, 0, 0xE7, 0, 0xE7, 0, 0, 0, 0xEB, 0,
				//						k  l  m    n     o   p  q  r  s  t    u
										0, 0, 0, 0xF1, 0xF1, 0, 0, 0, 0, 0, 0xF8};
				SetDeadKey(0);
				if(!IN_RANGE('a', ch, 'u'))			// Not relevant ASCII
					return S_OK;					//  letter
	
				vkey = chOff[ch - 'a'];				// Translate to base char
				if(!vkey)							// No accents available
					return S_OK;					//  in current approach

				if(ch == 'n')
				{
					if(nDeadKey != ACCENT_TILDE)
						return S_OK;
				}
				else if(nDeadKey == ACCENT_CEDILLA)
				{
					if(ch != 'c')
						return S_OK;
				}
				else								// aeiou
				{
					vkey += (WORD)nDeadKey;
					if (nDeadKey >= ACCENT_TILDE &&	// eiu with ~ or :
						(vkey == 0xF0 || vkey & 8))		
					{
						if(nDeadKey != ACCENT_UMLAUT)// Only have umlauts
							return S_OK;
						vkey--;
					}
				}
				if(fShift)							// Convert to upper case						
					vkey &= ~0x20;					
			}
			
			// If character is LRM | RLM character, then convert vkey
			if(W32->_fLRMorRLM && IsBiDi() && IN_RANGE(0xFD, vkey, 0xFE))
				vkey = LTRMARK + (vkey - 0xFD);				

			if(dwKbdFlags & CTRL)
				dwFlagsPutChar |= KBD_CTRL;		// Need for Ctrl+TAB in tables
			psel->PutChar(vkey, dwFlagsPutChar, publdr,
						  GetAdjustedTextLength() ? 0 : LOWORD(GetKeyboardLayout(0xFFFFFFFF)));
		}
	}
	else if(Get10Mode() && (vkey == VK_RETURN || vkey == CONTROL('J')))
		InsertEOP(dwFlags, FALSE, publdr);		// 1.0 handled <CR> on WM_CHAR

#ifndef NOCOMPLEXSCRIPTS	
	if(_fHbrCaps)
	{
		 W32->ActivateKeyboard(HEBREW_INDEX);
		 _fHbrCaps = FALSE;
	}
#endif
	return S_OK;
}

/*
 *	CTxtEdit::OnTxSysChar (vkey, dwFlags, publdr)
 *
 *	@mfunc
 *		Handle WM_SYSCHAR message
 *
 *	@rdesc
 *		HRESULT with the following values:
 *
 *		S_OK				if key was understood and consumed
 *		S_MSG_KEY_IGNORED	if key was understood, but not consumed
 *		S_FALSE				if key was not understood (and not consumed)
 */
HRESULT CTxtEdit::OnTxSysChar(
	WORD		  vkey,		//@parm Translated key code
	DWORD		  dwFlags,	//@parm lparam of WM_KEYDOWN msg
	IUndoBuilder *publdr)	//@parm Undobuilder to receive antievents
{
	if(!(HIWORD(dwFlags) & KF_ALTDOWN) ||
	   !IsntProtectedOrReadOnly(WM_KEYDOWN, vkey, dwFlags, FALSE))
	{
		return S_FALSE;
	}

	BOOL	fWholeDoc = TRUE;
	HRESULT hr = S_FALSE;
	int		level = 0;
	CTxtSelection * const psel = GetSel();

	switch(vkey)
	{
	case VK_BACK:
		return S_OK;

	case 'x':
		hr = psel->HexToUnicode(publdr);
		break;

	case 'X':
		hr = psel->UnicodeToHex(publdr);
		break;

	case '+':
	case '-':
		level = vkey == VK_ADD ? 1 : -1;
		fWholeDoc = FALSE;
		/* Fall through */
	case 'A':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
		{
			CTxtRange rg(*psel);
			if(!level)
				level = vkey == 'A' ? 9 : vkey - '0';
			return rg.ExpandOutline(level, fWholeDoc);
		}
	}
	return hr;
}

HRESULT CTxtEdit::OnTxSysKeyDown(
	WORD		  vkey,				//@parm Virtual key code
	DWORD		  dwFlags,			//@parm lparam of WM_KEYDOWN msg
	IUndoBuilder *publdr)			//@parm Undobuilder to receive antievents
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnTxSysKeyDown");

        
	if(IN_RANGE(VK_SHIFT, vkey, VK_MENU))
	{
		SetKeyboardFlag(GetKbdFlags(vkey, dwFlags));
		SetKeyPadNumber(0);				// Init keypad number to 0
		return S_FALSE;
	}

	if (StopMagellanScroll())
	    return S_FALSE;

	HRESULT hr = OnTxSpecialKeyDown(vkey, dwFlags, publdr);
	if(hr != S_FALSE)
		return hr;

	if(vkey == VK_BACK && (HIWORD(dwFlags) & KF_ALTDOWN))
	{
		if(PopAndExecuteAntiEvent(_pundo, 0) != NOERROR)
			hr = S_MSG_KEY_IGNORED;
	}
	else if(vkey == VK_F10 &&					// F10
			!(HIWORD(dwFlags) & KF_REPEAT) &&	// Key previously up
			(GetKeyboardFlags() & SHIFT))		// Shift is down
	{
		HandleKbdContextMenu();
	}

	return hr;
}

/////////////////////////////// Other system events //////////////////////////////

HRESULT CTxtEdit::OnContextMenu(LPARAM lparam)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnContextMenu");

	POINT pt;

	pt.x = LOWORD(lparam);
	pt.y = HIWORD(lparam);

	if(TxScreenToClient(&pt))
		return OnTxRButtonUp(pt.x, pt.y, 0, RB_NOSELCHECK);

	return S_FALSE;
}

/*
 *	CTxtEdit::HandleKbdContextMenu ()
 *
 *	@mfunc	decides where to put the context menu on the basis of where the
 *			the selection is.  Useful for shift-F10 and VK_APPS, where
 *			we aren't given a location.
 */
void CTxtEdit::HandleKbdContextMenu()
{
	POINTUV pt;
	RECTUV	rc;
	const CTxtSelection * const psel = GetSel();
	int RbOption = RB_DEFAULT;

	// Figure out where selection ends and put context menu near it
	if(_pdp->PointFromTp(*psel, NULL, FALSE, pt, NULL, TA_TOP) < 0)
		return;

	// Due to various factors, the result of PointFromTp doesn't land
	// in the selection in PointInSel. Therefore, we send in an override
	// here if the selection is non-degenerate and to force the result
	// and thus have the correct context menu appear.

	LONG cpMin;
	LONG cpMost;
	psel->GetRange(cpMin, cpMost);

	if (cpMin != cpMost)
	{
		RbOption = RB_FORCEINSEL;
	}

	// Make sure point is still within bounds of edit control
	_pdp->GetViewRect(rc);
	
	 //REVIEW (keithcu) What is this +2/-2???
	if (pt.u < rc.left)
		pt.u = rc.left;
	if (pt.u > rc.right - 2)
		pt.u = rc.right - 2;
	if (pt.v < rc.top)
		pt.v = rc.top;
	if (pt.v > rc.bottom - 2)
		pt.v = rc.bottom - 2;

	POINT ptxy;
	_pdp->PointFromPointuv(ptxy, pt);

	OnTxRButtonUp(ptxy.x, ptxy.y, 0, RbOption);
}


/////////////////////////////// Format Range Commands //////////////////////////////

/*
 *	CTxtEdit::OnFormatRange (pfr, prtcon, hdcMeasure,
 *							 xMeasurePerInch, yMeasurePerInch)
 *	@mfunc
 *		Format the range given by pfr
 *
 *	@comm
 *		This function inputs API cp's that may differ from the
 *		corresponding internal Unicode cp's.
 */
LRESULT CTxtEdit::OnFormatRange(
	FORMATRANGE * pfr, 
	SPrintControl prtcon,
	BOOL		  fSetupDC)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnFormatRange");

	LONG cpMin  = 0;
	LONG cpMost = 0;

	if(pfr)
	{
		cpMin  = GetCpFromAcp(pfr->chrg.cpMin);
		cpMost = GetCpFromAcp(pfr->chrg.cpMost);
	}
	// Even if there is 0 text, we want to print the control so that it will
	// fill the control with background color.
	// Use Adjusted Text Length.  Embedded objects using RichEdit will get the empty
	// document they expect and will create a default size document.
	if(!pfr || cpMin >= GetAdjustedTextLength() &&
		!prtcon._fPrintFromDraw)
	{	// We're done formatting, get rid of our printer's display context.
		delete _pdpPrinter;
		_pdpPrinter = NULL;

		return GetAcpFromCp(GetAdjustedTextLength());
	}

	LONG cpReturn = -1;
	BOOL fSetDCWorked = FALSE;

	// Fix MFC Print preview in mirrored control
	//
	// MFC CPreviewView sends us a mirrored rendering DC. We need to disable
	// this mirroring effect so our internal state remains consistent with user
	// action. We also need to disable mirrored window mode in CPreviewView
	// window. [wchao - 4/9/1999]
	//
	HDC  hdcLocal = pfr->hdc;

#ifndef NOCOMPLEXSCRIPTS
	DWORD dwLayout = W32GetLayout(hdcLocal);

	if (dwLayout & LAYOUT_RTL)
	{
		HWND hwndView = WindowFromDC(hdcLocal);

		if (hwndView)
		{
			DWORD	dwExStyleView = GetWindowLong(hwndView, GWL_EXSTYLE);
			
			if (dwExStyleView & WS_EX_LAYOUTRTL)
				SetWindowLong(hwndView, GWL_EXSTYLE, dwExStyleView & ~WS_EX_LAYOUTRTL);
		}

		W32SetLayout(hdcLocal, 0);
	}
#endif

	// First time in with this printer, set up a new display context.
	// IMPORTANT: proper completion of the printing process is required
	// to dispose of this context and begin a new context.
	// This is implicitly done by printing the last character, or
	// sending an EM_FORMATRANGE message with pfr equal to NULL.
	if(!_pdpPrinter)	
	{
		_pdpPrinter = new CDisplayPrinter (this, hdcLocal, &pfr->rc, prtcon);
		_pdpPrinter->Init();

		_pdpPrinter->SetWordWrap(TRUE);
		// Future: (ricksa) This is a really yucky way to pass the draw info
		// to the printer but it was quick. We want to make this better.
		_pdpPrinter->ResetDrawInfo(_pdp);

		// Set temporary zoom factor (if there is one).
		_pdpPrinter->SetTempZoomDenominator(_pdp->GetTempZoomDenominator());
	}
	else
		_pdpPrinter->SetPrintDimensions(&pfr->rc);

	LONG dxpInch = 0, dypInch = 0;
	// We set the DC everytime because it could have changed.
	if(GetDeviceCaps(hdcLocal, TECHNOLOGY) != DT_METAFILE)
	{
		// This is not a metafile so do the normal thing
		fSetDCWorked = _pdpPrinter->SetDC(hdcLocal);
	}
	else
	{
		//Forms^3 draws using screen resolution, while OLE specifies HIMETRIC
		dxpInch = fInOurHost() ? 2540 : W32->GetXPerInchScreenDC();
		dypInch = fInOurHost() ? 2540 : W32->GetYPerInchScreenDC();

		if (!fSetupDC)
		{
			RECT rc;
			rc.left = MulDiv(pfr->rcPage.left, dxpInch, LX_PER_INCH);
			rc.right = MulDiv(pfr->rcPage.right, dxpInch, LX_PER_INCH);
			rc.top = MulDiv(pfr->rcPage.top, dypInch, LY_PER_INCH);
			rc.bottom = MulDiv(pfr->rcPage.bottom, dypInch, LY_PER_INCH);

			SetWindowOrgEx(hdcLocal, rc.left, rc.top, NULL);
			SetWindowExtEx(hdcLocal, rc.right, rc.bottom, NULL);
		}

		_pdpPrinter->SetMetafileDC(hdcLocal, dxpInch, dypInch);
		fSetDCWorked = TRUE;
	}

	if(fSetDCWorked)
	{
		//It is illogical to have the target device be the screen and the presentation
		//device be a HIMETRIC metafile.
		LONG dxpInchT = -1, dypInchT = -1;
		if (dxpInch && GetDeviceCaps(pfr->hdcTarget, TECHNOLOGY) == DT_RASDISPLAY)
		{
			dxpInchT = dxpInch;
			dypInchT = dypInch;
		}

        // We set this every time because it could have changed.
        if(_pdpPrinter->SetTargetDC(pfr->hdcTarget, dxpInchT, dypInchT))
		{
			// Format another, single page worth of text.
			cpReturn = _pdpPrinter->FormatRange(cpMin, cpMost, prtcon._fDoPrint);
			if(!prtcon._fPrintFromDraw)
			{
				// After formatting, we know where the bottom is. But we only 
				// want to set this if we are writing a page rather than
				// displaying a control on the printer.
				pfr->rc.bottom = pfr->rc.top + _pdpPrinter->DYtoLY(_pdpPrinter->GetHeight());
			}
//REVIEW (keithcu) What to do here?

			// Remember this in case the host wishes to do its own banding.
			_pdpPrinter->SetPrintView(pfr->rc);	// we need to save this for OnDisplayBand.
			_pdpPrinter->SetPrintPage(pfr->rcPage);

			// If we're asked to render, then render the entire page in one go.
			if(prtcon._fDoPrint && (cpReturn > 0 || prtcon._fPrintFromDraw))
			{
				OnDisplayBand(&pfr->rc, prtcon._fPrintFromDraw);

				// Note: we can no longer call OnDisplayBand without reformatting.
				_pdpPrinter->DeleteSubLayouts(0, -1);
				_pdpPrinter->Clear(AF_DELETEMEM);
			}
		}
	}

	return cpReturn > 0 ? GetAcpFromCp(cpReturn) : cpReturn;
}

BOOL CTxtEdit::OnDisplayBand(
	const RECT *prcView,
	BOOL		fPrintFromDraw)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDisplayBand");

	HDC		hdcPrinter;
	RECT	rcView, rcPrint;
	RECTUV	rcuvView, rcuvPrint;

	// Make sure OnFormatRange was called and that it actually rendered something.
	if(!_pdpPrinter || !_pdpPrinter->Count())
		return FALSE;

	// Proportionally map to printers extents.
	_pdpPrinter->LRtoDR(rcView, *prcView, _pdpPrinter->GetTflow());

	rcPrint	= _pdpPrinter->GetPrintView();
	_pdpPrinter->LRtoDR(rcPrint, rcPrint, _pdpPrinter->GetTflow());

	_pdpPrinter->RectuvFromRect(rcuvPrint, rcPrint);
	_pdpPrinter->RectuvFromRect(rcuvView, rcView);

	// Get printer DC because we use it below.
	hdcPrinter = _pdpPrinter->GetDC();

	if(fPrintFromDraw)
	{
		// We need to take view inset into account
		_pdpPrinter->GetViewRect(rcuvPrint, &rcuvPrint);
	}

	// Render this band (if there's something to render)
	if(rcuvView.top < rcuvView.bottom)
		_pdpPrinter->Render(rcuvPrint, rcuvView);

	return TRUE;
}

//////////////////////////////// Protected ranges //////////////////////////////////
/*
 *	CTxtEdit::IsProtected (msg, wparam, lparam)
 *
 *	@mfunc
 *		Find out if selection is protected
 *
 *	@rdesc
 *		TRUE iff 1) control is read-only or 2) selection is protected and
 *		parent query says to protect
 */
BOOL CTxtEdit::IsProtected(
	UINT	msg, 		//@parm	Message id
	WPARAM	wparam, 	//@parm WPARAM from window's message
	LPARAM	lparam)		//@parm LPARAM from window's message
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::IsProtected");
	
	CHECKPROTECT chkprot = CHKPROT_EITHER;
	CTxtSelection *psel = GetSel();

	if(!psel)
		return FALSE;

	// There are a few special cases to consider, namely backspacing
	// into a protected range, deleting into a protected range, and type
	// with overstrike into a protected range.
	if(msg == WM_KEYDOWN && (wparam == VK_BACK || wparam == VK_F16))
	{
		// Check for format behind selection, if we are trying to 
		// backspace an insertion point.
		chkprot = CHKPROT_BACKWARD;
	}
	else if(msg == WM_KEYDOWN && wparam == VK_DELETE || 
		_fOverstrike && msg == WM_CHAR)
	{
		chkprot = CHKPROT_FORWARD;
	}

	// HACK ALERT: we don't do fIsDBCS protection checking for EM_REPLACESEL,
	// EM_SETCHARFORMAT, or EM_SETPARAFORMAT.  Outlook uses these APIs
	// extensively and DBCS protection checking messes them up. N.B. the
	// following if statement assumes that IsProtected returns a tri-value.
	PROTECT iProt = psel->IsProtected(chkprot);
	if (iProt == PROTECTED_YES && msg != EM_REPLACESEL && 
		msg != EM_SETCHARFORMAT && msg != EM_SETPARAFORMAT ||
		iProt == PROTECTED_ASK && _dwEventMask & ENM_PROTECTED && 
		QueryUseProtection(psel, msg, wparam, lparam))
	{
		return TRUE;
	}
	return FALSE;
}

/*
 *	CTxtEdit::IsntProtectedOrReadOnly (msg, wparam, lparam, BOOL)
 *
 *	@mfunc
 *		Find out if selection isn't protected or read only. If it is, 
 *		ring bell.  For msg = WM_COPY, only protection is checked.
 *
 *	@rdesc
 *		TRUE iff 1) control isn't read-only and 2) selection either isn't
 *		protected or parent query says not to protect
 *
 *	@devnote	This function is useful for UI operations (like typing).
 */
BOOL CTxtEdit::IsntProtectedOrReadOnly(
	UINT	msg,	//@parm Message
	WPARAM	wparam,	//@parm Corresponding wparam
	LPARAM	lparam,	//@parm Corresponding lparam
	BOOL	fBeep)	//@parm OK to beep
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::IsProtectedOrReadOnly");

	if (!IsProtected(msg, wparam, lparam) &&
		(msg == WM_COPY || !_fReadOnly))	// WM_COPY only cares about
	{										//  protection
		return TRUE;
	}
	if (fBeep)
		Beep();
	return FALSE;
}

/*
 *	CTxtEdit::IsProtectedRange (msg, wparam, lparam, prg)
 *
 *	@mfunc
 *		Find out if range prg is protected
 *
 *	@rdesc
 *		TRUE iff control is read-only or range is protected and parent
 *		query says to protect
 */
BOOL CTxtEdit::IsProtectedRange(
	UINT		msg, 		//@parm	Message id
	WPARAM		wparam, 	//@parm WPARAM from window's message
	LPARAM		lparam,		//@parm LPARAM from window's message
	CTxtRange *	prg)		//@parm Range to examine
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::IsProtectedRange");
	
	PROTECT iProt = prg->IsProtected(CHKPROT_EITHER);

	if (iProt == PROTECTED_YES ||
		(iProt == PROTECTED_ASK &&  
		 (_dwEventMask & ENM_PROTECTED) &&
		 QueryUseProtection(prg, msg, wparam, lparam)))
	// N.B.  the preceding if statement assumes that IsProtected returns a tri-value
	{
		return TRUE;
	}
	return FALSE;
}

/*
 *	RegisterTypeLibrary
 *
 *	@mfunc
 *		Auxiliary function to ensure the type library is registered if Idispatch is used.
 */
void RegisterTypeLibrary( void )
{
#ifndef NOREGISTERTYPELIB
	static BOOL fOnce = FALSE;

	if (!fOnce)
	{
		CLock Lock;

		fOnce =	TRUE;

		HRESULT  hRes = NOERROR;
		WCHAR    szModulePath[MAX_PATH];
		ITypeLib *pTypeLib = NULL;

		// Obtain the path to this module's executable file
		W32->GetModuleFileName( hinstRE, szModulePath, MAX_PATH );

		// Load and register the type library resource
		if (LoadRegTypeLib(LIBID_tom, 1, 0, LANG_NEUTRAL, &pTypeLib) != NOERROR)
		{
			hRes = W32->LoadTypeLibEx(szModulePath, REGKIND_REGISTER, &pTypeLib);
		}

		if(SUCCEEDED(hRes) && pTypeLib)
		{
			pTypeLib->Release();
		}
	}
#endif
}

/////////////////////////////// Private IUnknown //////////////////////////////

HRESULT __stdcall CTxtEdit::CUnknown::QueryInterface(
	REFIID riid, 
	void **ppvObj)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CUnknown::QueryInterface");

	CTxtEdit *ped = (CTxtEdit *)GETPPARENT(this, CTxtEdit, _unk);
	*ppvObj = NULL;

	if(IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_ITextServices)) 
		*ppvObj = (ITextServices *)ped;

	else if(IsEqualIID(riid, IID_IDispatch))
	{
		*ppvObj = (IDispatch *)ped;
		RegisterTypeLibrary();
	}

	else if(IsEqualIID(riid, IID_ITextDocument))
	{
		*ppvObj = (ITextDocument *)ped;
		// No need to do this.  It was put in for Alpha thunking.
		// A better thing to do is to force clients who need this
		// to QI for IDispatch before QI for ITextDocument
		// RegisterTypeLibrary();
	}

	else if(IsEqualIID(riid, IID_ITextDocument2))
		*ppvObj = (ITextDocument2 *)ped;

	else if(IsEqualIID(riid, IID_IRichEditOle))
		*ppvObj = (IRichEditOle *)ped;

	else if(IsEqualIID(riid, IID_IRichEditOleCallback))
	{
		// NB!! Returning this pointer in our QI is 
		// phenomenally bogus; it breaks fundamental COM
		// identity rules (granted, not many understand them!).
		// Anyway, RichEdit 1.0 did this, so we better.
		TRACEWARNSZ("Returning IRichEditOleCallback interface, COM "
			"identity rules broken!");

		*ppvObj = ped->GetRECallback();
	}

	if(*ppvObj)
	{
		((IUnknown *) *ppvObj)->AddRef();
		return S_OK;
	}
	return E_NOINTERFACE;
}

ULONG __stdcall	CTxtEdit::CUnknown::AddRef()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CUnknown::AddRef");

	return ++_cRefs;
}

ULONG __stdcall CTxtEdit::CUnknown::Release()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::CUnknown::Release");

	// the call manager will take care of deleting our instance if appropriate.
	CTxtEdit *ped = GETPPARENT(this, CTxtEdit, _unk);
	CCallMgr callmgr(ped);

	ULONG culRefs =	--_cRefs;

	if(culRefs == 0)
	{
		// Even though we don't delete ourselves now, dump the callback
		// if we have it.  This make implementation a bit easier on clients.

		if(ped->_pobjmgr)
			ped->_pobjmgr->SetRECallback(NULL);

		// Make sure our timers are gone
		ped->TxKillTimer(RETID_AUTOSCROLL);
		ped->TxKillTimer(RETID_DRAGDROP);
		ped->TxKillTimer(RETID_BGND_RECALC);
		ped->TxKillTimer(RETID_SMOOTHSCROLL);
		ped->TxKillTimer(RETID_MAGELLANTRACK);
	}
	return culRefs;
}

/*
 *  ValidateTextRange(pstrg)
 *
 *  @func
 *	  Makes sure that an input text range structure makes sense.
 *
 *  @rdesc
 *	  Size of the buffer required to accept copy of data or -1 if all the
 *	  data in the control is requested. 
 *
 *  @comm
 *	  This is used both in this file and in the RichEditANSIWndProc
 */
LONG ValidateTextRange(
	TEXTRANGE *pstrg)		//@parm pointer to a text range structure
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "ValidateTextRange");

	// Validate that the input structure makes sense. In the first
	// place it must be big enough. Secondly, the values must sense.
	// Remember that if the cpMost field is -1 and the cpMin field
	// is 0 this means that the call wants the entire buffer. 
	if (IsBadReadPtr(pstrg, sizeof(TEXTRANGE))	||
		((pstrg->chrg.cpMost < 1 || pstrg->chrg.cpMin < 0 ||
		  pstrg->chrg.cpMost <= pstrg->chrg.cpMin) &&
		 !(pstrg->chrg.cpMost == -1 && !pstrg->chrg.cpMin)))
	{
		// This isn't valid so tell the caller we didn't copy any data
		return 0;
	}
	// Calculate size of buffer that we need on return
	return pstrg->chrg.cpMost - pstrg->chrg.cpMin;
}


////////////////////////////////////  Selection  /////////////////////////////////////

CTxtSelection * CTxtEdit::GetSel()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSel");

	if(!_psel && _pdp)
	{
		// There is no selection object available so create it.
		_psel = new CTxtSelection(_pdp);
		if(_psel)
			_psel->AddRef();					// Set reference count = 1
	}

	// It is caller's responsiblity to notice that an error occurred
	// in allocation of selection object.
	return _psel;
}

void CTxtEdit::DiscardSelection()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::DiscardSelection");

	if(_psel)
	{
		_psel->Release();
		if(_psel)
		{
			// The text services reference is not the last reference to the 
			// selection. We could keep track of the fact that text services
			// has released its reference and when text services gets a 
			// reference again, do the AddRef there so that if the last 
			// reference went away while we were still inactive, the selection
			// object would go away. However, it is seriously doubtful that 
			// such a case will be very common. Therefore, just do the simplest
			// thing and put our reference back.
			_psel->AddRef();
		}
	}
}

void CTxtEdit::GetSelRangeForRender(
	LONG *pcpSelMin,
	LONG *pcpSelMost)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetSelRangeForRender");

	// If we have no selection or we are not active and the selection
	// has been requested to be hidden, there is no selection so we
	// just return 0's.
	if(!_psel || (!_fInPlaceActive && _fHideSelection))
	{
		*pcpSelMin = 0;
		*pcpSelMost = 0;
		return;
	}

	// Otherwise return the state of the current selection.
	*pcpSelMin  = _psel->GetScrSelMin();
	*pcpSelMost = _psel->GetScrSelMost();
}

LRESULT CTxtEdit::OnGetSelText(
	WCHAR *psz)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetSelText");

	LONG cpMin  = GetSelMin();					// length + 1 for the null
	LONG cpMost = GetSelMost();
	return GetTextRange(cpMin, cpMost - cpMin + 1, psz);
}

/*
 *	CTxtEdit::OnExGetSel (pcrSel)
 *
 *	@mfunc
 *		Get the current selection acpMin, acpMost packaged in a CHARRANGE.
 *	
 *	@comm
 *		This function outputs API cp's that may differ from the
 *		corresponding internal Unicode cp's.
 */
void CTxtEdit::OnExGetSel(
	CHARRANGE *pcrSel)	//@parm Output parm to receive acpMin, acpMost
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnExGetSel");

	pcrSel->cpMin  = GetAcpFromCp(GetSelMin());
	pcrSel->cpMost = GetAcpFromCp(GetSelMost());
}

/*
 *	CTxtEdit::OnGetSel (pacpMin, pacpMost)
 *
 *	@mfunc
 *		Get the current selection acpMin, acpMost.
 *	
 *	@rdesc
 *		LRESULT = acpMost > 65535L ? -1 : MAKELRESULT(acpMin, acpMost)
 *
 *	@comm
 *		This function outputs API cp's that may differ from the
 *		corresponding internal Unicode cp's.
 */
LRESULT CTxtEdit::OnGetSel(
	LONG *pacpMin,		//@parm Output parm to receive acpMin
	LONG *pacpMost)		//@parm Output parm to receive acpMost
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetSel");

	CHARRANGE crSel;

	OnExGetSel(&crSel);
	if(pacpMin)
		*pacpMin = crSel.cpMin;
	if(pacpMost)
		*pacpMost = crSel.cpMost;

	return (crSel.cpMost > 65535l)	? (LRESULT) -1
				: MAKELRESULT((WORD) crSel.cpMin, (WORD) crSel.cpMost);
}

/*
 *	CTxtEdit::OnSetSel (acpMin, acpMost)
 *
 *	@mfunc
 *		Implements the EM_SETSEL message
 *
 *	Algorithm:
 *		There are three basic cases to handle
 *
 *		cpMin < 0,  cpMost ???		-- Collapse selection to insertion point
 *									   at text end if cpMost < 0 and else at
 *									   selection active end
 *		cpMin >= 0, cpMost < 0		-- select from cpMin to text end with
 *									   active end at text end
 *
 *		cpMin >= 0, cpMost >= 0		-- Treat as cpMin, cpMost with active
 *									   end at cpMost
 *
 *	@comm
 *		This function inputs API cp's that may differ from the
 *		corresponding internal Unicode cp's.
 */
LRESULT CTxtEdit::OnSetSel(
	LONG acpMin,		//@parm Input acpMin
	LONG acpMost)		//@parm Input acpMost
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetSel");

	// Since this is only called from the window proc, we are always active
	Assert(GetSel());
	
	CTxtSelection * const psel = GetSel();
	LONG cpMin, cpMost;

	if(acpMin < 0)
		cpMin = cpMost = (acpMost < 0) ? tomForward : psel->GetCp();
	else
	{
		cpMin  = GetCpFromAcp(acpMin);
		cpMost = (acpMost < 0) ? tomForward : GetCpFromAcp(acpMost);
	}
	if(Get10Mode() && cpMost < cpMin)	// In 10 mode, ensure
	{									//  cpMost >= cpMin.  In
		cpMin ^= cpMost;				//  SetSelection, we set active
		cpMost ^= cpMin;				//  end to cpMost, which can be
		cpMin ^= cpMost;				//  smaller than cpMin, in spite
	}									//  of its name.
	psel->SetSelection(cpMin, cpMost);
	return GetAcpFromCp(psel->GetCpMost());
}

///////////////////////////////  DROP FILES support  //////////////////////////////////////
#ifndef NODROPFILES

LRESULT CTxtEdit::InsertFromFile (
	LPCTSTR lpFile)
{
	REOBJECT		reobj;
	LPRICHEDITOLECALLBACK const precall = GetRECallback();
	HRESULT			hr = NOERROR;

	if(!precall)
		return E_NOINTERFACE;

	ZeroMemory(&reobj, sizeof(REOBJECT));
	reobj.cbStruct = sizeof(REOBJECT);

	// Get storage for the object from client
	hr = precall->GetNewStorage(&reobj.pstg);
	if(hr)
	{
		TRACEERRORSZ("GetNewStorage() failed.");
		goto err;
	}

	// Create an object site for new object
	hr = GetClientSite(&reobj.polesite);
	if(!reobj.polesite)
	{
		TRACEERRORSZ("GetClientSite() failed.");
		goto err;
	}
	
	hr = OleCreateLinkToFile(lpFile, IID_IOleObject, OLERENDER_DRAW,
				NULL, NULL, reobj.pstg, (LPVOID*)&reobj.poleobj);	
	if(hr)
	{
		TRACEERRORSZ("Failure creating link object.");
		goto err;
	}

 	reobj.cp = REO_CP_SELECTION;
	reobj.dvaspect = DVASPECT_CONTENT;

 	//Get object clsid
	hr = reobj.poleobj->GetUserClassID(&reobj.clsid);
	if(hr)
	{
		TRACEERRORSZ("GetUserClassID() failed.");
		goto err;
	}

	// Let client know what we're up to
	hr = precall->QueryInsertObject(&reobj.clsid, reobj.pstg,
			REO_CP_SELECTION);
	if(hr != NOERROR)
	{
		TRACEERRORSZ("QueryInsertObject() failed.");
		goto err;
	}

	hr = reobj.poleobj->SetClientSite(reobj.polesite);
	if(hr)
	{
		TRACEERRORSZ("SetClientSite() failed.");
		goto err;
	}

	if(hr = InsertObject(&reobj))
	{
		TRACEERRORSZ("InsertObject() failed.");
	}

err:
	if(reobj.poleobj)
		reobj.poleobj->Release();

	if(reobj.polesite)
		reobj.polesite->Release();

	if(reobj.pstg)
		reobj.pstg->Release();

	return hr;
}

typedef void (WINAPI*DRAGFINISH)(HDROP);
typedef UINT (WINAPI*DRAGQUERYFILEA)(HDROP, UINT, LPSTR, UINT);
typedef UINT (WINAPI*DRAGQUERYFILEW)(HDROP, UINT, LPTSTR, UINT);
typedef BOOL (WINAPI*DRAGQUERYPOINT)(HDROP, LPPOINT);

LRESULT	CTxtEdit::OnDropFiles(
	HANDLE hDropFiles)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDropFiles");

	UINT	cFiles;
	UINT	iFile;
	char	szFile[MAX_PATH];
	WCHAR	wFile[MAX_PATH];
	POINT	ptDrop;
	CTxtSelection * const psel = GetSel();
	HMODULE		hDLL = NULL;
	DRAGFINISH		fnDragFinish; 
	DRAGQUERYFILEA	fnDragQueryFileA;
	DRAGQUERYFILEW	fnDragQueryFileW;
	DRAGQUERYPOINT	fnDragQueryPoint;

	if (_fReadOnly)
		return 0;

	AssertSz((hDropFiles != NULL), "CTxtEdit::OnDropFiles invalid hDropFiles");

	// dynamic load Shell32

	hDLL = LoadLibrary (TEXT("Shell32.DLL"));
	if(hDLL)
	{
		fnDragFinish = (DRAGFINISH)GetProcAddress (hDLL, "DragFinish");
		fnDragQueryFileA = (DRAGQUERYFILEA)GetProcAddress (hDLL, "DragQueryFileA");
		fnDragQueryFileW = (DRAGQUERYFILEW)GetProcAddress (hDLL, "DragQueryFileW");
		fnDragQueryPoint = (DRAGQUERYPOINT)GetProcAddress (hDLL, "DragQueryPoint");
	}
	else
		return 0;

	if(!fnDragFinish || !fnDragQueryFileA || !fnDragQueryFileW || !fnDragQueryPoint)
	{
		AssertSz(FALSE, "Shell32 GetProcAddress failed");
		goto EXIT0;
	}

	(*fnDragQueryPoint) ((HDROP)hDropFiles, &ptDrop);
	if(W32->OnWin9x())
		cFiles = (*fnDragQueryFileA) ((HDROP)hDropFiles, (UINT)-1, NULL, 0);
	else
		cFiles = (*fnDragQueryFileW) ((HDROP)hDropFiles, (UINT)-1, NULL, 0);

	if(cFiles)
	{
		LONG		cp = 0;
		ptDrop;
		CRchTxtPtr  rtp(this);
		const CCharFormat	*pCF;		
		POINTUV		pt;

		_pdp->PointuvFromPoint(pt, ptDrop);
		if(_pdp->CpFromPoint(pt, NULL, &rtp, NULL, FALSE) >= 0)
		{
			cp = rtp.GetCp();
			pCF = rtp.GetCF();
		}
		else
		{
			LONG iCF = psel->Get_iCF();
			cp = psel->GetCp();	
			pCF = GetCharFormat(iCF);
			ReleaseFormats(iCF, -1);
		}
		
		// Notify user for dropfile
		if(_dwEventMask & ENM_DROPFILES)
		{
			ENDROPFILES endropfiles;

			endropfiles.hDrop = hDropFiles;
			endropfiles.cp = Get10Mode() ? GetAcpFromCp(cp) : cp;
			endropfiles.fProtected = !!(pCF->_dwEffects & CFE_PROTECTED);

			if(TxNotify(EN_DROPFILES, &endropfiles))
				goto EXIT;					// Ignore drop file
			
			cp = Get10Mode() ? GetCpFromAcp(endropfiles.cp) : endropfiles.cp;	// Allow callback to update cp
		}
		psel->SetCp(cp, FALSE);	
	}

	for (iFile = 0;  iFile < cFiles; iFile++)
	{
		if(W32->OnWin9x())
		{
			(*fnDragQueryFileA) ((HDROP)hDropFiles, iFile, szFile, MAX_PATH);
			MultiByteToWideChar(CP_ACP, 0, szFile, -1, 
							wFile, MAX_PATH);
		}
		else
			(*fnDragQueryFileW) ((HDROP)hDropFiles, iFile, wFile, MAX_PATH);

		InsertFromFile (wFile);
	}

EXIT:
	(*fnDragFinish) ((HDROP)hDropFiles);

EXIT0:
	FreeLibrary (hDLL);
	return 0;
}

#else // NODROPFILES

LRESULT	CTxtEdit::OnDropFiles(HANDLE hDropFiles)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDropFiles");

	return 0;
}

#endif	// NODROPFILES


///////////////////////////////  Exposable methods  //////////////////////////////////////

/*
 *	CTxtEdit::TxCharFromPos (ppt, plres)
 *
 *	@mfunc
 *		Get the acp at the point *ppt.
 *	
 *	@rdesc
 *		HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *				  (CpFromPoint succeeded) ? S_OK : E_FAIL
 *	@comm
 *		This function outputs an API cp that may differ from the
 *		corresponding internal Unicode cp.
 */
HRESULT	CTxtEdit::TxCharFromPos(
	LPPOINT	 ppt,	//@parm Point to find the acp for
	LRESULT *plres)	//@parm Output parm to receive the acp
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxCharFromPos");

	if(!fInplaceActive())
	{
		// We have no valid display rectangle if this object is not active
		*plres = -1;
		return OLE_E_INVALIDRECT;
	}
	POINTUV pt;
	_pdp->PointuvFromPoint(pt, *ppt);
	*plres = _pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE);
	if(*plres == -1)
		return E_FAIL;

	*plres = GetAcpFromCp(*plres);
	return S_OK;
}

/*
 *	CTxtEdit::TxPosFromChar (acp, ppt)
 *
 *	@mfunc
 *		Get the point at acp.
 *	
 *	@rdesc
 *		HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *				  (PointFromTp succeeded) ? S_OK : E_FAIL
 *	@comm
 *		This function inputs an API cp that may differ from the
 *		corresponding internal Unicode cp.
 */
HRESULT CTxtEdit::TxPosFromChar(
	LONG	acp,		//@parm Input cp to get the point for
	POINT *	ppt)		//@parm Output parm to receive the point
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxPosFromChar");

	if(!fInplaceActive())
		return OLE_E_INVALIDRECT;

	CRchTxtPtr rtp(this, GetCpFromAcp(acp));

	POINTUV pt;
	if(_pdp->PointFromTp(rtp, NULL, FALSE, pt, NULL, TA_TOP) < 0)
		return E_FAIL;

	_pdp->PointFromPointuv(*ppt, pt);

	return S_OK;
}

/*
 *	CTxtEdit::TxFindWordBreak (nFunction, acp, plres)
 *
 *	@mfunc
 *		Find word break or classify character at acp.
 *	
 *	@rdesc
 *		HRESULT = plRet ? S_OK : E_INVALIDARG
 *
 *	@comm
 *		This function inputs and exports API cp's and cch's that may differ
 *		from the internal Unicode cp's and cch's.
 */
HRESULT CTxtEdit::TxFindWordBreak(
	INT		 nFunction,	//@parm Word break function
	LONG	 acp,		//@parm Input cp
	LRESULT *plres)		//@parm	cch moved to reach break
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxFindWordBreak");

	CTxtPtr tp(this, GetCpFromAcp(acp));		// This validates cp
	LONG	cpSave = tp.GetCp();				// Save starting value
	
	if(!plres)
		return E_INVALIDARG;
	
	*plres = tp.FindWordBreak(nFunction);

	// WB_CLASSIFY and WB_ISDELIMITER return values; others return offsets
	// this function returns values, so it converts when necessary
	if(nFunction != WB_CLASSIFY && nFunction != WB_ISDELIMITER)
		*plres = GetAcpFromCp(LONG(*plres + cpSave));

	return S_OK;
}

/*
 *	INT CTxtEdit::TxWordBreakProc (pch, ich, cb, action)
 *	
 *	@func
 *		Default word break proc used in conjunction with FindWordBreak. ich
 *		is character offset (start position) in the buffer pch, which is cb
 *		bytes in length.  Possible action values are:
 *
 *	WB_CLASSIFY
 *		Returns char class and word break flags of char at start position.
 *
 *	WB_ISDELIMITER
 *		Returns TRUE iff char at start position is a delimeter.
 *
 *	WB_LEFT
 *		Finds nearest word beginning before start position using word breaks.
 *
 *	WB_LEFTBREAK
 *		Finds nearest word end before start position using word breaks.
 *		Used by CMeasurer::Measure()
 *
 *	WB_MOVEWORDLEFT
 *		Finds nearest word beginning before start position using class
 *		differences. This value is used during CTRL+LEFT key processing.
 *
 *	WB_MOVEWORDRIGHT
 *		Finds nearest word beginning after start position using class
 *		differences. This value is used during CTRL+RIGHT key processing.
 *
 *	WB_RIGHT
 *		Finds nearest word beginning after start position using word breaks.
 *		Used by CMeasurer::Measure()
 *
 *	WB_RIGHTBREAK
 *		Finds nearest word end after start position using word breaks.
 *	
 *	@rdesc
 *		Character offset from start of buffer (pch) of the word break
 */
INT CTxtEdit::TxWordBreakProc(
	WCHAR *	pch,	    //@parm Char buffer
	INT		ich,	    //@parm Char offset of _cp in buffer
	INT		cb,		    //@parm Count of bytes in buffer
	INT		action,	    //@parm Type of breaking action
	LONG    cpStart,    //@parm cp for first character in pch
	LONG	cp)		    //@parm cp associated to ich
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxWordBreakProc");

	if (_pfnWB)
	{
		// Client overrode the wordbreak proc, delegate the call to it.		
		if (!Get10Mode())
		{
		    Assert(!_fExWordBreakProc);
			//8638: return number of characters, not bytes.
		    return _pfnWB(pch, ich, CchOfCb(cb), action);
		}
#ifndef NOANSIWINDOWS
		else
		{
            int ret = 0;
            char sz[256];
            char* pach = sz;
    		if (cb >= 255)
    		    pach = new char [cb + 1];

    		// this indicates if we have to adjust the pach because the api's for
		    // EDITWORDBREAKPROCEX and EDITWORDBREAKPROC are different when looking to the left
            BOOL fAdjustPtr = _fExWordBreakProc && (action == WB_LEFT || action == WB_MOVEWORDLEFT || action == WB_LEFTBREAK);

            // RichEdit 1.0, create a buffer, translate ich and WCTMB
    		// pch into the buffer.  Need codepage to use. Then get translate
    		// return value. Translations are like GetCachFromCch() and
    		// GetCchFromCach()
            if (_fExWordBreakProc)
            {
                Assert(ich == 0 || ich == 1 || ich == CchOfCb(cb));

                // We need to adjust the cp to the starting point of the buffer
                if (!fAdjustPtr)
                {
                    cpStart += ich;
                    pch += ich;
                    cb -= (2 * ich);                    
                }

                // initialize string w/ zero's so we can determine the length of the string for later
                memset(pach, 0, cb + 1);
            }
                
			int nLen = CchOfCb(cb);
    		CRchTxtPtr rtp(this, cpStart);
    		BYTE iCharRep = rtp.GetCF()->_iCharRep;
   	        if (WideCharToMultiByte(CodePageFromCharRep(iCharRep), 0, pch, nLen, pach, cb + 1, NULL, NULL))
       	    {
       	        // Documentation stipulates we need to point to the end of the string
       	        if (fAdjustPtr)
       	            pach += strlen(pach);

        	    if (_fExWordBreakProc)
	                ret = ((EDITWORDBREAKPROCEX)_pfnWB)(pach, nLen, CharSetFromCharRep(iCharRep), action);
	            else
				{
	                ret = ((EDITWORDBREAKPROCA)_pfnWB)(pach, rtp.GetCachFromCch(ich), nLen, action);

					// Need to reset cp position because GetCachFromCch may move the cp
					if (ich)
						rtp.SetCp(cpStart);
				}
	            
	            // For WB_ISDELIMITER and WB_CLASSIFY don't need to convert back
    	        // to ich because return value represents a BOOL
    	        if (action != WB_ISDELIMITER && action != WB_CLASSIFY)
    	            ret = rtp.GetCchFromCach(ret);
            }  		

    	    // Delete any allocated memory
    	    if (pach != sz)
    		    delete [] pach;
    		return ret;
		}
#endif // NOANSIWINDOWS
	}

	LONG	cchBuff = CchOfCb(cb);
	LONG	cch = cchBuff - ich;
	WCHAR	ch;
	WORD	cType3[MAX_CLASSIFY_CHARS];
	INT		kinsokuClassifications[MAX_CLASSIFY_CHARS];
	LCID	lcid = 0;
	WORD *	pcType3;
	INT  *	pKinsoku1, *pKinsoku2;
	WORD *	pwRes;
	WORD	startType3 = 0;
	WORD	wb = 0;
	WORD	wClassifyData[MAX_CLASSIFY_CHARS];	// For batch classifying

	Assert(cchBuff < MAX_CLASSIFY_CHARS);
	Assert(ich >= 0 && ich < cchBuff);

	if(W32->OnWin9x())						// Win9x needs lcid to do conversions
	{									 	// Complete fix would break pch into
    	CFormatRunPtr rpCF(_story.GetCFRuns());//  runs <--> lcid
		rpCF.BindToCp(cp, GetTextLength());
		lcid = GetCharFormat(rpCF.GetFormat())->_lcid;
	}
	// Single character actions
	if ( action == WB_CLASSIFY )
	{
	    // 1.0 COMPATABILITY - 1.0 returned 0 for apostrohpe's
	    WCHAR ch = pch[ich];
		if (Get10Mode() && ( ch ==  0x0027 /*APOSTROPHE*/ ||
	        ch == 0xFF07 /*FULLWIDTH APOSTROPHE*/))
	    {
	        return 0;	        
	    }
		return ClassifyChar(ch, lcid);
	}

	if ( action == WB_ISDELIMITER )
		return !!(ClassifyChar(pch[ich], lcid) & WBF_BREAKLINE);

	// Batch classify buffer for whitespace and kinsoku classes
	BatchClassify(pch, cchBuff, lcid, cType3, kinsokuClassifications, wClassifyData);

#ifndef NOCOMPLEXSCRIPTS
    if (_pbrk && cp > -1)
    {
		cp -= ich;

        for (LONG cbrk = cchBuff-1; cbrk >= 0; --cbrk)
        {
            if (cp + cbrk >= 0 && _pbrk->CanBreakCp(BRK_WORD, cp + cbrk))
            {
                // Mimic class open/close in Kinsoku classification.
                kinsokuClassifications[cbrk] = brkclsOpen;
                if (cbrk > 0)
				{
                    kinsokuClassifications[cbrk-1] = brkclsClose;
                    wClassifyData[cbrk-1] |= WBF_WORDBREAKAFTER;
				}
            }
        }
    }
#endif

	// Setup pointers
	pKinsoku2 = kinsokuClassifications + ich; 		// Ptr to current  kinsoku
	pKinsoku1 = pKinsoku2 - 1;						// Ptr to previous kinsoku

	if(!(action & 1))								// WB_(MOVE)LEFTxxx
	{
		ich--;
		Assert(ich >= 0);
	}
	pwRes	 = &wClassifyData[ich];
	pcType3	 = &cType3[ich];						// for ideographics

	switch(action)
	{
	case WB_LEFT:
		for(; ich >= 0 && *pwRes & WBF_BREAKLINE;	// Skip preceding line
			ich--, pwRes--)							//  break chars
				;									// Empty loop. Then fall
													//  thru to WB_LEFTBREAK
	case WB_LEFTBREAK:
		for(; ich >= 0 && !CanBreak(*pKinsoku1, *pKinsoku2);
			ich--, pwRes--, pKinsoku1--, pKinsoku2--)
				;									// Empty loop
		if(action == WB_LEFTBREAK)					// Skip preceding line
		{											//  break chars
			for(; ich >= 0 && *pwRes & WBF_BREAKLINE;
				ich--, pwRes--)
					;								// Empty loop
		}
		return ich + 1;

	case WB_MOVEWORDLEFT:
		for(; ich >= 0 && (*pwRes & WBF_CLASS) == 2;// Skip preceding blank
			ich--, pwRes--, pcType3--)				//  chars
				;
		if(ich >= 0)								// Save starting wRes and
		{											//  startType3
			wb = *pwRes--;							// Really type1
			startType3 = *pcType3--;				// type3
			ich--;
		}
		// Skip to beginning of current word
		while(ich >= 0 && (*pwRes & WBF_CLASS) != 3 && 
            !(*pwRes & WBF_WORDBREAKAFTER) &&
			(IsSameClass(*pwRes, wb, *pcType3, startType3) ||
			!wb && ich && ((ch = pch[ich]) == '\'' || ch == RQUOTE)))
		{
			ich--, pwRes--, pcType3--;
		}
		return ich + 1;


	case WB_RIGHTBREAK:
		for(; cch > 0 && *pwRes & WBF_BREAKLINE;	// Skip any leading line
			cch--, pwRes++)							//  break chars
				;									// Empty loop
													// Fall thru to WB_RIGHT
	case WB_RIGHT:
		// Skip to end of current word
		for(; cch > 0 && !CanBreak(*pKinsoku1, *pKinsoku2);
			cch--, pKinsoku1++, pKinsoku2++, pwRes++)
				;
		if(action != WB_RIGHTBREAK)					// Skip trailing line
		{											//  break chars
			for(; cch > 0 && *pwRes & WBF_BREAKLINE;
				cch--, pwRes++)
					;
		}
		return cchBuff - cch;

	case WB_MOVEWORDRIGHT:
		if(cch <= 0)								// Nothing to do
			return ich;

		wb = *pwRes;								// Save start wRes
		startType3 = *pcType3;						//  and startType3

		// Skip to end of word
		if (startType3 & C3_IDEOGRAPH ||			// If ideographic or
			(*pwRes & WBF_CLASS) == 3)				//  tab/cell, just
		{
			cch--, pwRes++;							//  skip one char
		}
		else while(cch > 0 && 
            !(*pwRes & WBF_WORDBREAKAFTER) &&
			(IsSameClass(*pwRes, wb, *pcType3, startType3) || !wb &&
			 ((ch = pch[cchBuff - cch]) == '\'' || ch == RQUOTE)))
		{
			cch--, pwRes++, pcType3++;
		}

		for(; cch > 0 && 
			((*pwRes & WBF_CLASS) == 2 				// Skip trailing blank
            || (*pwRes & WBF_WORDBREAKAFTER)); 		// Skip Thai break after
			cch--, pwRes++)							//  chars
					;
		return cchBuff - cch;
	}

	TRACEERRSZSC("CTxtEdit::TxWordBreakProc: unknown action", action);
	return ich;
}


/*
 *	CTxtEdit::TxFindText (flags, cpMin, cpMost, pch, pcpRet)
 *
 *	@mfunc
 *		Find text in direction specified by flags starting at cpMin if
 *		forward search (flags & FR_DOWN nonzero) and cpMost if backward
 *		search.
 *
 *	@rdesc
 *		HRESULT (success) ? NOERROR : S_FALSE
 *
 *	@comm
 *		Caller is responsible for setting cpMin to the appropriate end of
 *		the selection depending on which way the search is proceding.
 */
HRESULT CTxtEdit::TxFindText(
	DWORD		flags,	 //@parm Specify FR_DOWN, FR_MATCHCASE, FR_WHOLEWORD
	LONG		cpStart, //@parm Find start cp
	LONG		cpLimit, //@parm Find limit cp 
	const WCHAR*pch,	 //@parm Null terminated string to search for
	LONG *		pcpMin,	 //@parm Out parm to receive start of matched string
	LONG *		pcpMost) //@parm Out parm to receive end of matched string
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxFindText");

	if(Get10Mode())							// RichEdit 1.0 only searches
	{
		flags |= FR_DOWN;					//  forward
		if (cpLimit < -1)
		    cpLimit = -1;
	}

	DWORD		cchText = GetTextLength();
	LONG		cchToFind;
	const BOOL	fSetCur = (cchText >= 4096);
	HCURSOR		hcur = NULL;				// Init to keep compiler happy

	Assert(pcpMin && pcpMost);

	// Validate parameters
	if(!pch || !(cchToFind = wcslen(pch)) || cpStart < 0 || cpLimit < -1)
		return E_INVALIDARG;				// Nothing to search for

	CTxtPtr	tp(this, cpStart);	  
	
	if(fSetCur)								// In case this takes a while...
		hcur = TxSetCursor(LoadCursor(0, IDC_WAIT), NULL);
	
	*pcpMin  = tp.FindText(cpLimit, flags, pch, cchToFind);
	*pcpMost = tp.GetCp();

	if(fSetCur)
		TxSetCursor(hcur, NULL);
	
	return *pcpMin >= 0 ? NOERROR : S_FALSE;
}

/*
 *	CTxtEdit::TxGetLineCount (plres)
 *
 *	@mfunc
 *		Get the line count.
 *	
 *	@rdesc
 *		HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *				  (WaitForRecalc succeeded) ? S_OK : E_FAIL
 */
HRESULT CTxtEdit::TxGetLineCount(
	LRESULT *plres)		//@parm Output parm to receive line count
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetLineCount");

	AssertSz(plres, "CTxtEdit::TxGetLineCount invalid pcli");

	if(!fInplaceActive())
		return OLE_E_INVALIDRECT;

	if(!_pdp->WaitForRecalc(GetTextLength(), -1))
		return E_FAIL;

	*plres = _pdp->LineCount();
	Assert(*plres > 0);

	return S_OK;
}

/*
 *	CTxtEdit::TxLineFromCp (acp, plres)
 *
 *	@mfunc
 *		Get the line containing acp.
 *	
 *	@rdesc
 *		HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *				  (LineFromCp succeeded) ? S_OK : E_FAIL
 *	@comm
 *		This function inputs an API cp that may differ from the
 *		corresponding internal Unicode cp.
 */
HRESULT CTxtEdit::TxLineFromCp(
	LONG	 acp,		//@parm Input cp
	LRESULT *plres)		//@parm Ouput parm to receive line number
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineFromCp");

	BOOL	fAtEnd = FALSE;
	LONG	cp = 0;

	AssertSz(plres, "CTxtEdit::TxLineFromCp invalid plres");

	if(!fInplaceActive())
	{
		AssertSz(*plres == 0, 
			"CTxtEdit::TxLineFromCp error return lres not correct");
		return OLE_E_INVALIDRECT;
	}

	if(acp < 0)									// Validate cp
	{
		if(_psel)
		{
			cp = _psel->GetCpMin();
			fAtEnd = !_psel->GetCch() && _psel->CaretNotAtBOL();
		}
	}
	else
	{
		LONG cchText = GetTextLength();
		cp = GetCpFromAcp(acp);
		cp = min(cp, cchText);
	}
 
	*plres = _pdp->LineFromCp(cp, fAtEnd);

	HRESULT hr = *plres < 0 ? E_FAIL : S_OK;

	// Old messages expect 0 as a result of this call if there is an error.
	if(*plres == -1)
		*plres = 0;

	return hr;
}

/*
 *	CTxtEdit::TxLineLength (acp, plres)
 *
 *	@mfunc
 *		Get the line containing acp.
 *	
 *	@rdesc
 *		HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *				  (GetSel() succeeded) ? S_OK : E_FAIL
 *	@comm
 *		This function inputs an API cp and outputs an API cch that
 *		may differ from the	corresponding internal Unicode cp and cch.
 */
HRESULT CTxtEdit::TxLineLength(
	LONG	 acp,		//@parm Input cp
	LRESULT *plres)		//@parm Output parm to receive line length 
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineLength");

	LONG cch = 0;
	LONG cp;

	AssertSz(plres, "CTxtEdit::TxLineLength Invalid plres parameter");

	if(!fInplaceActive())
		return OLE_E_INVALIDRECT;

	if(acp < 0)
	{
		if(!_psel)
			return E_FAIL;
		cch = _psel->LineLength(&cp);
	}
	else
	{
		cp = GetCpFromAcp(acp);
		if(cp <= GetAdjustedTextLength())
		{
			CLinePtr rp(_pdp);
			rp.SetCp(cp, FALSE);
			cp -= rp.GetIch();				// Goto start of line
			cch = rp.GetAdjustedLineLength();
		}
	}
	if(fCpMap())							// Can be time consuming, so
	{										//  don't do it unless asked
		CRchTxtPtr rtp(this, cp);			//  for
		cch = rtp.GetCachFromCch(cch);
	}
	*plres = cch;
	return S_OK;
}

/*
 *	CTxtEdit::TxLineIndex (acp, plres)
 *
 *	@mfunc
 *		Get the line containing acp.
 *	
 *	@rdesc
 *		HRESULT = !fInplaceActive() ? OLE_E_INVALIDRECTS_OK :
 *				  (LineCount() && WaitForRecalcIli succeeded) ? S_OK : E_FAIL
 *	@comm
 *		This function outputs an API cp that may differ from the
 *		corresponding internal Unicode cp.
 */
HRESULT CTxtEdit::TxLineIndex(
	LONG	 ili,		//@parm Line # to find acp for
	LRESULT *plres)		//@parm Output parm to receive acp
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineIndex");

	HRESULT hr;
	AssertSz(plres, "CTxtEdit::TxLineIndex invalid plres");

	*plres = -1;
	if(!fInplaceActive())
		return OLE_E_INVALIDRECT;

	if(ili == -1)
	{
		// Fetch line from the current cp.
		LRESULT lres;					// For 64-bit compatibility
		hr = TxLineFromCp(-1, &lres);
		if(hr != NOERROR)
			return hr;
		ili = (LONG)lres;
	}

	// ili is a zero-based *index*, whereas count returns the total # of lines.
	// Therefore, we use >= for our comparisions.
	if(ili >= _pdp->LineCount() && !_pdp->WaitForRecalcIli(ili))
		return E_FAIL; 

	*plres = GetAcpFromCp(_pdp->CpFromLine(ili, NULL));
	
	return S_OK;
}


///////////////////////////////////  Miscellaneous messages  ////////////////////////////////////

/*
 *	CTxtEdit::OnFindText (msg, flags, pftex)
 *
 *	@mfunc
 *		Find text.
 *	
 *	@rdesc
 *		LRESULT = succeeded ? acpmin : -1
 *
 *	@comm
 *		This function inputs and exports API cp's that may differ
 *		from the internal Unicode cp's.
 */
LRESULT CTxtEdit::OnFindText(
	UINT		msg,
	DWORD		flags,
	FINDTEXTEX *pftex)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnFindText");

	LONG cpMin, cpMost;

	if(TxFindText(flags,
				  GetCpFromAcp(pftex->chrg.cpMin),
				  GetCpFromAcp(pftex->chrg.cpMost),
				  pftex->lpstrText, &cpMin, &cpMost) != S_OK)
	{
		if(msg == EM_FINDTEXTEX || msg == EM_FINDTEXTEXW)
		{
			pftex->chrgText.cpMin  = -1;
			pftex->chrgText.cpMost = -1;
		}
		return -1;
	}

	LONG acpMin  = GetAcpFromCp(cpMin);
	if(msg == EM_FINDTEXTEX || msg == EM_FINDTEXTEXW)	// We send a message
	{													//  back to change
		pftex->chrgText.cpMin  = acpMin;				//  selection to this
		pftex->chrgText.cpMost = GetAcpFromCp(cpMost);
	}
	return (LRESULT)acpMin;
}

	
// For plain-text instances, OnGetParaFormat() and OnSetParaFormat() apply to whole story
LRESULT CTxtEdit::OnGetCharFormat(
	CHARFORMAT2 *pCF2,
	DWORD		 dwFlags)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetCharFormat");

	UINT cb = pCF2->cbSize;
	UINT CodePage = 1200;

	if(!IsValidCharFormatW(pCF2))
	{
		if(!IsValidCharFormatA((CHARFORMATA *)pCF2))
			return 0;
		CodePage = GetDefaultCodePage(EM_GETCHARFORMAT);
	}

	if(dwFlags & (SCF_ASSOCIATEFONT | SCF_ASSOCIATEFONT2))
		return OnGetAssociateFont(pCF2, dwFlags);

	if(cb == sizeof(CHARFORMATW) ||	cb == sizeof(CHARFORMATA))
		dwFlags |= CFM2_CHARFORMAT;				// Tell callees that only
												//  CHARFORMAT parms needed
	CCharFormat CF;
	DWORD dwMask = CFM_ALL2;

	if(dwFlags & SCF_SELECTION)
		dwMask = GetSel()->GetCharFormat(&CF, dwFlags);
	else
		CF = *GetCharFormat(-1);

	if(dwFlags & CFM2_CHARFORMAT)				// Maintain CHARFORMAT
	{											//  compatibility
		CF._dwEffects &= CFM_EFFECTS;
		dwMask		  &= CFM_ALL;
	}

	CF.Get(pCF2, CodePage);
	pCF2->dwMask = dwMask;
	return (LRESULT)dwMask;
}

LRESULT CTxtEdit::OnGetParaFormat(
	PARAFORMAT2 *pPF2,
	DWORD		 dwFlags)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetParaFormat");

	if(!IsValidParaFormat(pPF2))
		return 0;

	DWORD dwMask2 = 0;
	if(pPF2->cbSize == sizeof(PARAFORMAT))	// Tell callees that only
		dwMask2 = PFM2_PARAFORMAT;			//  PARAFORMAT parms needed

	CParaFormat PF;
	DWORD		dwMask = GetSel()->GetParaFormat(&PF, dwMask2);

	if(dwMask2 & PFM2_PARAFORMAT)
		dwMask &= PFM_ALL;

	PF.Get(pPF2);
	pPF2->dwMask = dwMask;
	return (LRESULT)dwMask;
}

/*
 *	CTxtEdit::OnSetFontSize(yPoint, publdr)
 *
 *	@mfunc
 *		Set new font height by adding yPoint to current height  
 *		and rounding according to the table in cfpf.cpp
 *
 *	@rdesc
 *		LRESULT nonzero if success
 */
LRESULT CTxtEdit::OnSetFontSize(
	LONG yPoint,			//@parm # pts to add to current height
	DWORD dwFlags,			//@parm Options
	IUndoBuilder *publdr)	//@parm Undobuilder to receive antievents
{	
	// TODO: ? Return nonzero if we set a new font size for some text.

	CCharFormat CF;
	CF._yHeight = (SHORT)yPoint;

	return OnSetCharFormat(dwFlags ? dwFlags : SCF_SELECTION, &CF, publdr,
						   CFM_SIZE, CFM2_CHARFORMAT | CFM2_USABLEFONT);
}

/*
 *	CTxtEdit::OnSetFont(hfont)
 *
 *	@mfunc
 *		Set new default font from hfont
 *
 *	@rdesc
 *		LRESULT nonzero if success
 */
LRESULT CTxtEdit::OnSetFont(
	HFONT hfont)			//@parm Handle of font to use for default
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetFont");

	CCharFormat	CF;
	if(FAILED(CF.InitDefault(hfont)))
		return 0;

	DWORD dwMask2 = CFM2_CHARFORMAT;
	WPARAM wparam = SCF_ALL;

	if(!GetAdjustedTextLength())
	{
		dwMask2 = CFM2_CHARFORMAT | CFM2_NOCHARSETCHECK;
		wparam = 0;
	}

	return !FAILED(OnSetCharFormat(wparam, &CF, NULL, CFM_ALL, dwMask2));
}

/*
 *	CTxtEdit::OnSetCharFormat(wparam, pCF, publdr, dwMask, dwMask2)
 *
 *	@mfunc
 *		Set new default CCharFormat
 *
 *	@rdesc
 *		LRESULT nonzero if success
 */
LRESULT CTxtEdit::OnSetCharFormat(
	WPARAM		  wparam,	//@parm Selection flag
	CCharFormat * pCF,		//@parm CCharFormat to apply
	IUndoBuilder *publdr,	//@parm Undobuilder to receive antievents
	DWORD		  dwMask,	//@parm CHARFORMAT2 mask
	DWORD		  dwMask2)	//@parm Second mask
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetCharFormat");

	// This says that if there's a selection that's protected and the
	// parent window wants protection notifications and doesn't want
	// changes with a protected selection, then return 0.  This is more
	// stringent than RE 2.0, but it's more like 1.0.
	if (_psel && _psel->IsProtected(CHKPROT_EITHER) == PROTECTED_ASK &&
		_dwEventMask & ENM_PROTECTED)
	{
		CHARFORMAT CF0;					// Selection is protected, client
										//  wants protect notifications
		CF0.cbSize = sizeof(CHARFORMAT);//  and protected mask is on
		CF0.dwEffects = pCF->_dwEffects;// Concoct CHARFORMAT for query
		CF0.dwMask = dwMask;			// Maybe need more fields...			
		if(QueryUseProtection(_psel, EM_SETCHARFORMAT, wparam, (LPARAM)&CF0))
			return 0;					// No deal
	}

	BOOL fRet = TRUE;

	AssertSz(!_fSelChangeCharFormat || IsRich(),
		"Inconsistent _fSelChangeCharFormat flag");

	if ((wparam & SCF_ALL) ||
		!_fSelChangeCharFormat && _story.GetCFRuns() && !(wparam & SCF_SELECTION))
	{	
		CTxtRange rg(this, 0, -GetTextLength());

		if(publdr)
			publdr->StopGroupTyping();

		if ((dwMask & (CFM_CHARSET | CFM_FACE)) == (CFM_CHARSET | CFM_FACE))
		{
			if(GetAdjustedTextLength())
			{
				dwMask2 |= CFM2_MATCHFONT;
				if (_fAutoFontSizeAdjust)
				{
					dwMask2 |= CFM2_ADJUSTFONTSIZE;
					if (fUseUIFont())
						dwMask2 |= CFM2_UIFONT;
				}
			}
			else
				dwMask2 |= CFM2_NOCHARSETCHECK;
		}

		fRet = (rg.SetCharFormat(pCF, 0, publdr, dwMask, dwMask2) == NOERROR);

		// If we have an insertion point, apply format to it as well
		if (_psel && !_psel->GetCch() &&
			_psel->SetCharFormat(pCF, wparam, publdr, dwMask, dwMask2) != NOERROR)
		{
			fRet = FALSE;
		}
	}
	else if(wparam & SCF_SELECTION)
	{
		// Change selection character format unless protected
		if(!_psel || !IsRich())
			return 0;

		return _psel->SetCharFormat(pCF, wparam, publdr, dwMask, dwMask2) 
				== NOERROR;
	}

	// Change default character format
	CCharFormat		   CF;					// Local CF to party on
	LONG			   iCF;					// Possible new CF index
	const CCharFormat *pCF1;				// Ptr to current default CF
	ICharFormatCache  *pICFCache = GetCharFormatCache();

	if(FAILED(pICFCache->Deref(Get_iCF(), &pCF1)))	// Get ptr to current
	{										//  default CCharFormat
		fRet = FALSE;						
		goto Update;
	}
	CF = *pCF1;								// Copy current default CF
	CF.Apply(pCF, dwMask, dwMask2);			// Modify copy
	if(FAILED(pICFCache->Cache(&CF, &iCF)))	// Cache modified copy
	{
		fRet = FALSE;
		goto Update;
	}

#ifndef NOLINESERVICES
	if (g_pols)
		g_pols->DestroyLine(NULL);
#endif

	pICFCache->Release(Get_iCF());			// Release _iCF regardless
	Set_iCF(iCF);							//  of whether _iCF = iCF,
											//  i.e., only 1 ref count
	if(_psel && !_psel->GetCch() && _psel->Get_iFormat() == -1)
		_psel->UpdateCaret(FALSE);

	if ((dwMask & (CFM_CHARSET | CFM_FACE)) == CFM_FACE &&
		!GetFontName(pCF->_iFont)[0] && GetFontName(CF._iFont)[0] &&
		IsBiDiCharRep(CF._iCharRep))
	{
		// Client requested font/charset be chosen for it according to thread
		// locale. If BiDi, then also set RTL para default
		CParaFormat PF;
		PF._wEffects = PFE_RTLPARA;
		OnSetParaFormat(SPF_SETDEFAULT, &PF, publdr, PFM_RTLPARA, PFM2_PARAFORMAT);
	}

Update:
	// FUTURE (alexgo): this may be unnecessary if the display handles
	// updating more automatically.
	_pdp->UpdateView();
	return fRet;
}

/*
 *	CTxtEdit::OnSetParaFormat(wparam, pPF, publdr, dwMask, dwMask2)
 *
 *	@mfunc
 *		Set new default CParaFormat
 *
 *	@rdesc
 *		LRESULT nonzero if success
 */
LRESULT CTxtEdit::OnSetParaFormat(
	WPARAM		 wparam,	//@parm wparam passed thru to IsProtected()
	CParaFormat *pPF,		//@parm CParaFormat to use
	IUndoBuilder *publdr,	//@parm Undobuilder to receive antievents
	DWORD		  dwMask,	//@parm CHARFORMAT2 mask
	DWORD		  dwMask2)	//@parm Second mask
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetParaFormat");

	// If we're using context direction in the control, then we disallow
	// the paragraph direction property and the alignment property (unless
	// it's for center alignment).
	if(IsStrongContext(_nContextDir) || IsStrongContext(_nContextAlign))
	{
		Assert(!IsRich());
		if(dwMask & (PFM_RTLPARA | PFM_ALIGNMENT))
		{
			if (IsStrongContext(_nContextAlign) &&
				(pPF->_bAlignment == PFA_LEFT || pPF->_bAlignment == PFA_RIGHT))
			{
				dwMask &= ~PFM_ALIGNMENT;
			}
			if(IsStrongContext(_nContextDir))
				dwMask &= ~PFM_RTLPARA;
		}
	}
	BOOL fMatchKbdToPara = FALSE;

	if(dwMask & PFM_RTLPARA)
	{
		// In plain text allow DIR changes to change DIR and ALIGNMENT
		if(!IsRich())
		{
			// Clear all para masks, except for DIR and ALIGN
			dwMask &= (PFM_RTLPARA | PFM_ALIGNMENT);
			wparam |= SPF_SETDEFAULT;
		}
		if(_psel && _fFocus)
			fMatchKbdToPara = TRUE;
	}
	if(!(wparam & SPF_SETDEFAULT))	
	{
		// If DEFAULT flag is specified, don't change selection
		if(!_psel || IsProtected(EM_SETPARAFORMAT, wparam, (LPARAM)pPF))
			return 0;

		LRESULT lres = NOERROR == (pPF->fSetStyle(dwMask, dwMask2)
			 ? _psel->SetParaStyle(pPF, publdr, dwMask)
			 : _psel->SetParaFormat(pPF, publdr, dwMask, dwMask2));

		// This is a bit funky, but basically, if the text is empty
		// then we also need to set the default paragraph format
		// (done in the code below).  Thus, if we hit a failure or
		// if the document is not empty, go ahead and return.  
		// Otherwise, fall through to the default case.
		if(!lres || GetAdjustedTextLength())
		{
			if(fMatchKbdToPara)
				_psel->MatchKeyboardToPara();
			return lres;
		}
	}

	// No text in document or (wparam & SPF_SETDEFAULT): set default format

	LONG			   iPF;						// Possible new PF index
	CParaFormat		   PF = *GetParaFormat(-1);	// Local PF to party on
	IParaFormatCache  *pPFCache = GetParaFormatCache();

	PF.Apply(pPF, dwMask, dwMask2);				// Modify copy
	if(FAILED(pPFCache->Cache(&PF, &iPF)))		// Cache modified copy
		return 0;
	pPFCache->Release(Get_iPF());				// Release _iPF regardless of
	Set_iPF(iPF);								// Update default format index

	if(PF.IsRtlPara())		
		OrCharFlags(FRTL, publdr);				// BiDi in backing store

	if(!IsRich() && dwMask & PFM_RTLPARA)		// Changing plain-text default PF
	{
		ItemizeDoc(publdr);						// causing re-itemize the whole doc.

		// (#6503) We cant undo the -1 format change in plaintext and that causes
		// many problems when we undo ReplaceRange event happening before the paragraph
		// switches. We better abandon the whole stack for now. (wchao)
		// -FUTURE- We should create an antievent for -1 PF change.
		ClearUndo(publdr);						
	}
	_pdp->UpdateView();
	if (_psel)
		_psel->UpdateCaret(!Get10Mode() || _psel->IsCaretInView());
	if(fMatchKbdToPara)
		_psel->MatchKeyboardToPara();
	return TRUE;
}


////////////////////////////////  System notifications  ////////////////////////////////

LRESULT CTxtEdit::OnSetFocus()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetFocus");

	_fFocus = TRUE;
	
	// Update our idea of the current keyboard layout
	W32->RefreshKeyboardLayout();

	InitKeyboardFlags();

	if(!_psel)
		return 0;

	// _fMouseDown may sometimes be true.
	// This can happen when somebody steals our focus when we were doing
	// something with the mouse down--like processing a click. Thus, we'll
	// never get the MouseUpMessage.
	if(_fMouseDown)
	{
		TRACEWARNSZ("Getting the focus, yet we think the mouse is down");
	}
	_fMouseDown = FALSE;

	BOOL fAutoKeyboard = _fAutoKeyboard;	// Don't change keyboard on SetFocus
	_fAutoKeyboard = FALSE;

	// BUG FIX #5369
	// Special case where we don't have a selection (or a caret). We need
	// to display something on focus so display a caret
	_psel->UpdateCaret(_fScrollCaretOnFocus, _psel->GetCch() == 0);
	_fScrollCaretOnFocus = FALSE;

	_psel->ShowSelection(TRUE);

	// If there is an in-place active object, we need to set the focus to
	// it. (In addition to the work that we do; this maintains compatibility
	// with RichEdit 1.0).
	if(_pobjmgr)
	{
		COleObject *pobj = _pobjmgr->GetInPlaceActiveObject();
		if(pobj)
		{
			IOleInPlaceObject *pipobj;
			
			if(pobj->GetIUnknown()->QueryInterface(IID_IOleInPlaceObject, 
					(void **)&pipobj) == NOERROR)
			{
				HWND hwnd;
				pipobj->GetWindow(&hwnd);

				if(hwnd)
					SetFocus(hwnd);
				pipobj->Release();
			}
		}
	}

	if(IsInPageView())
		TxInvalidate();
	TxNotify(EN_SETFOCUS, NULL);
	_fAutoKeyboard = fAutoKeyboard;			// Restore setting
	return 0;
}

LRESULT CTxtEdit::OnKillFocus()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnKillFocus");

    StopMagellanScroll();
    
	if(_pundo)
		_pundo->StopGroupTyping();

	if(_psel)
	{
		// Scroll back to beginning if necessary
		if (_fScrollCPOnKillFocus)
		{
			bool fHideSelectionLocal = _fHideSelection;

			// cannot hide Selection so cp=0 will be scroll into view.
			_fHideSelection = 0;	
			OnSetSel(0, 0);
			_fHideSelection = fHideSelectionLocal;
		}

		_psel->DeleteCaretBitmap(TRUE);	// Delete caret bitmap if one exists
		if(_fHideSelection)
			_psel->ShowSelection(FALSE);
	}

	_fFocus = FALSE;
	DestroyCaret();
	TxNotify(EN_KILLFOCUS, NULL);

	_fScrollCaretOnFocus = FALSE;		// Just to be safe, clear this
	return 0;
}


#if defined(DEBUG) && !defined(NOFULLDEBUG)
void CTxtEdit::OnDumpPed()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnDumpPed");

	char sz[256];
	CTxtSelection * const psel = GetSel();
	SELCHANGE selchg;
	CHARRANGE crg = {0, 0};

	psel->SetSelectionInfo(&selchg);

	LONG cPage = 0;
	LONG nPage = 0;
	if(_pdp->IsMultiLine() && IsInPageView())
	{
		LONG cPageMoved;
		CTxtRange rg(this, 0, 0);

		_pdp->GetPage(&nPage, 0, &crg);
		rg.Set(crg.cpMin, 0);
		rg.Expand(tomPage, NULL);
		Assert(rg.GetCpMost() == crg.cpMost);
		rg.Set(0, 0);
		rg.Move(tomPage, tomForward, &cPageMoved);
		rg.GetIndex(tomPage, &cPage);
		Assert(cPageMoved == cPage ||
			rg.GetCp() == GetAdjustedTextLength() && cPageMoved == cPage - 1);
	}

	wsprintfA(sz,
		"cchText = %ld		cchTextMost = %ld\r\n"
		"cpSelActive = %ld		cchSel = %ld\r\n"
		"wSelType = %x		# lines = %ld\r\n"
		"SysDefLCID = %lx		UserDefLCID = %lx\r\n"
		"Page = %ld			cPage = %ld\r\n"
		"cpMinPage = %ld		cpMostPage = %ld",
		GetTextLength(),	TxGetMaxLength(),
		psel->GetCp(),	psel->GetCch(),
		selchg.seltyp,	_pdp->LineCount(),
		GetSystemDefaultLCID(), GetUserDefaultLCID(),
		nPage, cPage, crg.cpMin, crg.cpMost
	);
	Tracef(TRCSEVINFO, "%s", sz);
	MessageBoxA(0, sz, "ED", MB_OK);
}
#endif					// DEBUG


///////////////////////////// Scrolling Commands //////////////////////////////////////


HRESULT CTxtEdit::TxLineScroll(
	LONG cli,
	LONG cch)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxLineScroll");

	// Currently cch does nothing in the following call, so we ignore
	// its translation from cach to cch (need to instantiate an rtp
	// for the current line
	_pdp->LineScroll(cli, cch);
	return S_OK;
}

void CTxtEdit::OnScrollCaret()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnScrollCaret");

	if(_psel)
	{
		_psel->SetForceScrollCaret(TRUE);
        _psel->UpdateCaret(TRUE);
		_psel->SetForceScrollCaret(FALSE);
	}
}


///////////////////////////////// Editing messages /////////////////////////////////

void CTxtEdit::OnClear(
	IUndoBuilder *publdr)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnClear");

	if(!_psel || TxGetReadOnly())
	{
		Beep();
		return;
	}
	
	if(_psel->GetCch() && !IsProtected(WM_CLEAR, 0, 0))
	{
		_psel->StopGroupTyping();
		_psel->ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE);
	}
}

void CTxtEdit::Beep()
{
	if(_fAllowBeep)
		MessageBeep(0);
}


///////////////////////////////////  Miscellaneous  ///////////////////////////////////////////

/*
 *	CTxtEdit::ItemizeDoc(publdr, cchRange)
 *
 *	@mfunc
 *		Helper routine to itemize the cchRange size of document content
 *		called by various clients outside CTxtRange.
 */
void CTxtEdit::ItemizeDoc(
	IUndoBuilder *	publdr,
	LONG			cchRange)
{
	// If cchRange = -1, itemize the whole doc
	if (cchRange == -1)
		cchRange = GetTextLength();

	// We wouldnt itemize if the doc only contains a single EOP
	// because we dont want Check_rpPF being called when the -1
	// PF format hasnt been properly established.
	// This is kind of hack, should be removed in the future.
	//

	if(cchRange && GetAdjustedTextLength())
	{										// Only itemize if more than
		CTxtRange rg(this, 0, -cchRange);	//  final EOP
		rg.ItemizeRuns(publdr);
	}

#if 0
	// =FUTURE=
	//		Once we open SPF_SETDEFAULT to public. We shall incorporate this code.
	// Basically, one can change the default paragraph reading order at runtime. All
	// PF runs referencing to -1 PF format then need to be reassigned a new paragraph
	// level value and reitemized.(6-10-99, wchao)
	//
    if(cchRange > 0)
    {
        CTxtRange rg(this, 0, -cchRange);

		// -1 PF format may have changed.
		// We shall make sure that the level of each PF run match the reading order
		// before start itemization.
		//
		if (rg.Check_rpPF())
		{
			LONG	cchLeft = cchRange;
			LONG	cchMove = 0;
			LONG	cch;

			while (cchLeft > 0)
			{
				rg._rpPF.GetRun(0)->_level._value = rg.IsParaRTL() ? 1 : 0;

				cch = rg._rpPF.GetCchLeft();

				if (!rg._rpPF.NextRun())
					break;		// no more run

				cchMove += cch;
				cchLeft -= cch;
			}

			Assert (cchMove + cchLeft == cchRange);

			rg._rpPF.Move(-cchMove);	// fly back to cp = 0
		}

		// Now we rerun itemization
        rg.ItemizeRuns(publdr);
    }
#endif
}

/*
 *  CTxtEdit::OrCharFlags(dwFlags, publdr)
 *
 *	@mfunc
 *		Or in new char flags and activate LineServices and Uniscribe
 *		if complex script chars occur.
 */
void CTxtEdit::OrCharFlags(
    QWORD qwFlags,
    IUndoBuilder* publdr)
{
#ifndef NOCOMPLEXSCRIPTS
	// REVIEW: Should we send a notification for LS turn on?
	// Convert dwFlags to new on flags
	qwFlags &= qwFlags ^ _qwCharFlags;
	if(qwFlags)
	{
		_qwCharFlags |= qwFlags;			// Update flags

		qwFlags &= FCOMPLEX_SCRIPT;

		if(qwFlags && (_qwCharFlags & FCOMPLEX_SCRIPT) == qwFlags)
		{
			// REVIEW: Need to check if Uniscribe and LineServices are available...
			OnSetTypographyOptions(TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
			ItemizeDoc();
            // FUTURE: (#6838) We cant undo operations before the first itemization.
            ClearUndo(publdr);
			_fAutoKeyboard = IsBiDi();
		}

		UINT brk = 0;

		if (qwFlags & FNEEDWORDBREAK)
			brk += BRK_WORD;

		if (qwFlags & FNEEDCHARBREAK)
			brk += BRK_CLUSTER;

		if (brk)
		{
			CUniscribe* pusp = Getusp();

			if (!_pbrk && pusp && pusp->IsValid())
			{
				// First time detecting the script that needs word/cluster-breaker 
				// (such as Thai, Indic, Lao etc.)
				_pbrk = new CTxtBreaker(this);
				Assert(_pbrk);
			}

			if (_pbrk && _pbrk->AddBreaker(brk))
			{
				// Sync up the breaking array(s)
				_pbrk->Refresh();
			}
		}
	}
#endif
}

/*
 *	CTxtEdit::OnSetTypographyOptions(wparam, lparam)
 *
 *	@mfunc
 *		If CTxtEdit isn't a password or accelerator control and wparam
 *		differs from _bTypography, update the latter and the view.
 *
 *	@rdesc
 *		HRESULT = S_OK
 */
HRESULT CTxtEdit::OnSetTypographyOptions(
	WPARAM wparam,		//@parm Typography flags
	LPARAM lparam)		//@parm Typography mask
{
	// Validate params
	if(wparam & ~(TO_SIMPLELINEBREAK | TO_ADVANCEDTYPOGRAPHY | TO_DISABLECUSTOMTEXTOUT | TO_ADVANCEDLAYOUT))
		return E_INVALIDARG;

	DWORD dwTypography = _bTypography & ~lparam;	// Kill current flag values
	dwTypography |= wparam & lparam;				// Or in new values

	if(_cpAccelerator == -1 && _bTypography != (BYTE)dwTypography)
	{
		_bTypography = (BYTE)dwTypography;
		_pdp->InvalidateRecalc();
		TxInvalidate();
	}
	return S_OK;
}

void CTxtEdit::TxGetViewInset(
	RECTUV *prc,
	const CDisplay *pdp) const
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetViewInset");
	
	// Get inset, which is in HIMETRIC
	RECTUV rcHimetric;

	if(SUCCEEDED(_phost->TxGetViewInset((RECT*) &rcHimetric)))
	{
		if(!pdp)						// If no display is specified,
			pdp = _pdp;					//  use main display

		AssertSz(pdp->IsValid(), "CTxtEdit::TxGetViewInset Device not valid");

		prc->left	= pdp->HimetricUtoDU(rcHimetric.left);
		prc->top	= pdp->HimetricVtoDV(rcHimetric.top);
		prc->right	= pdp->HimetricUtoDU(rcHimetric.right);
		prc->bottom = pdp->HimetricVtoDV(rcHimetric.bottom);
	}
	else
	{
		// The call to the host failed. While this is highly improbable, we do 
		// want to something reasonably sensible. Therefore, we will just pretend 
		// there is no inset and continue.
		ZeroMemory(prc, sizeof(RECTUV));
	}
}


//
//	helper functions. FUTURE (alexgo) maybe we should get rid of
//  some of these
//

/*	FUTURE (murrays): Unless they are called a lot, the TxGetBit routines
	might be done more compactly as:

BOOL CTxtEdit::TxGetBit(
	DWORD dwMask)
{
	DWORD dwBits = 0;
	_phost->TxGetPropertyBits(dwMask, &dwBits);
	return dwBits != 0;
}

e.g., instead of TxGetSelectionBar(), we use TxGetBit(TXTBIT_SELECTIONBAR).
If they are called a lot (like TxGetSelectionBar()), the bits should probably
be cached, since that saves a bunch of cache misses incurred in going over to
the host.

*/

BOOL CTxtEdit::IsLeftScrollbar() const	
{
	if(!_fHost2)
		return FALSE;

#ifndef NOCOMPLEXSCRIPTS
	DWORD dwStyle, dwExStyle;

	_phost->TxGetWindowStyles(&dwStyle, &dwExStyle);
	return dwExStyle & WS_EX_LEFTSCROLLBAR;
#else
	return FALSE;
#endif
}

TXTBACKSTYLE CTxtEdit::TxGetBackStyle() const					
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetBackStyle");

	TXTBACKSTYLE style = TXTBACK_OPAQUE;
	_phost->TxGetBackStyle(&style);
	return style;
}

BOOL CTxtEdit::TxGetAutoSize() const					
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetAutoSize");

	return (_dwEventMask & ENM_REQUESTRESIZE);
}

BOOL CTxtEdit::TxGetAutoWordSel() const				
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetAutoWordSel");

	DWORD dwBits = 0;
	_phost->TxGetPropertyBits(TXTBIT_AUTOWORDSEL, &dwBits);
	return dwBits != 0;
}

DWORD CTxtEdit::TxGetMaxLength() const					
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetMaxLength");

	// Keep this a DWORD in case client uses a cpMost of 0xFFFFFFFF, which is
	// admittedly a little large, at least for 32-bit address spaces!
	// tomForward would be a more reasonable max length, altho it's also
	// probably larger than possible in a 32-bit address space.
	return _cchTextMost;
}

/*
 *	CTxtEdit::TxSetMaxToMaxText(LONG cExtra)
 *
 *	@mfunc
 *		Set new maximum text length based on length of text and possibly extra chars
 *		to accomodate.
 */
void CTxtEdit::TxSetMaxToMaxText(LONG cExtra)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxSetMaxToMaxText");

	// See if we need to update the text max
	LONG cchRealLen = GetAdjustedTextLength() + cExtra;

	if(_fInOurHost && _cchTextMost < (DWORD)cchRealLen)
		_cchTextMost = cchRealLen;
}

WCHAR CTxtEdit::TxGetPasswordChar() const
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetPasswordChar");

	if(_fUsePassword)
	{
		WCHAR ch = L'*';
		_phost->TxGetPasswordChar(&ch);

		// We don't allow these characters as password chars
		if(ch < 32 || ch == WCH_EMBEDDING)
			return L'*';
		return ch;
	}
	return 0;
}

void CTxtEdit::TxGetClientRect(RECTUV *prc) const
{
	RECT rc;
	_phost->TxGetClientRect(&rc);
	_pdp->RectuvFromRect(*prc, rc);
}

BOOL CTxtEdit::TxShowScrollBar(INT fnBar, BOOL fShow)
{
	//Convert scrollbar bits from logical to physical
	if (IsUVerticalTflow(_pdp->GetTflow()))
		fnBar = fnBar == SB_HORZ ? SB_VERT : SB_HORZ;

	return _phost->TxShowScrollBar(fnBar, fShow);
}

BOOL CTxtEdit::TxEnableScrollBar (INT fnBar, INT fuArrowFlags)
{
	//Convert scrollbar bits from logical to physical
	if (IsUVerticalTflow(_pdp->GetTflow()))
		fnBar = fnBar == SB_HORZ ? SB_VERT : SB_HORZ;

	return _phost->TxEnableScrollBar(fnBar, fuArrowFlags);
}

BOOL CTxtEdit::TxSetScrollRange(INT fnBar, LONG nMinPos, INT nMaxPos, BOOL fRedraw)
{
	//Convert scrollbar bits from logical to physical
	if (IsUVerticalTflow(_pdp->GetTflow()))
		fnBar = fnBar == SB_HORZ ? SB_VERT : SB_HORZ;

	return _phost->TxSetScrollRange(fnBar, nMinPos, nMaxPos, fRedraw);
}

BOOL CTxtEdit::TxSetScrollPos (INT fnBar, INT nPos, BOOL fRedraw)
{
	//Convert scrollbar bits from logical to physical
	if (IsUVerticalTflow(_pdp->GetTflow()))
		fnBar = fnBar == SB_HORZ ? SB_VERT : SB_HORZ;

	return _phost->TxSetScrollPos(fnBar, nPos, fRedraw);
}

BOOL CTxtEdit::TxSetCaretPos(INT u, INT v)
{
	POINTUV ptuv = {u, v};
	POINT pt;
	_pdp->PointFromPointuv(pt, ptuv);
	return _phost->TxSetCaretPos(pt.x, pt.y);
}

DWORD CTxtEdit::TxGetScrollBars() const					
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetScrollBars");

	DWORD dwScroll;
	_phost->TxGetScrollBars(&dwScroll);

	//Convert scrollbar bits from physical to logical
	if (IsUVerticalTflow(_pdp->GetTflow()))
	{
		DWORD dwScrollT = dwScroll;

		dwScroll &= ~(WS_HSCROLL | WS_VSCROLL);

		if (dwScrollT & WS_VSCROLL)
			dwScroll |= WS_HSCROLL;

		if (dwScrollT & WS_HSCROLL)
			dwScroll |= WS_VSCROLL;
	}

	return dwScroll;
}

LONG CTxtEdit::TxGetSelectionBarWidth() const				
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetSelectionBarWidth");
	
	LONG lSelBarWidth = 0;
	_phost->TxGetSelectionBarWidth(&lSelBarWidth);
	return lSelBarWidth;
}

BOOL CTxtEdit::TxGetWordWrap() const
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetWordWrap");

	DWORD dwBits = 0;
	_phost->TxGetPropertyBits(TXTBIT_WORDWRAP, &dwBits);
	return dwBits != 0;
}

BOOL CTxtEdit::TxGetSaveSelection() const
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::TxGetSaveSelection");

	DWORD dwBits = 0;
	_phost->TxGetPropertyBits(TXTBIT_SAVESELECTION, &dwBits);
	return dwBits != 0;
}

/* 
 *	CTxtEdit::ClearUndo()
 *
 *	@mfunc	Clear all undo buffers
 */
void CTxtEdit::ClearUndo(
	IUndoBuilder *publdr)	//@parm the current undo context (may be NULL)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::ClearUndo");

	if(_pundo)
		_pundo->ClearAll();

	if(_predo)
		_predo->ClearAll();

	if(publdr)
		publdr->Discard();
}

/////////////////////////////// ITextHost2 Extensions //////////////////////////////

/*
 *	CTxtEdit::TxIsDoubleClickPending ()
 *
 *	@mfunc	calls host via ITextHost2 to find out if double click is pending.
 *
 *	@rdesc 	TRUE/FALSE
 */
BOOL CTxtEdit::TxIsDoubleClickPending()
{
	return _fHost2 ? _phost->TxIsDoubleClickPending() : FALSE;
}			

/*
 *	CTxtEdit::TxGetWindow(phwnd)
 *
 *	@mfunc	calls host via ITextHost2 to get current window for this edit
 *			instance.  This is very helpful for	OLE object support
 *
 *	@rdesc	HRESULT
 */
HRESULT	CTxtEdit::TxGetWindow(
	HWND *phwnd)
{
	return _fHost2 ? _phost->TxGetWindow(phwnd) : E_NOINTERFACE;
}

/*
 *	CTxtEdit::TxSetForegroundWindow ()
 *
 *	@mfunc	calls host via ITextHost2 to make our window the foreground
 *			window. Used to support drag/drop.
 *
 *	@rdesc	HRESULT
 */
HRESULT	CTxtEdit::TxSetForegroundWindow()
{
	return _fHost2 ? _phost->TxSetForegroundWindow() : E_NOINTERFACE;
}

/*
 *	CTxtEdit::TxGetPalette()
 *
 *	@mfunc	calls host via ITextHost2 to get current palette
 *
 *	@rdesc	HPALETTE
 */
HPALETTE CTxtEdit::TxGetPalette()
{
	return _fHost2 ? _phost->TxGetPalette() : NULL;
}

/*
 *	CTxtEdit::TxGetFEFlags(pFEFlags)
 *
 *	@mfunc	calls host via ITextHost2 to get current FE settings
 *
 *	@rdesc	HRESULT
 */
HRESULT	CTxtEdit::TxGetFEFlags(
	LONG *pFEFlags)
{
#ifndef NOFEPROCESSING
	*pFEFlags = 0;						// In case no ITextHost2 methods

	HRESULT hResult = _fHost2 ? _phost->TxGetFEFlags(pFEFlags) : E_NOINTERFACE;

	if (hResult == NOERROR || hResult == E_NOINTERFACE)
	{
		if (Get10Mode())
			*pFEFlags |= tomRE10Mode;
		if (_fUseAtFont)
			*pFEFlags |= tomUseAtFont;
		if (_fUsePassword)
			*pFEFlags |= tomUsePassword;
		*pFEFlags |= (_pdp->GetTflow()) << 2;
	}
	return hResult;
#else
	return E_NOINTERFACE;
#endif
}

/*
 *	CTxtEdit::TxSetCursor(hcur, fText)
 *
 *	@mfunc	calls host via ITextHost2 to set cursor
 *
 *	@rdesc	HCURSOR
 */
HCURSOR CTxtEdit::TxSetCursor(
	HCURSOR	hcur,
	BOOL	fText)
{
	return _fHost2 ? _phost->TxSetCursor2(hcur, fText) : ::SetCursor(hcur);
}


//
//	Event Notification methods
//

/*
 *	CTxtEdit::TxNotify(iNotify, pv)
 *
 *	@mfunc	This function checks bit masks and sends notifications to the
 *			host.
 *
 *	@devnote	Callers should check to see if a special purpose notification
 *			method has already been provided.
 *
 *	@rdesc	S_OK, S_FALSE, or some error
 */
HRESULT CTxtEdit::TxNotify(
	DWORD iNotify, 		//@parm Notification to send
	void *pv)			//@parm Data associated with notification
{
	// First, disallow notifications that we handle elsewhere
	Assert(iNotify != EN_SELCHANGE); 	//see SetSelectionChanged
	Assert(iNotify != EN_ERRSPACE);		//see SetOutOfMemory
	Assert(iNotify != EN_CHANGE);		//see SetChangedEvent
	Assert(iNotify != EN_HSCROLL);		//see SendScrollEvent
	Assert(iNotify != EN_VSCROLL);		//see SendScrollEvent
	Assert(iNotify != EN_MAXTEXT);		//see SetMaxText
	Assert(iNotify != EN_MSGFILTER);	//this is handled specially
										// in TxSendMessage

	// Switch on the event to check masks.  

	DWORD dwMask;
	switch(iNotify)
	{
		case EN_DROPFILES:
			dwMask = ENM_DROPFILES;
			goto Notify;

		case EN_PROTECTED:
			dwMask = ENM_PROTECTED;
			goto Notify;

		case EN_REQUESTRESIZE:
			dwMask = ENM_REQUESTRESIZE;
			goto Notify;

		case EN_PARAGRAPHEXPANDED:
			dwMask = ENM_PARAGRAPHEXPANDED;
			goto Notify;

		case EN_IMECHANGE:
			if (!Get10Mode())
				return S_FALSE;
			dwMask = ENM_IMECHANGE;
			goto Notify;

		case EN_PAGECHANGE:
			dwMask = ENM_PAGECHANGE;
			goto Notify;

		case EN_UPDATE:
		    if (!Get10Mode())
		        break;
		    dwMask = ENM_UPDATE;
		    //FALL THROUGH CASE

		Notify:
			if(!(_dwEventMask & dwMask))
				return NOERROR;			
	}
	return _phost->TxNotify(iNotify, pv);
}
			
/*
 *	CTxtEdit::SendScrollEvent(iNotify)
 *
 *	@mfunc	Sends scroll event if appropriate
 *
 *	@comm	Scroll events must be sent before any view updates have
 *			been requested and only if ENM_SCROLL is set.
 */
void CTxtEdit::SendScrollEvent(
	DWORD iNotify)		//@parm Notification to send
{
	Assert(iNotify == EN_HSCROLL || iNotify == EN_VSCROLL);

	// FUTURE (alexgo/ricksa).  The display code can't really
	// handle this assert yet.  Basically, we're trying to
	// say that scrollbar notifications have to happen
	// _before_ the window is updated.  When we do the
	// display rewrite, try to handle this better.

	// Assert(_fUpdateRequested == FALSE);

	if(_dwEventMask & ENM_SCROLL)
		_phost->TxNotify(iNotify, NULL);
}

/*
 *	CTxtEdit::HandleLowFiRTF (szControl)
 *
 *	@mfunc	Handles sending EN_LOWFIRTF notifications.
 *
 *	@rdesc	TRUE if the EN_LOWFIRTF message was sent and 
 *			processed successfully.
 */
BOOL CTxtEdit::HandleLowFiRTF(
	char * szControl)		//@parm	RTF control word prompting notification
{
	if(!(_dwEventMask & ENM_LOWFIRTF))
		return FALSE;

	ENLOWFIRTF enLowFiRTF;

	ZeroMemory(&enLowFiRTF, sizeof(enLowFiRTF));
	enLowFiRTF.nmhdr.code = EN_LOWFIRTF;
	enLowFiRTF.szControl = szControl;
	return _phost->TxNotify(EN_LOWFIRTF, &enLowFiRTF) == S_FALSE;
}

/*
 *	CTxtEdit::HandleLinkNotification (msg, wparam, lparam, pfInLink)
 *
 *	@mfunc	Handles sending EN_LINK notifications.
 *
 *	@rdesc	TRUE if the EN_LINK message was sent and 
 *			processed successfully.  Typically, that means the
 *			caller should stop whatever processing it was doing.
 */
BOOL CTxtEdit::HandleLinkNotification(
	UINT	msg,		//@parm	msg prompting the link notification
	WPARAM	wparam,		//@parm wparam of the message
	LPARAM	lparam,		//@parm lparam of the message
	BOOL *	pfInLink)	//@parm if non-NULL, indicate if over a link
{
	if(pfInLink)
		*pfInLink = FALSE;

	if(!(_dwEventMask & ENM_LINK) || !_fInPlaceActive)
		return FALSE;

	LONG cp;

	if(msg == WM_CHAR)
	{
		if(!_psel->GetCp() && !_psel->GetCch())
			return FALSE;

		_psel->_rpCF.AdjustBackward();
		DWORD dwEffectsPrev = _psel->GetCF()->_dwEffects;
		_psel->_rpCF.AdjustForward();

		if (!(dwEffectsPrev & CFE_LINK) || (CFE_LINKPROTECTED | CFE_HIDDEN)
			 == (dwEffectsPrev & (CFE_LINKPROTECTED | CFE_HIDDEN)) ||
			!(_psel->GetCF()->_dwEffects & CFE_LINK))
		{
			return FALSE;
		}
		cp = _psel->GetCp();
	}
	else
	{
		HITTEST Hit;
		POINT	ptxy = {LOWORD(lparam), HIWORD(lparam)};
		POINTUV	pt;
		if(msg == WM_SETCURSOR)
		{
			GetCursorPos(&ptxy);
			if(!_phost->TxScreenToClient(&ptxy))
				return FALSE;
		}
		_pdp->PointuvFromPoint(pt, ptxy);
		cp = _pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, &Hit);

		if(Hit != HT_Link)					// Not a hyperlink
			return FALSE;
	}

	LONG	  cpMin, cpMost;			// It's a hyperlink
	ENLINK	  enlink;
	CTxtRange rg(this, cp, 0);

	ZeroMemory(&enlink, sizeof(enlink));
	enlink.nmhdr.code = EN_LINK;

	if(pfInLink)
		*pfInLink = TRUE;

	rg.SetIgnoreFormatUpdate(TRUE);
	rg.Expander(tomLink, TRUE, NULL, &cpMin, &cpMost);

	//If the previous character of a link is hidden, then
	//this came in as an RTF hyperlink field, so just export
	//the hidden text to the client to be passed to the browser.
	rg.SetCp(cpMin + 1, FALSE);
	if (rg.GetCF()->_dwEffects & CFE_HIDDEN)
	{
		rg.Expander(tomHidden, TRUE, NULL, &cpMin, &cpMost);
		rg.SetCp(cpMin, FALSE);
		WCHAR ch;

		//Go to end of hyperlink (search for k)
		if (rg.CRchTxtPtr::FindText(cpMost, FR_DOWN, L"K", 1) == -1)
			return FALSE;

		cpMin = rg.GetCp();

		//Strip off quotes and spaces
		while ((ch = rg.CRchTxtPtr::GetChar()) == ' ' || ch == '\"')
		{
			rg.Move(1, FALSE);
			cpMin++;
		}

		//Find end of hyperlink. Do not just start from the end as
		//a fldinst can contain stuff which isn't part of the hyperlink
		WCHAR chPrev = rg.CRchTxtPtr::GetPrevChar();

		if (rg.CRchTxtPtr::FindText(cpMost, FR_DOWN, &chPrev, 1) == -1)
			return FALSE;

		cpMost = rg.GetCp() - 1;
	}

	// Fill in ENLINK data structure for our EN_LINK
	// callback asking client what we should do 
	enlink.msg = msg;
	enlink.wParam = wparam;
	enlink.lParam = lparam;
	enlink.chrg.cpMin  = GetAcpFromCp(cpMin);
	enlink.chrg.cpMost = GetAcpFromCp(cpMost);

	if(msg == WM_CHAR)							// Need to send both down and up
	{											//  msgs, since Outlook responds
		enlink.msg = WM_LBUTTONDOWN;			//  to down and others to up
		_phost->TxNotify(EN_LINK, &enlink);
		enlink.msg = WM_LBUTTONUP;
	}
	return _phost->TxNotify(EN_LINK, &enlink) == S_FALSE;
}

/*
 *	CTxtEdit::QueryUseProtection(prg, msg, wparam, lparam)
 *
 *	@mfunc	sends EN_PROTECTED to the host, asking if we should continue
 *	to honor the protection on a given range of characters
 *
 *	@rdesc	TRUE if protection should be honored, FALSE otherwise
 */
BOOL CTxtEdit::QueryUseProtection(
	CTxtRange *prg,	 	//@parm range to check for
	UINT	msg,   		//@parm msg used
	WPARAM	wparam,		//@parm wparam of the msg
	LPARAM 	lparam)		//@parm lparam of the msg
{
	LONG		cpMin, cpMost;
	ENPROTECTED enp;
	BOOL		fRet = FALSE;
	CCallMgr *	pcallmgr = GetCallMgr();

	Assert(_dwEventMask & ENM_PROTECTED);

	if( pcallmgr->GetInProtected() ||
		_fSuppressNotify)		// Don't ask host if we don't want to send notification
		return FALSE;

	pcallmgr->SetInProtected(TRUE);

	ZeroMemory(&enp, sizeof(ENPROTECTED));
	
	prg->GetRange(cpMin, cpMost);

	enp.msg = msg;
	enp.wParam = wparam;
	enp.lParam = lparam;
	enp.chrg.cpMin  = GetAcpFromCp(cpMin);
	enp.chrg.cpMost = GetAcpFromCp(cpMost);

	if(_phost->TxNotify(EN_PROTECTED, &enp) == S_FALSE)
		fRet = TRUE;

	pcallmgr->SetInProtected(FALSE);

	return fRet;
}


#ifdef DEBUG
//This is a debug api used to dump the document runs.
//If a pointer to the ped is passed, it is saved and
//used.  If NULL is passed, the previously saved ped
//pointer is used.  This allows the "context" to be
//setup by a function that has access to the ped and
//DumpDoc can be called lower down in a function that
//does not have access to the ped.
extern "C" {
void DumpStory(void *ped)
{
    static CTxtEdit *pedSave = (CTxtEdit *)ped;
    if(pedSave)
    {
        CTxtStory * pStory = pedSave->GetTxtStory();
        if(pStory)
            pStory->DbgDumpStory();

		CObjectMgr * pobjmgr = pedSave->GetObjectMgr();
        if(pobjmgr)
            pobjmgr->DbgDump();
    }
}
}
#endif

/*
 *	CTxtEdit::TxGetDefaultCharFormat (pCF)
 *
 *	@mfunc	helper function to retrieve character formats from the
 *			host.  Does relevant argument checking
 *
 *	@rdesc	HRESULT
 */
HRESULT CTxtEdit::TxGetDefaultCharFormat(
	CCharFormat *pCF,		//@parm Character format to fill in
	DWORD &		 dwMask)	//@parm Mask supplied by host or default
{
	HRESULT hr = pCF->InitDefault(0);
	dwMask = CFM_ALL2;

	const CHARFORMAT2 *pCF2 = NULL;

	if (_phost->TxGetCharFormat((const CHARFORMAT **)&pCF2) != NOERROR ||
		!IsValidCharFormatW(pCF2))
	{
		return hr;
	}

	dwMask  = pCF2->dwMask;
	DWORD dwMask2 = 0;
	if(pCF2->cbSize == sizeof(CHARFORMAT))
	{
		// Suppress CHARFORMAT2 specifications (except for Forms^3 disabled)
		dwMask	&= fInOurHost() ? CFM_ALL : (CFM_ALL | CFM_DISABLED);
		dwMask2 = CFM2_CHARFORMAT;
	}

	CCharFormat CF;							// Transfer external CHARFORMAT(2)
	CF.Set(pCF2, 1200);						//  parms to internal CCharFormat
	return pCF->Apply(&CF, dwMask, dwMask2);
}

/*
 *	CTxtEdit::TxGetDefaultParaFormat (pPF)
 *
 *	@mfunc	helper function to retrieve  paragraph formats.  Does
 *			the relevant argument checking.
 *
 *	@rdesc	HRESULT
 */
HRESULT CTxtEdit::TxGetDefaultParaFormat(
	CParaFormat *pPF)		//@parm Paragraph format to fill in
{
	HRESULT hr = pPF->InitDefault(0);

	const PARAFORMAT2 *pPF2 = NULL;

	if (_phost->TxGetParaFormat((const PARAFORMAT **)&pPF2) != NOERROR ||
		!IsValidParaFormat(pPF2))
	{
		return hr;
	}

	DWORD dwMask  = pPF2->dwMask;
	DWORD dwMask2 = 0;
	if(pPF2->cbSize == sizeof(PARAFORMAT))	// Suppress all but PARAFORMAT
	{										//  specifications
		dwMask &= PFM_ALL;
		dwMask2 = PFM2_PARAFORMAT;			// Tell Apply() that PARAFORMAT
	}										//  was used

	CParaFormat PF;							// Transfer external PARAFORMAT(2)
	PF.Set(pPF2);							//  parms to internal CParaFormat
	return pPF->Apply(&PF, dwMask, dwMask2);// Apply parms identified by dwMask
}										 


/*
 *	CTxtEdit::SetContextDirection(fUseKbd)
 *
 *	@mfunc
 *		Determine the paragraph direction and/or alignment based on the context
 *		rules (direction/alignment follows first strong character in the
 *		control) and apply this direction and/or alignment to the default
 *		format.
 *
 *	@comment
 *		Context direction only works for plain text controls. Note that 
 *		this routine only switches the default CParaFormat to RTL para if it
 *		finds an RTL char. IsBiDi() will automatically be TRUE for this case,
 *		since each char is checked before entering the backing store.
 */
void CTxtEdit::SetContextDirection(
	BOOL fUseKbd)		//@parm Use keyboard to set context when CTX_NEUTRAL
{
	// It turns out that Forms^3 can send EM_SETBIDIOPTIONS even for non BiDi controls.
	// AssertSz(IsBiDi(), "CTxtEdit::SetContextDirection called for nonBiDi control");
	if(IsRich() || !IsBiDi() || _nContextDir == CTX_NONE && _nContextAlign == CTX_NONE)
		return;

	LONG	cch = GetTextLength();
	CTxtPtr tp(this, 0);
	WCHAR	ch = tp.GetChar();
	WORD	ctx = CTX_NEUTRAL;
	BOOL	fChanged = FALSE;

	// Find first strongly directional character
	while (cch && !IsStrongDirectional(MECharClass(ch)))
	{
		ch = tp.NextChar();
		cch--;
	}

	// Set new context based on first strong character
	// if no strong charactes in ctrl and have Bidi keybd, then make RTL 
	if(cch)
		ctx = IsRTL(MECharClass(ch)) ? CTX_RTL : CTX_LTR;
	else
		ctx = (W32->IsBiDiLcid(LOWORD(GetKeyboardLayout(0)))) ? CTX_RTL : CTX_LTR;
	
	// Has context direction or alignment changed?
	if (_nContextDir   != CTX_NONE && _nContextDir   != ctx ||
		_nContextAlign != CTX_NONE && _nContextAlign != ctx)
	{
		// Start with current default CParaFormat
		CParaFormat PF = *GetParaFormat(-1);

		// If direction has changed...
		if(_nContextDir != CTX_NONE && _nContextDir != ctx)
		{
			if(ctx == CTX_LTR || ctx == CTX_RTL || fUseKbd)
			{
				if (ctx == CTX_RTL ||
					ctx == CTX_NEUTRAL && W32->IsBiDiLcid(LOWORD(GetKeyboardLayout(0))))
				{
					PF._wEffects |= PFE_RTLPARA;
				}
				else
				{
					Assert(ctx == CTX_LTR || ctx == CTX_NEUTRAL);
					PF._wEffects &= ~PFE_RTLPARA;
				}
				fChanged = TRUE;
			}
			_nContextDir = ctx;
		}

		// If the alignment has changed...
		if(_nContextAlign != CTX_NONE && _nContextAlign != ctx)
		{
			if(PF._bAlignment != PFA_CENTER)
			{
				if(ctx == CTX_LTR || ctx == CTX_RTL || fUseKbd)
				{
					if (ctx == CTX_RTL ||
						ctx == CTX_NEUTRAL && W32->IsBiDiLcid(LOWORD(GetKeyboardLayout(0))))
					{
						PF._bAlignment = PFA_RIGHT;
					}
					else
					{
						Assert(ctx == CTX_LTR || ctx == CTX_NEUTRAL);
						PF._bAlignment = PFA_LEFT;
					}
				}
			}
			_nContextAlign = ctx;
		}

		// Modify default CParaFormat
		IParaFormatCache *pPFCache = GetParaFormatCache();
		LONG iPF;

		if(SUCCEEDED(pPFCache->Cache(&PF, &iPF)))
		{
			pPFCache->Release(Get_iPF());	// Release _iPF regardless of
			Set_iPF(iPF);					// Update default format index
	
			if (fChanged)
				ItemizeDoc(NULL);

			// Refresh display
			Assert(_pdp);
			if(!_pdp->IsPrinter())
			{
				_pdp->InvalidateRecalc();
				TxInvalidate();
			}
		}
	}

	// Reset the first strong cp.
	_cpFirstStrong = tp.GetCp();

	Assert(_nContextDir != CTX_NONE || _nContextAlign != CTX_NONE);
}

/*
 *	CTxtEdit::GetAdjustedTextLength ()
 *
 *	@mfunc
 *		retrieve text length adjusted for the default end-of-document marker
 *
 *	@rdesc
 *		Text length without final EOP
 *
 *	@devnote
 *		For Word and RichEdit compatibility, we insert a CR or CRLF at the
 *		end of every new rich-text control.  This routine calculates the
 *		length of the document _without_ this final EOD marker.
 *
 *		For 1.0 compatibility, we insert a CRLF.  However, TOM (and Word)
 *		requires that we use a CR, from 2.0 on, we do that instead.
 */
LONG CTxtEdit::GetAdjustedTextLength()
{
	LONG cchAdjText = GetTextLength();

	Assert(!Get10Mode() || IsRich());		// No RE10 plain-text controls

	if(IsRich())
		cchAdjText -= fUseCRLF() ? 2 : 1;	// Subtract cch of final EOP

	return cchAdjText;
}

/*
 *	CTxtEdit::Set10Mode()
 *
 *	@mfunc
 *		Turns on the 1.0 compatibility mode bit.  If the control is
 *		rich text, it already has a default 'CR' at the end, which
 *		needs to turn into a CRLF for compatibility with RichEdit 1.0.
 *
 *	@devnote
 *		This function should only be called _immediately_ after
 *		creation of text services and before all other work.  There
 *		are Asserts to help ensure this.  Remark (murrays): why not
 *		allow the change provided the control is empty except for the
 *		final CR?
 *
 *		FUTURE: we might want to split _f10Mode into three flags:
 *		1) _fMapCps		// API cp's are MBCS and need conversion to Unicode 
 *		2) _fCRLF		// Use CRLFs for EOPs instead of CRs
 *		3) _f10Mode		// All other RE 1.0 compatibility things
 *
 *		Category 3 includes 1) automatically using FR_DOWN in searches,
 *		2) ignoring direction in CDataTransferObj::EnumFormatEtc(),
 *		3) not resetting _fModified when switching to a new doc,
 */
void CTxtEdit::Set10Mode()
{
	CCallMgr	callmgr(this);
	_f10Mode = TRUE;

	// Make sure nothing important has happened to the control.
	// If these values are non-NULL, then somebody is probably trying
	// to put us into 1.0 mode after we've already done work as
	// a 2.0 control.	
	Assert(GetTextLength() == cchCR);
	Assert(_psel == NULL);
	Assert(_fModified == NULL);

	SetRichDocEndEOP(cchCR);

	if(!_pundo)
		CreateUndoMgr(1, US_UNDO);

	if(_pundo)
		((CUndoStack *)_pundo)->EnableSingleLevelMode();

	// Turn off dual font
	_fDualFont = FALSE;

	// Turn on auto sizing for NTFE systems
	if (OnWinNTFE())
		_fAutoFontSizeAdjust = TRUE;
}

/*
 *	CTxtEdit::SetRichDocEndEOP(cchToReplace)
 *
 *	@mfunc	Place automatic EOP at end of a rich text document.
 */
void CTxtEdit::SetRichDocEndEOP(
	LONG cchToReplace)
{
	CRchTxtPtr rtp(this, 0);

	// Assume this is a 2.0 Doc
	LONG cchEOP = cchCR;
	const WCHAR *pszEOP = szCR;

	if(_f10Mode)
	{
		// Reset update values for a 1.0 doc
		cchEOP = cchCRLF;
		pszEOP = szCRLF;
	}

	rtp.ReplaceRange(cchToReplace, cchEOP, pszEOP, NULL, -1);
	
	_fModified = FALSE;
	_fSaved = TRUE;
	GetCallMgr()->ClearChangeEvent();
}

/*
 *	CTxtEdit::PopAndExecuteAntiEvent(pundomgr, void *pAE)
 *
 *	@mfunc	Freeze display and execute anti-event
 *
 *	@rdesc	HRESULT from IUndoMgr::PopAndExecuteAntiEvent
 */
HRESULT	CTxtEdit::PopAndExecuteAntiEvent(
	IUndoMgr *pundomgr,	//@parm Undo manager to direct call to
	void  *pAE)			//@parm AntiEvent for undo manager
{
	if(!pundomgr || _fReadOnly || !_fUseUndo || !pundomgr->CanUndo())
		return S_FALSE;

	if(_fReadOnly)
		return E_ACCESSDENIED;

	HRESULT hr;
	// Let stack based classes clean up before restoring selection
	{
		CFreezeDisplay		fd(_pdp);
		CSelPhaseAdjuster	selpa(this);

		hr = pundomgr->PopAndExecuteAntiEvent(pAE);
	}

	if(_psel)
	{
		// Once undo/redo has been executed, flush insertion point formatting
		_psel->Update_iFormat(-1);
		_psel->Update(TRUE);
	}
	return hr;
}

/*
 *	CTxtEdit::PasteDataObjectToRange(pdo, prg, cf, rps, publdr, dwFlags)
 *
 *	@mfunc	Freeze display and paste object
 *
 *	@rdesc	HRESULT from IDataTransferEngine::PasteDataObjectToRange
 */
HRESULT	CTxtEdit::PasteDataObjectToRange(
	IDataObject *	pdo, 
	CTxtRange *		prg, 
	CLIPFORMAT		cf, 
	REPASTESPECIAL *rps,
	IUndoBuilder *	publdr, 
	DWORD			dwFlags)
{
	HRESULT hr = _ldte.PasteDataObjectToRange(pdo, prg, cf, rps, publdr, 
		dwFlags);

	if(_psel)
	{
#ifdef DEBUG
		_psel->Invariant();
#endif
		_psel->Update(TRUE);		   // now update the caret
	}

	return hr;
}

/*
 *	GetECDefaultHeightAndWidth (pts, hdc, lZoomNumerator, lZoomDenominator,
 *					yPixelsPerInch, pxAveWidth, pxOverhang, pxUnderhang)
 *
 *	@mfunc	Helper for host to get ave char width and height for default 
 *			character set for the control.
 *
 *	@rdesc	Height of default character set
 *
 *	@devnote:
 *			This really only s/b called by the window's host.
 */
LONG GetECDefaultHeightAndWidth(
	ITextServices *pts,			//@parm ITextServices to conver to CTxtEdit.
	HDC hdc,					//@parm DC to use for retrieving the font.
	LONG lZoomNumerator,		//@parm Zoom numerator
	LONG lZoomDenominator,		//@parm Zoom denominator
	LONG yPixelsPerInch,		//@parm Pixels per inch for hdc
	LONG *pxAveWidth,			//@parm Optional ave width of character
	LONG *pxOverhang,			//@parm Optional overhang
	LONG *pxUnderhang)			//@parm Optional underhang
{
	CLock lock;					// Uses global (shared) FontCache
	// Convert the text-edit ptr
	CTxtEdit *ped = (CTxtEdit *) pts;

	// Get the CCcs that has all the information we need
	yPixelsPerInch = MulDiv(yPixelsPerInch, lZoomNumerator, lZoomDenominator);
	CCcs *pccs = ped->GetCcs(ped->GetCharFormat(-1), yPixelsPerInch);

	if(!pccs)
		return 0;

	if(pxAveWidth)
		*pxAveWidth = pccs->_xAveCharWidth;

	if(pxOverhang)
	{
		Assert(pxUnderhang);
		pccs->GetFontOverhang(pxOverhang, pxUnderhang);
	}
	
	SHORT	yAdjustFE = pccs->AdjustFEHeight(!ped->fUseUIFont() && ped->_pdp->IsMultiLine());
	LONG yHeight = pccs->_yHeight + (yAdjustFE << 1);

	pccs->Release();						// Release the CCcs
	return yHeight;
}

/* 
 *	CTxtEdit::TxScrollWindowEx (dx, dy, lprcScroll, lprcClip, hrgnUpdate,
 *									lprcUpdate, fupScroll)
 *	@mfunc
 *		Request Text Host to scroll the content of the specified client area
 *
 *	@comm
 *		This method is only valid when the control is in-place active;
 *		calls while inactive may fail.
 */
void CTxtEdit::TxScrollWindowEx(
	INT		dx, 			//@parm	Amount of horizontal scrolling
	INT		dy, 			//@parm	Amount of vertical scrolling
	LPCRECT lprcScroll, 	//@parm	Scroll rectangle
	LPCRECT lprcClip)		//@parm	Clip rectangle
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEEXTERN, "CTxtEdit::TxScrollWindowEx");

	if(_fInPlaceActive)
	{
#if !defined(NOMAGELLAN)
		CMagellanBMPStateWrap bmpOff(*this, NULL);
#endif

		_phost->TxScrollWindowEx(dx, dy, lprcScroll, lprcClip, 0, 0, SW_INVALIDATE | SW_SCROLLCHILDREN);

	}
}

/*
 *	CTxtEdit::GetAcpFromCp (cp)
 *
 *	@mfunc
 *		Get API cp (acp) from Unicode cp in this text instance. The API cp
 *		may be Unicode, in which case it equals cp, or MBCS, in which case
 *		it's greater than cp if any Unicode characters preceding cp convert
 *		to double-byte characters.  An MBCS cp is the BYTE index of a character
 *		relative to the start of the story, while a Unicode cp is the character
 *		index.  The values are the same if all charsets are represented by
 *		SBCS charsets, e.g., ASCII.  If all characters are represented by
 *		double-byte characters, then acp = 2*cp.
 *
 *	@rdesc
 *		MBCS Acp from Unicode cp in this text instance
 *
 *	@devnote
 *		This could be made more efficient by having the selection maintain
 *		the acp that corresponds to its _rpTX._cp, provided RE 1.0 mode is
 *		active.  Alternatively CTxtEdit could have a _prg that tracks this
 *		value, but at a higher cost (17 DWORDs instead of 1 per instance).
 *
 *		FUTURE: we might want to have a conversion-mode state instead of just
 *		_f10Mode, since some people might want to know use MBCS cp's even in
 *		RE 3.0.  If so, use the corresponding new state flag instead of
 *		Get10Mode() in the following.
 */
LONG CTxtEdit::GetAcpFromCp(
	LONG cp,				//@parm Unicode cp to convert to MBCS cp
	BOOL fPrecise)			//@parm fPrecise flag to get byte count for MBCS
{
	if(!(IsFE() && (fCpMap() || fPrecise)))	// RE 2.0 and higher use char-count
		return cp;							//  cp's, while RE 1.0 uses byte
											//  counts
											//  bPrecise is for Ansi Apps that want byte counts
											//  (e.g. Outlook Subject line)

	CRchTxtPtr rtp(this);					// Start at cp = 0
	return rtp.GetCachFromCch(cp);
}

LONG CTxtEdit::GetCpFromAcp(
	LONG acp,				//@parm MBCS cp to convert to Unicode cp
	BOOL fPrecise)			//@parm fPrecise flag to get Unicode cp for MBCS
{
	if( acp == -1 || !(IsFE() && (fCpMap() || fPrecise)))
		return acp;

	CRchTxtPtr rtp(this);					// Start at cp = 0
	return rtp.GetCchFromCach(acp);
}


/*
 *	CTxtEdit::GetViewKind (plres)
 *
 *	@mfunc
 *		get view mode
 *
 *	@rdesc
 *		HRESULT = (plres) ? NOERROR : E_INVALIDARG
 *
 *	@devnote
 *		This could be a TOM property method (along with SetViewMode())
 */
HRESULT CTxtEdit::GetViewKind(
	LRESULT *plres)		//@parm Out parm to receive view mode
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetViewKind");

	if(!plres)
		return E_INVALIDARG;

	*plres = IsInOutlineView() ? VM_OUTLINE
		   : IsInPageView()	   ? VM_PAGE : VM_NORMAL;
	return NOERROR;
}

/*
 *	CTxtEdit::SetViewKind (Value)
 *
 *	@mfunc
 *		Turn outline mode on or off
 *
 *	@rdesc
 *		HRESULT = IsRich() ? NOERROR : S_FALSE
 *
 *	@devnote
 *		This could be a TOM property method (along with GetViewMode())
 */
HRESULT CTxtEdit::SetViewKind(
	long Value)		//@parm Turn outline mode on/off for Value nonzero/zero
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::SetViewKind");

	CTxtSelection *psel = GetSel();
	BOOL fPageView = Value == VM_PAGE && _pdp->IsMultiLine();

	if(fPageView || Value == VM_NORMAL && IsInPageView())
	{
		_fPageView = (WORD)fPageView;
		if(!IsInOutlineView())
		{
			_pdp->Paginate(0, TRUE);
			psel->Update(TRUE);
			TxInvalidate();
			return NOERROR;
		}
	}

	if(!IsRich() || !_pdp->IsMultiLine())
		return S_FALSE;

    Value = (Value == VM_OUTLINE);			// Convert to 1/0
	if(_fOutlineView != Value)
	{
		HCURSOR	hcur = TxSetCursor(LoadCursor(0, IDC_WAIT), NULL);

		_fOutlineView = (WORD)Value;
		if(!GetAdjustedTextLength())		// No text in control: in outline
		{									//  view, use Heading 1; in normal
			CParaFormat PF;					//  view, use Normal style
			PF._sStyle = (SHORT)(IsInOutlineView()
					  ? STYLE_HEADING_1 : STYLE_NORMAL);
			psel->SetParaStyle(&PF, NULL, PFM_STYLE);
		}
		else
		{
			// There is text. Make sure there is paragraph formatting.
			_psel->Check_rpPF();
		}

		psel->CheckIfSelHasEOP(-1, 0);
		_pdp->UpdateView();
	    psel->Update(TRUE);
		TxSetCursor(hcur, NULL);
	}
	return NOERROR;
}

/*
 *	CTxtEdit::GetViewScale (pValue)
 *
 *	@mfunc
 *		get view zoom scale in percent
 *
 *	@rdesc
 *		HRESULT = (pValue) ? NOERROR : E_INVALIDARG
 *
 *	@devnote
 *		This could be a TOM property method (along with SetViewScale())
 */
HRESULT CTxtEdit::GetViewScale(
	long *pValue)		//@parm Get % zoom factor
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetViewScale");

	if(!pValue)
		return E_INVALIDARG;

	*pValue = 100;
	if(GetZoomNumerator() && GetZoomDenominator())
		*pValue = (100*GetZoomNumerator())/GetZoomDenominator();

	return NOERROR;
}

/*
 *	CTxtEdit::SetViewScale (Value)
 *
 *	@mfunc
 *		Set zoom numerator equal to the scale percentage Value and
 *		zoom denominator equal to 100
 *
 *	@rdesc
 *		NOERROR
 *
 *	@devnote
 *		This could be a TOM property method (along with GetViewScale())
 */
HRESULT CTxtEdit::SetViewScale(
	long Value)		//@parm Set view scale factor
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::SetViewScale");

	if((unsigned)Value > 2000)
		return E_INVALIDARG;

	SetZoomNumerator(Value);
	SetZoomDenominator(100);
	return NOERROR;
}

/* 
 *	CTxtEdit::UpdateOutline()
 *
 *	@mfunc
 *		Update selection and screen after ExpandOutline() operation
 *
 *	@comm
 *		This method is only valid when the control is in-place active;
 *		calls while inactive may fail.
 */
HRESULT CTxtEdit::UpdateOutline()
{
	Assert(IsInOutlineView());

	GetSel()->Update(FALSE);
    TxInvalidate();
    return NOERROR;
}

/*
 *	CTxtEdit::MoveSelection(lparam, publdr)
 *
 *	@mfunc
 *		Move selected text up/down by the number of paragraphs given by
 *		LOWORD(lparam).
 *
 *	@rdesc
 *		TRUE iff movement occurred
 */
HRESULT CTxtEdit::MoveSelection (
	LPARAM lparam,			//@parm	# paragraphs to move by
	IUndoBuilder *publdr)	//@parm undo builder to receive antievents
{
	TRACEBEGIN(TRCSUBSYSSEL, TRCSCOPEINTERN, "CTxtRange::MoveSelection");

	CFreezeDisplay	fd(_pdp);
	CTxtSelection *	psel = GetSel();
	LONG			cch;
	LONG			cchSel = psel->GetCch();
	LONG			cpMin, cpMost;
	LONG			cpSel = psel->GetCp();
	IDataObject *	pdo = NULL; 
	CTxtRange		rg(*psel); 
	LONG			cpNext = 0;
	LONG			cpCur = 0;
	BOOL			fDeleteCR = FALSE;

	if(publdr)
		publdr->StopGroupTyping();

	rg.Expander(tomParagraph, TRUE, NULL, &cpMin, &cpMost);
	CPFRunPtr rp(rg);
	cch = rp.FindExpanded();			// Include subordinate paras
	if(cch < 0)
		cch = tomForward;
	rg.Move(cch, TRUE);
	cpMost = rg.GetCpMost();

	if(lparam > 0 && cpMost == GetTextLength())
	{									
		Beep();							// Already at end
		return S_FALSE;
	}

	HRESULT hr = _ldte.RangeToDataObject(&rg, SF_RTF, &pdo);
	if(hr != NOERROR)
		goto error;

	if(lparam > 0)
		psel->EndOf(tomParagraph, FALSE, NULL);
	else
		psel->StartOf(tomParagraph, FALSE, NULL);

	cpCur = psel->GetCp();
	hr = psel->Move(tomParagraph, lparam, NULL);
	if(psel->GetCp() == cpCur)
	{
		psel->Set(cpSel, cchSel);
		Beep();
		goto error;
	}

	// Since psel->Move() calls psel->Update(), the selection is forced
	// to be in noncollapsed text. Going backward, this might leave the
	// selection just before the EOP of a paragraph, instead of being at the
	// start of the paragraph where it should be.  Going forward it may have
	// tried to reach the EOD, but was adjusted backward. This case gets
	// a bit awkward...
	if(psel->GetCp() < cpCur)					// Going backward: be sure
		psel->StartOf(tomParagraph, FALSE, NULL);//  end up at start of para

	else if(!psel->_rpTX.IsAfterEOP())			// Going forward and sel
	{											//  adjusted backward
		psel->Move(tomForward, FALSE);			// Go to final CR, insert a CR
		CTxtRange rgDel(*psel);					//  use psel because UI
		rgDel.ReplaceRange(1, szCR, publdr, SELRR_REMEMBERRANGE);
		psel->Move(1, FALSE);
		fDeleteCR = TRUE;						// Remember to delete it
	}

	cpCur = psel->GetCp();
	hr = _ldte.PasteDataObjectToRange(pdo, psel, 0, NULL, 
									  publdr, PDOR_NONE);
	if(hr != NOERROR)
		goto error;

	if(fDeleteCR)								// Delete CR (final CR becomes
	{											//  CR for this para). Don't
		CTxtRange rgDel(*psel);					//  use psel because UI
		Assert(rgDel._rpTX.IsAfterEOP());		//  restricts it's ability to
		rgDel.Delete(tomCharacter, -1, &cch);	//  delete
	}											
												
	cpNext = psel->GetCp();			
	psel->Set(cpCur, 0);
	psel->CheckOutlineLevel(publdr);
	psel->Set(cpNext, 0);
	psel->CheckOutlineLevel(publdr);

	// Now set selection anti-events. If selection preceded paste point,
	// subtract its length from redo position, since selection will get
	// deleted if we are doing a DRAGMOVE within this instance.
	cch = cpMost - cpMin;						// cch of rg
	if(cpSel < cpCur)
		cpNext -= cch;

	psel->Set(psel->GetCp() + fDeleteCR, cch);	// Include final CR

	// rg.ReplaceRange won't delete final CR, so remember if it's included
	fDeleteCR = rg.GetCpMost() == GetTextLength();
	rg.ReplaceRange(0, NULL, publdr, SELRR_REMEMBERRANGE);

	if(fDeleteCR)								// Needed to delete final CR
		rg.DeleteTerminatingEOP(publdr);		// Delete one immediately
												//  before it instead
	rg.CheckOutlineLevel(publdr);
	if(publdr)
	{
		HandleSelectionAEInfo(this, publdr, cpSel, cchSel, cpNext, cch,
							  SELAE_FORCEREPLACE);
	}
	hr = NOERROR;

error:
	if(pdo)
		pdo->Release();
	return hr;
}

/*
 *	CTxtEdit::OnInsertTable(ptrp, pclp)
 *
 *	@mfunc
 *		EM_INSERTTABLE acts similarly to EM_REPLACESEL for a degenerate
 *		selection (insertion point), but inserts a number of identical
 *		empty table rows instead of some plain text. Specifically
 *		it inserts ptrp->cRow empty table rows with the row and cell
 *		parameters given by ptrp and pclp, respectively.  It leaves the
 *		selection pointing to the start of the first cell in the row.  The
 *		client can then populate the table cells by pointing the selection
 *		at the cell end marks and inserting and formatting the desired text.
 *		Such text can include nested table rows, etc.
 *
 *		The format for a table row is
 *
 *		{CR ... }CR
 *
 *		where { stands for STARTGROUP (0xFFF9), CR is 0xD, } stands for
 *		ENDGROUP (0xFFFB) and ... stands for TABLEROWPARMS::cCell cell-end
 *		marks.  A cell-end mark is given by CELL (0x7), which is what Word
 *		also uses for this purposes.  For example, a row with three cells has
 *		the plain text 0xFFF9 0xD 7 7 7 0xFFFB 0xD.  The start and end group
 *		character pairs are assigned identical PARAFORMAT2 information that
 *		describes the row and cell parameters.  If rows with different
 *		parameters are needed, multiple single-row calls can be made with the
 *		desired parameters.
 *
 *	@rdesc
 *		HRESULT = S_OK if row inserted
 */
HRESULT CTxtEdit::OnInsertTable(
	TABLEROWPARMS * ptrp,	//@parm Describes table row parameters
	TABLECELLPARMS *pclp,	//@parm Describes cell parameters
	IUndoBuilder *publdr)	//@parm Undo builder to receive antievents
{
	CParaFormat		PF;
	CTxtSelection *	pSel = GetSel();
	CELLPARMS		rgCellParms[MAX_TABLE_CELLS];

	if (!ptrp || !pclp || !ptrp->cRow ||
		!IN_RANGE(1, ptrp->cCell, MAX_TABLE_CELLS) || 
		ptrp->cbRow  != sizeof(TABLEROWPARMS) ||
		ptrp->cbCell != sizeof(TABLECELLPARMS))
	{
		return E_INVALIDARG;
	}
	if(pSel->GetCch() || !IsRich() || !_pdp || !_pdp->IsMultiLine())
		return E_FAIL;

	LONG cpSelSave = pSel->GetCp();

	pSel->StopGroupTyping();
	while(pSel->GetPF()->IsTableRowDelimiter())
		pSel->AdvanceCRLF(CSC_NORMAL, FALSE);

	PF.InitDefault(0);
	PF._bTabCount	  = ptrp->cCell;
	PF._bAlignment	  = ptrp->nAlignment;
	PF._dxOffset	  = ptrp->dxCellMargin;
	PF._dxStartIndent = ptrp->dxIndent;
	PF._dyLineSpacing = ptrp->dyHeight;
	PF._wEffects	  = PFE_TABLE | PFE_TABLEROWDELIMITER;
	PF._bTableLevel	  = pSel->GetPF()->_bTableLevel + 1;

	if(ptrp->fRTL)
		PF._wEffects |= PFE_RTLPARA;
	if(ptrp->fKeep)
		PF._wEffects |= PFE_KEEP;
	if(ptrp->fKeepFollow)
		PF._wEffects |= PFE_KEEPNEXT;

	LONG uCell;
	LONG dul = 0;
	CCellColor ccr;

	for(LONG i = 0; i < ptrp->cCell; i++)
	{
		uCell = pclp->dxWidth;			// Cell width must be between
		uCell = max(0, uCell);			//  0" and 22"
		uCell = min(1440*22, uCell);
		dul += uCell;
		if(dul > 1440*22)
			return E_INVALIDARG;
		uCell += (pclp->nVertAlign << 24);
		if(pclp->fMergeTop)
			uCell |= fTopCell;
		else if(pclp->fMergePrev)
			uCell |= fLowCell;
		if(pclp->fVertical)
			uCell |= fVerticalCell;

		rgCellParms[i].uCell = uCell;

		rgCellParms[i].dxBrdrWidths = (CheckTwips(pclp->dxBrdrLeft)   << 0*8)
									+ (CheckTwips(pclp->dyBrdrTop)	  << 1*8)
									+ (CheckTwips(pclp->dxBrdrRight)  << 2*8)
									+ (CheckTwips(pclp->dyBrdrBottom) << 3*8);

		rgCellParms[i].dwColors = (ccr.GetColorIndex(pclp->crBrdrLeft)	<< 0*5)
								+ (ccr.GetColorIndex(pclp->crBrdrTop)	<< 1*5)
								+ (ccr.GetColorIndex(pclp->crBrdrRight)	<< 2*5)
								+ (ccr.GetColorIndex(pclp->crBrdrBottom)<< 3*5)
								+ (ccr.GetColorIndex(pclp->crBackPat)	<< 4*5)
								+ (ccr.GetColorIndex(pclp->crForePat)	<< 5*5);
		if(pclp->wShading > 10000)
			return E_INVALIDARG;

		rgCellParms[i].bShading = (BYTE)(pclp->wShading/50);

		if(!ptrp->fIdentCells)
			pclp++;
	}
	if(ccr._crCellCustom1)
	{
		PF._crCustom1 = ccr._crCellCustom1;
		if(ccr._crCellCustom2)
			PF._crCustom2 = ccr._crCellCustom2;
	}

	PF._iTabs = GetTabsCache()->Cache((LONG *)&rgCellParms[0],
									  ptrp->cCell * (CELL_EXTRA + 1));
	HRESULT hr = S_OK;
	for(i = ptrp->cRow; i--; )
	{
		LONG cchCells = pSel->InsertTableRow(&PF, publdr);
		if(!cchCells)
		{
			hr = E_FAIL;
			break;
		}
		pSel->Move(cchCells + 2, FALSE);	// Leave selection at end of row
	}

	GetTabsCache()->Release(PF._iTabs);
	pSel->Update(TRUE);
	if(publdr)
		HandleSelectionAEInfo(this, publdr,
			cpSelSave, 0, pSel->GetCp(), 0, SELAE_FORCEREPLACE);
	return hr;
}

/*
 *	CTxtEdit::SetReleaseHost
 *
 *	@mfunc	Handles notification that edit control must keep its
 *			reference to the host alive.
 */
void CTxtEdit::SetReleaseHost()
{
	_phost->AddRef();
	_fReleaseHost = TRUE;
}

#if !defined(NOMAGELLAN)
/*
 *	CTxtEdit::HandleMouseWheel(wparam, lparam)
 *
 *	@mfunc	Handles scrolling as a result of rotating a mouse roller wheel.
 *
 *	@rdesc	LRESULT
 */
LRESULT	CTxtEdit::HandleMouseWheel(
	WPARAM wparam,
	LPARAM lparam)
{ 	
	// This bit of global state is OK
	static LONG gcWheelDelta = 0;
	short zdelta = (short)HIWORD(wparam);
	BOOL fScrollByPages = FALSE;

	// Cancel middle mouse scrolling if it's going.
	OnTxMButtonUp(0, 0, 0);

	// Handle zoom or data zoom
	if((wparam & MK_CONTROL) == MK_CONTROL)
	{
	    // bug fix 5760
	    // prevent zooming if control is NOT rich or
	    // is a single line control
	    if (!_pdp->IsMultiLine())
	        return 0;
	        
		LONG lViewScale;
		GetViewScale(&lViewScale);
		lViewScale += (zdelta/WHEEL_DELTA) * 10;	// 10% per click
		if(lViewScale <= 500 && lViewScale >= 10)	// Word's limits
		{
			SetViewScale(lViewScale);							
			_pdp->UpdateView();
		}
		return 0;
	}

	if(wparam & (MK_SHIFT | MK_CONTROL))
		return 0;

	gcWheelDelta += zdelta;

	if(abs(gcWheelDelta) >= WHEEL_DELTA)
	{
		LONG cLineScroll = W32->GetRollerLineScrollCount();
		if(cLineScroll != -1)
			cLineScroll *= abs(gcWheelDelta)/WHEEL_DELTA;

		gcWheelDelta %= WHEEL_DELTA;

		// -1 means scroll by pages; so simply call page up/down. 
		if(cLineScroll == -1 || IsInPageView())
		{
			fScrollByPages = TRUE;
			if(_pdp)
				_pdp->VScroll(zdelta < 0 ? SB_PAGEDOWN : SB_PAGEUP, 0);
		}
		else
		{
			mouse.MagellanRollScroll(_pdp, zdelta, cLineScroll, 
				SMOOTH_ROLL_NUM, SMOOTH_ROLL_DENOM, TRUE);
		}

		// notify through the messagefilter that we scrolled
		if(_dwEventMask & ENM_SCROLLEVENTS)
		{
			MSGFILTER msgfltr;
			ZeroMemory(&msgfltr, sizeof(MSGFILTER));
			msgfltr.msg	   = WM_VSCROLL;
			msgfltr.wParam = fScrollByPages ?
								(zdelta < 0 ? SB_PAGEDOWN: SB_PAGEUP):
								(zdelta < 0 ? SB_LINEDOWN: SB_LINEUP);
			
			// We don't check the result of this call --
			// it's not a message we received and we're not going to
			// process it any further
			_phost->TxNotify(EN_MSGFILTER, &msgfltr);			
		}
		return TRUE;
	}
	return 0;
}
#endif

const int cchCorrectMax = 256; //Max characters to be autocorrected (Office spec)
const int cchFromMax = 768;

/*
 *	CTxtEdit::AutoCorrect(psel, ch, publdr)
 *
 *	@mfunc	Call the client to autocorrect the recently added word. Don't replace
 *	the recently added character in string passed to client. (We don't want to
 *	ReplaceRange a CELL character, for example.)
 */
void CTxtEdit::AutoCorrect(
	CTxtSelection *psel,
	WCHAR		   chAdd, 
	IUndoBuilder * publdr)
{
	LONG	cch = 0;
	WCHAR	pchFrom[cchFromMax + 1];
	WCHAR	pchTo[cchCorrectMax + 1];
	CTxtPtr	tp(psel->_rpTX);
	WCHAR	chPrev = tp.GetPrevChar();
	BOOL	fCheckIsLink = (L':' == chPrev || L'.' == chPrev) && GetDetectURL();

	for(LONG i = 4; i-- && tp.GetCp(); )
	{
		LONG cchWord =  -tp.FindWordBreak(WB_MOVEWORDLEFT);
		if (i == 2 && fCheckIsLink)
		{
			BOOL fURLLeadin = FALSE;
			GetDetectURL()->IsURL(tp, cchWord + 1, &fURLLeadin);
			if(fURLLeadin)
				return;	
		}
		if(cch + cchWord > cchFromMax)		// Don't bite off more than
			break;							//  buffer can chew

		cch += cchWord;
		if (IsEOP(tp.GetPrevChar()))		// Don't autocorrect across an EOP
			break;
	}
	// Be sure we don't go into hyperlink or SYMBOL_CHARSET territory
	if(psel->_rpCF.IsValid())
	{
		CCFRunPtr		   rp(*psel);

		for(LONG cchMax = 0; cchMax < cch; rp.SetIch(0))
		{
			rp.AdjustBackward();
			const CCharFormat *pCF = rp.GetCF();
			if (pCF->_dwEffects & (CFE_LINK | CFE_HIDDEN) ||
				pCF->_iCharRep == SYMBOL_INDEX)
			{
				break;
			}
			cchMax += rp.GetIch();
			if(!rp.GetIRun())
				break;						// Reached start of doc
		}
		if(cchMax < cch)					// Hyperlink within words to check
		{
			tp.Move(cch - cchMax);			// Only check chars back to link,
			cch = cchMax;					//  symbols, or CharRep change
		}
	}
	tp.GetText(cch, pchFrom);
	pchFrom[cch] = 0;

	long	  cchTo = cchCorrectMax, cchReplaced = 0;
	CCFRunPtr rp(*psel);
	rp.Move(-2);

	if(_pDocInfo->_pfnAutoCorrect(rp.GetCF()->_lcid, pchFrom, pchTo, cchTo, &cchReplaced))
	{
		// If plain text check for special cases to suppress per bug 8717.		
		// copyright, registered trademark, trademark, ellipses.
		if(!_fRich && (pchTo[0] == 0xA9 || pchTo[0] == 0xAE ||
			pchTo[0] == 0x2122 || pchTo[0] == 0x2026))
		{
			return;
		}

		if (publdr)
		{
			publdr->Done();
			publdr->StopGroupTyping();
		}

		CTxtRange rg(*psel);
		DWORD	  ch = rg.GetPrevChar();
		LONG	  cpSave = psel->GetCp();
		LONG	  cchDelim = 1;

		if(ch >= 0x1100)					// Most East Asian chars aren't delims
		{
			if(ch < 0x1200 || IN_RANGE(0x3040, ch, 0xD7FF) ||
				ch >= 0xF900 && (ch < 0xFAFF || IN_RANGE(0xFF21, ch, 0xFFDF)))
			{
				cchDelim = 0;
			}
		}
		rg.Set(rg.GetCp() - cchDelim, cchReplaced - cchDelim);
		rg.CleanseAndReplaceRange(wcslen(pchTo) - cchDelim, pchTo, FALSE, publdr, pchTo);
		if(!cchDelim)
			psel->SetCp(rg.GetCp(), FALSE);
		if (publdr)
			HandleSelectionAEInfo(this, publdr, cpSave, 0, psel->GetCp(), 0, SELAE_FORCEREPLACE);

//		publdr->SetNameID(UID_AUTOCORRECT);
	}
}

/*
 *	CTxtEdit::OnSetAssociateFont(pCF, dwFlags)
 *
 *	@mfunc	Set the Associate font for the input LCID or charset
 *
 *	@rdesc	LRESULT
 */
LRESULT	CTxtEdit::OnSetAssociateFont(
	CHARFORMAT2 *pCF2,
	DWORD		dwFlags)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnSetAssociateFont");

	int		cpg = 0;
	SHORT	iFont;
	BYTE	yHeight;
	int		cbSize = pCF2->cbSize;

	Assert(cbSize == sizeof(CHARFORMAT2W) || cbSize == sizeof(CHARFORMAT2A));

	int iCharRep = CharRepFromCharSet(pCF2->bCharSet);

	if (pCF2->dwMask & CFM_LCID)
	{
		iCharRep = CharRepFromLID(cbSize == sizeof(CHARFORMAT2W)
								  ? pCF2->lcid : ((CHARFORMAT2A *)pCF2)->lcid,
								  dwFlags & SCF_ASSOCIATEFONT2);
		cpg = CodePageFromCharRep(iCharRep);
	}

	if (iCharRep == -1)
		return 0;							// Can't get Char repertoire, so forget it

	if (cbSize == sizeof(CHARFORMAT2W))
		iFont = GetFontNameIndex(pCF2->szFaceName);
	else
	{
		// need to convert CHARFORMAT2A face name
		LONG	cch;
		BOOL	fMissingCodePage;
		WCHAR	szFaceName[LF_FACESIZE];				

		cch = MBTWC(cpg, 0,
					((CHARFORMAT2A *)pCF2)->szFaceName,	-1, 
					szFaceName, LF_FACESIZE, &fMissingCodePage);

		if (fMissingCodePage || cch <= 0)
			return 0;

		iFont = GetFontNameIndex(szFaceName);
	}

	yHeight = pCF2->yHeight / TWIPS_PER_POINT;

	CLock lock;
	if (W32->SetPreferredFontInfo(iCharRep, dwFlags & SCF_USEUIRULES ? true : false, iFont, yHeight, pCF2->bPitchAndFamily))
		return 1;

	return 0;
}

/*
 *	CTxtEdit::OnGetAssociateFont(pCF, dwFlags)
 *
 *	@mfunc	Get the Associate font for the input LCID or charset
 *
 *	@rdesc	LRESULT
 */
LRESULT	CTxtEdit::OnGetAssociateFont(
	CHARFORMAT2 *pCF2,
	DWORD		dwFlags)
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::OnGetAssociateFont");

	int		cpg = 0;
	SHORT	iFont;
	BYTE	yHeight;
	BYTE	bPitchAndFamily;
	int		cbSize = pCF2->cbSize;

	Assert(cbSize == sizeof(CHARFORMAT2W) || cbSize == sizeof(CHARFORMAT2A));

	int iCharRep = CharRepFromCharSet(pCF2->bCharSet);
	if (pCF2->dwMask & CFM_LCID)
	{
		iCharRep = CharRepFromLID(cbSize == sizeof(CHARFORMAT2W)
								  ? pCF2->lcid : ((CHARFORMAT2A *)pCF2)->lcid,
								  dwFlags & SCF_ASSOCIATEFONT2);
		cpg = CodePageFromCharRep(iCharRep);
	}

	if (iCharRep == -1)
		return 0;							// Can't get char repertoire, so forget it

	if (W32->GetPreferredFontInfo(iCharRep, dwFlags & SCF_USEUIRULES ? true : false, iFont, yHeight, bPitchAndFamily))
	{
		pCF2->yHeight = yHeight * TWIPS_PER_POINT;
		pCF2->bPitchAndFamily = bPitchAndFamily;
		
		if (cbSize == sizeof(CHARFORMAT2W))
			wcscpy(pCF2->szFaceName, GetFontName((LONG)iFont));
		else
		{
			// need to convert CHARFORMAT2A face name
			LONG	cch;
			BOOL	fMissingCodePage;
			const WCHAR	*pszFaceName = GetFontName((LONG)iFont);				

			cch = WCTMB(cpg, 0,
						pszFaceName, -1,
						((CHARFORMAT2A *)pCF2)->szFaceName, LF_FACESIZE, 
						NULL, NULL, &fMissingCodePage);

			if (fMissingCodePage || cch <= 0)
				return 0;
		}
		return 1;
	}
	return 0;
}

#ifndef NOINKOBJECT
/*
 *	CTxtEdit::SetInkProps(ILineInfo *pILineInfo, UINT *piInkWidth)
 *
 *	@mfunc	Setup the Ink object properties
 *
 *	@rdesc	HRESULT
 */
HRESULT	CTxtEdit::SetInkProps(
	LONG		cp,
	ILineInfo	*pILineInfo, 
	UINT		*piInkWidth)
{
	HRESULT		hr = E_FAIL;
	INKMETRIC	inkMetric;
	CTxtRange	rg(this, cp, 1);
	const CCharFormat *pCF = rg.GetCF();
	
	if (pCF)
	{
		memset(&inkMetric, 0, sizeof(inkMetric));

		if (pCF->_wWeight > FW_NORMAL)
			inkMetric.iWeight = 3;								// Bold

		inkMetric.fItalic = !!(pCF->_dwEffects & CFE_ITALIC);	// Italic
																// Height in HIMETRIC
		inkMetric.iHeight = (UINT)MulDiv(pCF->_yHeight, HIMETRIC_PER_INCH, LY_PER_INCH);

		inkMetric.color = pCF->_crTextColor;					// Color

		// Get zoomed height
		LONG dvpInch = MulDiv(GetDeviceCaps(W32->GetScreenDC(), LOGPIXELSY), _pdp->GetZoomNumerator(), _pdp->GetZoomDenominator());
		CCcs *pccs = GetCcs(pCF, dvpInch);

		if (pccs)
		{
			inkMetric.iFontDescent = (UINT)MulDiv(inkMetric.iHeight, pccs->_yDescent, pccs->_yHeight);
			inkMetric.iFontAscent = inkMetric.iHeight - inkMetric.iFontDescent;

			// Release cache entry since we are done with it.
			pccs->Release();
		}

		hr = pILineInfo->SetFormat(&inkMetric);
	}

	return hr;
}
#endif
/*
 *	CTxtEdit::GetCaretWidth()
 *
 *	@mfunc	Get caret width
 *
 *	@rdesc
 *		caret width
 */
HRESULT	CTxtEdit::GetCaretWidth()
{
	TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CTxtEdit::GetCaretWidth");

	RECT rcInset;

	if(!fInHost2()	||					// Host 1
		SUCCEEDED(_phost->TxGetViewInset(&rcInset)) && !rcInset.right)
		return duCaret;

	return 0;
}

/*
 *	CCellColor::GetColorIndex(cr)
 *
 *	@mfunc
 *		Get color index corresponding to cr. Possible return values are 0
 *		(autocolor), 1-16 (the 16 standard colors: g_Colors), and two custom
 *		colors, 17 and 18 defined on a first-come basis.
 *
 *	@rdesc
 *		Color index corresponding to cr
 */
LONG CCellColor::GetColorIndex(
	COLORREF cr)
{
	if(cr == tomAutoColor)
		return 0;

	for(LONG i = 0; i < 16; i++)
	{
		if(cr == g_Colors[i])
			return i + 1;
	}
	if(!_crCellCustom1 || cr == _crCellCustom1)
	{
		_crCellCustom1 = cr;				// First custom cr 
		return 17;
	}

	if(!_crCellCustom2 || cr == _crCellCustom2)	
	{
		_crCellCustom2 = cr;				// Second custom cr
		return 18;
	}
	return 0;								// No custom cr available	

}