// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1998 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"
#include <ctype.h>

#ifdef AFX_CORE4_SEG
#pragma code_seg(AFX_CORE4_SEG)
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CEditView

#define new DEBUG_NEW

AFX_STATIC const UINT _afxMsgFindReplace = ::RegisterWindowMessage(FINDMSGSTRING);

#ifdef _UNICODE

AFX_STATIC_DATA HFONT _afxUnicodeFont = 0;

void AFX_CDECL AfxEditviewTerm()
{
	AfxDeleteObject((HGDIOBJ*)&_afxUnicodeFont);
}
char _afxEditviewTerm = (char)atexit(&AfxEditviewTerm);

#endif //_UNICODE

BEGIN_MESSAGE_MAP(CEditView, CCtrlView)
	//{{AFX_MSG_MAP(CEditView)
	ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateNeedSel)
	ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateNeedClip)
	ON_UPDATE_COMMAND_UI(ID_EDIT_SELECT_ALL, OnUpdateNeedText)
	ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
	ON_UPDATE_COMMAND_UI(ID_EDIT_FIND, OnUpdateNeedText)
	ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateNeedText)
	ON_UPDATE_COMMAND_UI(ID_EDIT_REPEAT, OnUpdateNeedFind)
	ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateNeedSel)
	ON_UPDATE_COMMAND_UI(ID_EDIT_CLEAR, OnUpdateNeedSel)
	ON_CONTROL_REFLECT_EX(EN_CHANGE, OnEditChange)
	ON_WM_CREATE()
	ON_MESSAGE(WM_SETFONT, OnSetFont)
	ON_COMMAND(ID_EDIT_CUT, OnEditCut)
	ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
	ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
	ON_COMMAND(ID_EDIT_CLEAR, OnEditClear)
	ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
	ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll)
	ON_COMMAND(ID_EDIT_FIND, OnEditFind)
	ON_COMMAND(ID_EDIT_REPLACE, OnEditReplace)
	ON_COMMAND(ID_EDIT_REPEAT, OnEditRepeat)
	ON_WM_DESTROY()
	//}}AFX_MSG_MAP
	// Special registered message for Find and Replace
	ON_REGISTERED_MESSAGE(_afxMsgFindReplace, OnFindReplaceCmd)
	// Standard Print commands (print only - not preview)
	ON_COMMAND(ID_FILE_PRINT, CCtrlView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, CCtrlView::OnFilePrint)
END_MESSAGE_MAP()

const AFX_DATADEF DWORD CEditView::dwStyleDefault =
	AFX_WS_DEFAULT_VIEW |
	WS_HSCROLL | WS_VSCROLL |
	ES_AUTOHSCROLL | ES_AUTOVSCROLL |
	ES_MULTILINE | ES_NOHIDESEL;

// Operating system specific maximum buffer limit
const AFX_DATADEF UINT CEditView::nMaxSize = 1024U*1024U-1;

/////////////////////////////////////////////////////////////////////////////
// _AFX_EDIT_STATE

_AFX_EDIT_STATE::_AFX_EDIT_STATE()
{
	// Note: it is only necessary to initialize non-zero data.

	bNext = TRUE;
}

_AFX_EDIT_STATE::~_AFX_EDIT_STATE()
{
}

EXTERN_PROCESS_LOCAL(_AFX_EDIT_STATE, _afxEditState)

/////////////////////////////////////////////////////////////////////////////
// CEditView construction/destruction

// pass a NULL style because dwStyleDefault stays for backward compatibility
CEditView::CEditView() : CCtrlView(_T("EDIT"), NULL)
{
	m_nTabStops = 8*4;  // default 8 character positions
	m_hPrinterFont = NULL;
	m_hMirrorFont = NULL;
	m_pShadowBuffer = NULL;
	m_nShadowSize = 0;
}

CEditView::~CEditView()
{
	ASSERT(m_hWnd == NULL);
	ASSERT(m_pShadowBuffer == NULL || afxData.bWin95);
	delete[] m_pShadowBuffer;
}

BOOL CEditView::PreCreateWindow(CREATESTRUCT& cs)
{
	m_dwDefaultStyle = dwStyleDefault;
	return CCtrlView::PreCreateWindow(cs);
}

int CEditView::OnCreate(LPCREATESTRUCT lpcs)
{
	if (CCtrlView::OnCreate(lpcs) != 0)
		return -1;

#ifdef _UNICODE
	AfxLockGlobals(CRIT_EDITVIEW);
	if (_afxUnicodeFont == NULL)
	{
		// get unicode font same size as system font
		HFONT hSystemFont = (HFONT)GetStockObject(SYSTEM_FONT);
		LOGFONT systemFont;
		VERIFY(::GetObject(hSystemFont, sizeof(LOGFONT), (void*)&systemFont));

		// default size and facename, but allow customization
		LOGFONT logFont; memset(&logFont, 0, sizeof(LOGFONT));
		logFont.lfHeight = systemFont.lfHeight;
		logFont.lfWeight = systemFont.lfWeight;
		logFont.lfCharSet = DEFAULT_CHARSET;
		lstrcpy(logFont.lfFaceName, _T("Lucida Sans Unicode"));
		AfxCustomLogFont(AFX_IDS_UNICODE_FONT, &logFont);

		// attempt to create the font
		_afxUnicodeFont = ::CreateFontIndirect(&logFont);
		if (_afxUnicodeFont == NULL)
			TRACE1("Unable to create unicode font '%s'.\n", logFont.lfFaceName);
	}
	AfxUnlockGlobals(CRIT_EDITVIEW);
	// set unicode font instead of using system font
	if (_afxUnicodeFont != NULL)
		SendMessage(WM_SETFONT, (WPARAM)_afxUnicodeFont);
#endif

	GetEditCtrl().LimitText(nMaxSize);
	GetEditCtrl().SetTabStops(m_nTabStops);

	return 0;
}

void CEditView::OnDestroy()
{
	_AFX_EDIT_STATE* pEditState = _afxEditState;
	pEditState->pFindReplaceDlg = NULL;

	CView::OnDestroy();
}

// EDIT controls always turn off WS_BORDER and draw it themselves
void CEditView::CalcWindowRect(LPRECT lpClientRect, UINT nAdjustType)
{
	if (nAdjustType != 0)
	{
		// default behavior for in-place editing handles scrollbars
		DWORD dwStyle = GetStyle();
		if (dwStyle & WS_VSCROLL)
			lpClientRect->right += afxData.cxVScroll - CX_BORDER;
		if (dwStyle & WS_HSCROLL)
			lpClientRect->bottom += afxData.cyHScroll - CY_BORDER;
		return;
	}

	::AdjustWindowRectEx(lpClientRect, GetStyle() | WS_BORDER, FALSE,
		GetExStyle() & ~(WS_EX_CLIENTEDGE));
}

/////////////////////////////////////////////////////////////////////////////
// CEditView document like functions

void CEditView::DeleteContents()
{
	ASSERT_VALID(this);
	ASSERT(m_hWnd != NULL);
	SetWindowText(NULL);
	ASSERT_VALID(this);
}

void CEditView::Serialize(CArchive& ar)
	// Read and write CEditView object to archive, with length prefix.
{
	ASSERT_VALID(this);
	ASSERT(m_hWnd != NULL);
	if (ar.IsStoring())
	{
		UINT nLen = GetBufferLength();
		ar << (DWORD)nLen;
		WriteToArchive(ar);
	}
	else
	{
		DWORD dwLen;
		ar >> dwLen;
		if (dwLen > nMaxSize)
			AfxThrowArchiveException(CArchiveException::badIndex);
		UINT nLen = (UINT)dwLen;
		ReadFromArchive(ar, nLen);
	}
	ASSERT_VALID(this);
}

void CEditView::ReadFromArchive(CArchive& ar, UINT nLen)
	// Read certain amount of text from the file, assume at least nLen
	// characters (not bytes) are in the file.
{
	ASSERT_VALID(this);

	LPVOID hText = LocalAlloc(LMEM_MOVEABLE, (nLen+1)*sizeof(TCHAR));
	if (hText == NULL)
		AfxThrowMemoryException();

	LPTSTR lpszText = (LPTSTR)LocalLock(hText);
	ASSERT(lpszText != NULL);
	if (ar.Read(lpszText, nLen*sizeof(TCHAR)) != nLen*sizeof(TCHAR))
	{
		LocalUnlock(hText);
		LocalFree(hText);
		AfxThrowArchiveException(CArchiveException::endOfFile);
	}
	// Replace the editing edit buffer with the newly loaded data
	lpszText[nLen] = '\0';
#ifndef _UNICODE
	if (afxData.bWin95)
	{
		// set the text with SetWindowText, then free
		BOOL bResult = ::SetWindowText(m_hWnd, lpszText);
		LocalUnlock(hText);
		LocalFree(hText);

		// make sure that SetWindowText was successful
		if (!bResult || ::GetWindowTextLength(m_hWnd) < (int)nLen)
			AfxThrowMemoryException();

		// remove old shadow buffer
		delete[] m_pShadowBuffer;
		m_pShadowBuffer = NULL;
		m_nShadowSize = 0;

		ASSERT_VALID(this);
		return;
	}
#endif
	LocalUnlock(hText);
	HLOCAL hOldText = GetEditCtrl().GetHandle();
	ASSERT(hOldText != NULL);
	LocalFree(hOldText);
	GetEditCtrl().SetHandle((HLOCAL)(UINT)(DWORD)hText);
	Invalidate();
	ASSERT_VALID(this);
}

void CEditView::WriteToArchive(CArchive& ar)
	// Write just the text to an archive, no length prefix.
{
	ASSERT_VALID(this);
	LPCTSTR lpszText = LockBuffer();
	ASSERT(lpszText != NULL);
	UINT nLen = GetBufferLength();
	TRY
	{
		ar.Write(lpszText, nLen*sizeof(TCHAR));
	}
	CATCH_ALL(e)
	{
		UnlockBuffer();
		THROW_LAST();
	}
	END_CATCH_ALL
	UnlockBuffer();
	ASSERT_VALID(this);
}

void CEditView::SerializeRaw(CArchive& ar)
	// Read/Write object as stand-alone file.
{
	ASSERT_VALID(this);
	if (ar.IsStoring())
	{
		WriteToArchive(ar);
	}
	else
	{
		CFile* pFile = ar.GetFile();
		ASSERT(pFile->GetPosition() == 0);
		DWORD nFileSize = pFile->GetLength();
		if (nFileSize/sizeof(TCHAR) > nMaxSize)
		{
			AfxMessageBox(AFX_IDP_FILE_TOO_LARGE);
			AfxThrowUserException();
		}
		// ReadFromArchive takes the number of characters as argument
		ReadFromArchive(ar, (UINT)nFileSize/sizeof(TCHAR));
	}
	ASSERT_VALID(this);
}

/////////////////////////////////////////////////////////////////////////////
// CEditView Printing Helpers

AFX_STATIC UINT AFXAPI _AfxEndOfLine(LPCTSTR lpszText, UINT nLen, UINT nIndex)
{
	ASSERT(AfxIsValidAddress(lpszText, nLen, FALSE));
	LPCTSTR lpsz = lpszText + nIndex;
	LPCTSTR lpszStop = lpszText + nLen;
	while (lpsz < lpszStop && *lpsz != '\r')
		++lpsz;
	return lpsz - lpszText;
}

AFX_STATIC UINT AFXAPI _AfxNextLine(LPCTSTR lpszText, UINT nLen, UINT nIndex)
{
	ASSERT(AfxIsValidAddress(lpszText, nLen, FALSE));
	LPCTSTR lpsz = lpszText + nIndex;
	LPCTSTR lpszStop = lpszText + nLen;
	while (lpsz < lpszStop && *lpsz == '\r')
		++lpsz;
	if (lpsz < lpszStop && *lpsz == '\n')
		++lpsz;
	return lpsz - lpszText;
}

AFX_STATIC UINT AFXAPI
_AfxClipLine(CDC* pDC, int aCharWidths[256], int cxLine, int nTabStop,
	LPCTSTR lpszText, UINT nIndex, UINT nIndexEnd)
{
	ASSERT_VALID(pDC);
	ASSERT(nIndex < nIndexEnd);
	ASSERT(AfxIsValidAddress(lpszText, nIndexEnd, FALSE));

	TEXTMETRIC tm;
	::GetTextMetrics(pDC->m_hDC, &tm);

	// make an initial guess on the number of characters that will fit
	int cx = 0;
	LPCTSTR lpszStart = lpszText + nIndex;
	LPCTSTR lpszStop = lpszText + nIndexEnd;
	LPCTSTR lpsz = lpszStart;
	while (lpsz < lpszStop)
	{
		if (*lpsz == '\t')
			cx += nTabStop - (cx % nTabStop);
		else
		{
#ifdef _UNICODE
			if (*lpsz <= 0xFF)
				cx += aCharWidths[(BYTE)*lpsz];
			else
				cx += tm.tmAveCharWidth;
#else //_UNICODE
			if (_afxDBCS && _istlead(*lpsz))
			{
				++lpsz;
				cx += tm.tmAveCharWidth;
			}
			else
				cx += aCharWidths[(BYTE)*lpsz];
#endif //!_UNICODE
		}
		++lpsz;
		if (cx > cxLine)
			break;
	}

	// adjust for errors in the guess
	cx = pDC->GetTabbedTextExtent(lpszStart, lpsz-lpszStart, 1, &nTabStop).cx;
	if (cx > cxLine)
	{
		// remove characters until it fits
		do
		{
			ASSERT(lpsz != lpszStart);
			if (_afxDBCS)
				lpsz = _tcsdec(lpszStart, lpsz);
			else
				--lpsz;
			cx = pDC->GetTabbedTextExtent(lpszStart, lpsz-lpszStart, 1, &nTabStop).cx;
		} while (cx > cxLine);
	}
	else if (cx < cxLine)
	{
		// add characters until it doesn't fit
		while (lpsz < lpszStop)
		{
			lpsz = _tcsinc(lpsz);
			ASSERT(lpsz <= lpszStop);
			cx = pDC->GetTabbedTextExtent(lpszStart, lpsz-lpszStart, 1, &nTabStop).cx;
			if (cx > cxLine)
			{
				if (_afxDBCS)
					lpsz = _tcsdec(lpszStart, lpsz);
				else
					--lpsz;
				break;
			}
		}
	}

	// return index of character just past the last that would fit
	return lpsz - lpszText;
}

/////////////////////////////////////////////////////////////////////////////
// CEditView Printing support

BOOL CEditView::OnPreparePrinting(CPrintInfo* pInfo)
{
	return DoPreparePrinting(pInfo);
}

void CEditView::OnBeginPrinting(CDC* pDC, CPrintInfo*)
{
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);
	// initialize page start vector
	ASSERT(m_aPageStart.GetSize() == 0);
	m_aPageStart.Add(0);
	ASSERT(m_aPageStart.GetSize() > 0);

	if (m_hPrinterFont == NULL)
	{
		// get current screen font object metrics
		CFont* pFont = GetFont();
		LOGFONT lf;
		LOGFONT lfSys;
		if (pFont == NULL)
			return;
		VERIFY(pFont->GetObject(sizeof(LOGFONT), &lf));
		VERIFY(::GetObject(::GetStockObject(SYSTEM_FONT), sizeof(LOGFONT),
			&lfSys));
		if (lstrcmpi((LPCTSTR)lf.lfFaceName, (LPCTSTR)lfSys.lfFaceName) == 0)
			return;

		// map to printer font metrics
		HDC hDCFrom = ::GetDC(NULL);
		lf.lfHeight = ::MulDiv(lf.lfHeight, pDC->GetDeviceCaps(LOGPIXELSY),
			::GetDeviceCaps(hDCFrom, LOGPIXELSY));
		lf.lfWidth = ::MulDiv(lf.lfWidth, pDC->GetDeviceCaps(LOGPIXELSX),
			::GetDeviceCaps(hDCFrom, LOGPIXELSX));
		::ReleaseDC(NULL, hDCFrom);

		// create it, if it fails we just use the printer's default.
		m_hMirrorFont = ::CreateFontIndirect(&lf);
		m_hPrinterFont = m_hMirrorFont;
	}
	ASSERT_VALID(this);
}

BOOL CEditView::PaginateTo(CDC* pDC, CPrintInfo* pInfo)
	// attempts pagination to pInfo->m_nCurPage, TRUE == success
{
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);

	CRect rectSave = pInfo->m_rectDraw;
	UINT nPageSave = pInfo->m_nCurPage;
	ASSERT(nPageSave > 1);
	ASSERT(nPageSave >= (UINT)m_aPageStart.GetSize());
	VERIFY(pDC->SaveDC() != 0);
	pDC->IntersectClipRect(0, 0, 0, 0);
	pInfo->m_nCurPage = m_aPageStart.GetSize();
	while (pInfo->m_nCurPage < nPageSave)
	{
		ASSERT(pInfo->m_nCurPage == (UINT)m_aPageStart.GetSize());
		OnPrepareDC(pDC, pInfo);
		ASSERT(pInfo->m_bContinuePrinting);
		pInfo->m_rectDraw.SetRect(0, 0,
			pDC->GetDeviceCaps(HORZRES), pDC->GetDeviceCaps(VERTRES));
		pDC->DPtoLP(&pInfo->m_rectDraw);
		OnPrint(pDC, pInfo);
		if (pInfo->m_nCurPage == (UINT)m_aPageStart.GetSize())
			break;
		++pInfo->m_nCurPage;
	}
	BOOL bResult = pInfo->m_nCurPage == nPageSave;
	pDC->RestoreDC(-1);
	pInfo->m_nCurPage = nPageSave;
	pInfo->m_rectDraw = rectSave;
	ASSERT_VALID(this);
	return bResult;
}

void CEditView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);
	ASSERT(pInfo != NULL);  // overriding OnPaint -- never get this.

	if (pInfo->m_nCurPage > (UINT)m_aPageStart.GetSize() &&
		!PaginateTo(pDC, pInfo))
	{
		// can't paginate to that page, thus cannot print it.
		pInfo->m_bContinuePrinting = FALSE;
	}
	ASSERT_VALID(this);
}

UINT CEditView::PrintInsideRect(CDC* pDC, RECT& rectLayout,
	UINT nIndexStart, UINT nIndexStop)
	// worker function for laying out text in a rectangle.
{
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);
	BOOL bWordWrap = (GetStyle() & ES_AUTOHSCROLL) == 0;

	// get buffer and real starting and ending postions
	UINT nLen = GetBufferLength();
	if (nIndexStart >= nLen)
		return nLen;
	LPCTSTR lpszText = LockBuffer();
	if (nIndexStop > nLen)
		nIndexStop = nLen;
	ASSERT(nIndexStart < nLen);

	// calculate text & tab metrics
	TEXTMETRIC tm;
	pDC->GetTextMetrics(&tm);
	int cyChar = tm.tmHeight + tm.tmExternalLeading;
	int nTabStop = m_nTabStops *
		pDC->GetTabbedTextExtent(_T("\t"), 1, 0, NULL).cx / 8 / 4;
	int aCharWidths[256];
	pDC->GetCharWidth(0, 255, aCharWidths);

	int y = rectLayout.top;
	UINT cx = rectLayout.right - rectLayout.left;
	UINT nIndex = nIndexStart;

	VERIFY(pDC->SaveDC() != 0);
	BOOL bLayoutOnly = pDC->IntersectClipRect(&rectLayout) == NULLREGION;

	do
	{
		UINT nIndexEnd = _AfxEndOfLine(lpszText, nIndexStop, nIndex);
		if (nIndex == nIndexEnd)
		{
			y += cyChar;
		}
		else if (bWordWrap)
		{
			// word-wrap printing
			do
			{
				UINT nIndexWrap = _AfxClipLine(pDC, aCharWidths,
					cx, nTabStop, lpszText, nIndex, nIndexEnd);
				UINT nIndexWord = nIndexWrap;
				if (nIndexWord != nIndexEnd)
				{
					while (nIndexWord > nIndex &&
					  !_istspace(lpszText[nIndexWord]))
					{
						nIndexWord--;
					}
					if (nIndexWord == nIndex)
						nIndexWord = nIndexWrap;
				}
				CRect rect(rectLayout.left, y, rectLayout.right, y+cyChar);
				if (!bLayoutOnly && pDC->RectVisible(rect))
				{
					pDC->TabbedTextOut(rect.left, y,
						(LPCTSTR)(lpszText+nIndex), nIndexWord-nIndex, 1,
						&nTabStop, rect.left);
				}
				y += cyChar;
				nIndex = nIndexWord;
				while (nIndex < nIndexEnd && _istspace(lpszText[nIndex]))
					nIndex++;
			} while (nIndex < nIndexEnd && y+cyChar <= rectLayout.bottom);

			nIndexEnd = nIndex;
		}
		else
		{
			// non-word wrap printing (much easier and faster)
			CRect rect(rectLayout.left, y, rectLayout.right, y+cyChar);
			if (!bLayoutOnly && pDC->RectVisible(rect))
			{
				UINT nIndexClip = _AfxClipLine(pDC, aCharWidths, cx, nTabStop,
					lpszText, nIndex, nIndexEnd);
				if (nIndexClip < nIndexEnd)
				{
					if (_istlead(*(lpszText+nIndexClip)))
						nIndexClip++;
					nIndexClip++;
				}
				pDC->TabbedTextOut(rect.left, y,
					(LPCTSTR)(lpszText+nIndex), nIndexClip-nIndex, 1,
					&nTabStop, rect.left);
			}
			y += cyChar;
		}
		nIndex = _AfxNextLine(lpszText, nIndexStop, nIndexEnd);
	}
	while (nIndex < nIndexStop && y+cyChar <= rectLayout.bottom);

	pDC->RestoreDC(-1);
	UnlockBuffer();
	ASSERT_VALID(this);

	rectLayout.bottom = y;
	return nIndex;
}

void CEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);
	ASSERT(pInfo != NULL);
	ASSERT(pInfo->m_bContinuePrinting);

	CFont* pOldFont = NULL;
	if (m_hPrinterFont != NULL)
		pOldFont = pDC->SelectObject(CFont::FromHandle(m_hPrinterFont));
	pDC->SetBkMode(TRANSPARENT);

	UINT nPage = pInfo->m_nCurPage;
	ASSERT(nPage <= (UINT)m_aPageStart.GetSize());
	UINT nIndex = m_aPageStart[nPage-1];

	// print as much as possible in the current page.
	nIndex = PrintInsideRect(pDC, pInfo->m_rectDraw, nIndex, GetBufferLength());

	if (pOldFont != NULL)
		pDC->SelectObject(pOldFont);

	// update pagination information for page just printed
	if (nPage == (UINT)m_aPageStart.GetSize())
	{
		if (nIndex < GetBufferLength())
			m_aPageStart.Add(nIndex);
	}
	else
	{
		ASSERT(nPage+1 <= (UINT)m_aPageStart.GetSize());
		ASSERT(nIndex == m_aPageStart[nPage+1-1]);
	}
}

void CEditView::OnEndPrinting(CDC*, CPrintInfo*)
{
	ASSERT_VALID(this);

	m_aPageStart.RemoveAll();
	if (m_hMirrorFont != NULL && m_hPrinterFont == m_hMirrorFont)
	{
		AfxDeleteObject((HGDIOBJ*)&m_hMirrorFont);
		m_hPrinterFont = NULL;
	}
}

/////////////////////////////////////////////////////////////////////////////
// CEditView commands

void CEditView::OnUpdateNeedSel(CCmdUI* pCmdUI)
{
	ASSERT_VALID(this);
	int nStartChar, nEndChar;
	GetEditCtrl().GetSel(nStartChar, nEndChar);
	pCmdUI->Enable(nStartChar != nEndChar);
	ASSERT_VALID(this);
}

void CEditView::OnUpdateNeedClip(CCmdUI* pCmdUI)
{
	ASSERT_VALID(this);
	pCmdUI->Enable(::IsClipboardFormatAvailable(CF_TEXT));
	ASSERT_VALID(this);
}

void CEditView::OnUpdateNeedText(CCmdUI* pCmdUI)
{
	ASSERT_VALID(this);
	pCmdUI->Enable(GetWindowTextLength() != 0);
	ASSERT_VALID(this);
}

void CEditView::OnUpdateNeedFind(CCmdUI* pCmdUI)
{
	ASSERT_VALID(this);
	_AFX_EDIT_STATE* pEditState = _afxEditState;
	pCmdUI->Enable(GetWindowTextLength() != 0 &&
		!pEditState->strFind.IsEmpty());
	ASSERT_VALID(this);
}

void CEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
{
	ASSERT_VALID(this);
	pCmdUI->Enable(GetEditCtrl().CanUndo());
	ASSERT_VALID(this);
}

BOOL CEditView::OnEditChange()
{
	ASSERT_VALID(this);
	GetDocument()->SetModifiedFlag();
	ASSERT_VALID(this);

	return FALSE;   // continue routing
}

void CEditView::OnEditCut()
{
	ASSERT_VALID(this);
	GetEditCtrl().Cut();
	ASSERT_VALID(this);
}

void CEditView::OnEditCopy()
{
	ASSERT_VALID(this);
	GetEditCtrl().Copy();
	ASSERT_VALID(this);
}

void CEditView::OnEditPaste()
{
	ASSERT_VALID(this);
	GetEditCtrl().Paste();
	ASSERT_VALID(this);
}

void CEditView::OnEditClear()
{
	ASSERT_VALID(this);
	GetEditCtrl().Clear();
	ASSERT_VALID(this);
}

void CEditView::OnEditUndo()
{
	ASSERT_VALID(this);
	GetEditCtrl().Undo();
	ASSERT_VALID(this);
}

void CEditView::OnEditSelectAll()
{
	ASSERT_VALID(this);
	GetEditCtrl().SetSel(0, -1);
	ASSERT_VALID(this);
}

/////////////////////////////////////////////////////////////////////////////
// CEditView Font Handling

LRESULT CEditView::OnSetFont(WPARAM, LPARAM)
{
	ASSERT_VALID(this);
	Default();
	GetEditCtrl().SetTabStops(m_nTabStops);
	ASSERT_VALID(this);
	return 0;
}

void CEditView::SetPrinterFont(CFont* pFont)
{
	ASSERT_VALID(this);
	m_hPrinterFont = (HFONT)pFont->GetSafeHandle();
	ASSERT_VALID(this);
}

CFont* CEditView::GetPrinterFont() const
{
	ASSERT_VALID(this);
	return CFont::FromHandle(m_hPrinterFont);
}

/////////////////////////////////////////////////////////////////////////////
// CEditView attributes

LPCTSTR CEditView::LockBuffer() const
{
	ASSERT_VALID(this);
	ASSERT(m_hWnd != NULL);
#ifndef _UNICODE
	if (afxData.bWin95)
	{
		// under Win32s, it is necessary to maintain a shadow buffer
		//  it is only updated when the control contents have been changed.
		if (m_pShadowBuffer == NULL || GetEditCtrl().GetModify())
		{
			ASSERT(m_pShadowBuffer != NULL || m_nShadowSize == 0);
			UINT nSize = GetWindowTextLength()+1;
			if (nSize > m_nShadowSize)
			{
				// need more room for shadow buffer
				CEditView* pThis = (CEditView*)this;
				delete[] m_pShadowBuffer;
				pThis->m_pShadowBuffer = NULL;
				pThis->m_nShadowSize = 0;
				pThis->m_pShadowBuffer = new TCHAR[nSize];
				pThis->m_nShadowSize = nSize;
			}

			// update the shadow buffer with GetWindowText
			ASSERT(m_nShadowSize >= nSize);
			ASSERT(m_pShadowBuffer != NULL);
			GetWindowText(m_pShadowBuffer, nSize);

			// turn off edit control's modify bit
			GetEditCtrl().SetModify(FALSE);
		}
		return m_pShadowBuffer;
	}
#endif
	// else -- running under non-subset Win32 system
	HLOCAL hLocal = GetEditCtrl().GetHandle();
	ASSERT(hLocal != NULL);
	LPCTSTR lpszText = (LPCTSTR)LocalLock(hLocal);
	ASSERT(lpszText != NULL);
	ASSERT_VALID(this);
	return lpszText;
}

void CEditView::UnlockBuffer() const
{
	ASSERT_VALID(this);
	ASSERT(m_hWnd != NULL);
#ifndef _UNICODE
	if (afxData.bWin95)
		return;
#endif
	HLOCAL hLocal = GetEditCtrl().GetHandle();
	ASSERT(hLocal != NULL);
	LocalUnlock(hLocal);
}

// this function returns the length in characters
UINT CEditView::GetBufferLength() const
{
	ASSERT_VALID(this);
	ASSERT(m_hWnd != NULL);
	LPCTSTR lpszText = LockBuffer();
	UINT nLen = lstrlen(lpszText);
	UnlockBuffer();
	return nLen;
}

void CEditView::GetSelectedText(CString& strResult) const
{
	ASSERT_VALID(this);
	int nStartChar, nEndChar;
	GetEditCtrl().GetSel(nStartChar, nEndChar);
	ASSERT((UINT)nEndChar <= GetBufferLength());
	LPCTSTR lpszText = ((CEditView*)this)->LockBuffer();
	UINT nLen = _AfxEndOfLine(lpszText, nEndChar, nStartChar) - nStartChar;
	memcpy(strResult.GetBuffer(nLen), lpszText + nStartChar,
		nLen * sizeof(TCHAR));
	strResult.ReleaseBuffer(nLen);
	UnlockBuffer();
	ASSERT_VALID(this);
}

/////////////////////////////////////////////////////////////////////////////
// CEditView Find & Replace

void CEditView::OnEditFind()
{
	ASSERT_VALID(this);
	OnEditFindReplace(TRUE);
	ASSERT_VALID(this);
}

void CEditView::OnEditReplace()
{
	ASSERT_VALID(this);
	OnEditFindReplace(FALSE);
	ASSERT_VALID(this);
}

void CEditView::OnEditRepeat()
{
	ASSERT_VALID(this);
	_AFX_EDIT_STATE* pEditState = _afxEditState;
	if (!FindText(pEditState->strFind,
		pEditState->bNext,
		pEditState->bCase))
	{
		OnTextNotFound(pEditState->strFind);
	}
	ASSERT_VALID(this);
}

void CEditView::OnEditFindReplace(BOOL bFindOnly)
{
	ASSERT_VALID(this);
	_AFX_EDIT_STATE* pEditState = _afxEditState;
	if (pEditState->pFindReplaceDlg != NULL)
	{
		if (pEditState->bFindOnly == bFindOnly)
		{
			pEditState->pFindReplaceDlg->SetActiveWindow();
			pEditState->pFindReplaceDlg->ShowWindow(SW_SHOW);
			return;
		}
		ASSERT(pEditState->bFindOnly != bFindOnly);
		pEditState->pFindReplaceDlg->SendMessage(WM_CLOSE);
		ASSERT(pEditState->pFindReplaceDlg == NULL);
		ASSERT_VALID(this);
	}

	CString strFind;
	GetSelectedText(strFind);
	if (strFind.IsEmpty())
		strFind = pEditState->strFind;
	CString strReplace = pEditState->strReplace;
	pEditState->pFindReplaceDlg = new CFindReplaceDialog;
	ASSERT(pEditState->pFindReplaceDlg != NULL);
	DWORD dwFlags = FR_HIDEWHOLEWORD;
	if (pEditState->bNext)
		dwFlags |= FR_DOWN;
	if (pEditState->bCase)
		dwFlags |= FR_MATCHCASE;
	if (!pEditState->pFindReplaceDlg->Create(bFindOnly, strFind,
		strReplace, dwFlags, this))
	{
		pEditState->pFindReplaceDlg = NULL;
		ASSERT_VALID(this);
		return;
	}

	pEditState->pFindReplaceDlg->SetActiveWindow();
	pEditState->pFindReplaceDlg->ShowWindow(SW_SHOW);
	ASSERT(pEditState->pFindReplaceDlg != NULL);
	pEditState->bFindOnly = bFindOnly;
	ASSERT_VALID(this);
}

void CEditView::OnFindNext(LPCTSTR lpszFind, BOOL bNext, BOOL bCase)
{
	ASSERT_VALID(this);
	_AFX_EDIT_STATE* pEditState = _afxEditState;
	pEditState->strFind = lpszFind;
	pEditState->bCase = bCase;
	pEditState->bNext = bNext;

	if (!FindText(pEditState->strFind, bNext, bCase))
		OnTextNotFound(pEditState->strFind);
	ASSERT_VALID(this);
}

void CEditView::OnReplaceSel(LPCTSTR lpszFind, BOOL bNext, BOOL bCase,
	LPCTSTR lpszReplace)
{
	ASSERT_VALID(this);
	_AFX_EDIT_STATE* pEditState = _afxEditState;
	pEditState->strFind = lpszFind;
	pEditState->strReplace = lpszReplace;
	pEditState->bCase = bCase;
	pEditState->bNext = bNext;

	if (!InitializeReplace())
		return;

	GetEditCtrl().ReplaceSel(pEditState->strReplace);
	FindText(pEditState->strFind, bNext, bCase);
	ASSERT_VALID(this);
}

void CEditView::OnReplaceAll(LPCTSTR lpszFind, LPCTSTR lpszReplace, BOOL bCase)
{
	ASSERT_VALID(this);
	_AFX_EDIT_STATE* pEditState = _afxEditState;
	pEditState->strFind = lpszFind;
	pEditState->strReplace = lpszReplace;
	pEditState->bCase = bCase;
	pEditState->bNext = TRUE;

	if (!InitializeReplace() &&
		!SameAsSelected(pEditState->strFind, pEditState->bCase))
	{
		// initial find was not successful
		return;
	}

	do
	{
		GetEditCtrl().ReplaceSel(pEditState->strReplace);
	} while (FindText(pEditState->strFind, 1, bCase));

	ASSERT_VALID(this);
}

BOOL CEditView::InitializeReplace()
	// helper to do find first if no selection
{
	ASSERT_VALID(this);

	_AFX_EDIT_STATE* pEditState = _afxEditState;

	// do find next if no selection
	int nStartChar, nEndChar;
	GetEditCtrl().GetSel(nStartChar, nEndChar);
	if (nStartChar == nEndChar)
	{
		if (!FindText(pEditState->strFind, pEditState->bNext,
			pEditState->bCase))
		{
			// text not found
			OnTextNotFound(pEditState->strFind);
		}
		return FALSE;
	}

	if (!SameAsSelected(pEditState->strFind, pEditState->bCase))
	{
		if (!FindText(pEditState->strFind, pEditState->bNext,
			pEditState->bCase))
		{
			// text not found
			OnTextNotFound(pEditState->strFind);
		}
		return FALSE;
	}

	ASSERT_VALID(this);
	return TRUE;
}

LRESULT CEditView::OnFindReplaceCmd(WPARAM, LPARAM lParam)
{
	ASSERT_VALID(this);

	_AFX_EDIT_STATE* pEditState = _afxEditState;
	CFindReplaceDialog* pDialog = CFindReplaceDialog::GetNotifier(lParam);
	ASSERT(pDialog != NULL);
	ASSERT(pDialog == pEditState->pFindReplaceDlg);
	if (pDialog->IsTerminating())
	{
		pEditState->pFindReplaceDlg = NULL;
	}
	else if (pDialog->FindNext())
	{
		OnFindNext(pDialog->GetFindString(),
			pDialog->SearchDown(), pDialog->MatchCase());
	}
	else if (pDialog->ReplaceCurrent())
	{
		ASSERT(!pEditState->bFindOnly);
		OnReplaceSel(pDialog->GetFindString(),
			pDialog->SearchDown(), pDialog->MatchCase(),
			pDialog->GetReplaceString());
	}
	else if (pDialog->ReplaceAll())
	{
		ASSERT(!pEditState->bFindOnly);
		OnReplaceAll(pDialog->GetFindString(), pDialog->GetReplaceString(),
			pDialog->MatchCase());
	}
	ASSERT_VALID(this);
	return 0;
}

typedef int (WINAPI* AFX_COMPARE_PROC)(LPCTSTR str1, LPCTSTR str2);

BOOL CEditView::SameAsSelected(LPCTSTR lpszCompare, BOOL bCase)
{
	// check length first
	size_t nLen = lstrlen(lpszCompare);
	int nStartChar, nEndChar;
	GetEditCtrl().GetSel(nStartChar, nEndChar);
	if (nLen != (size_t)(nEndChar - nStartChar))
		return FALSE;

	// length is the same, check contents
	CString strSelect;
	GetSelectedText(strSelect);
	return (bCase && lstrcmp(lpszCompare, strSelect) == 0) ||
		(!bCase && lstrcmpi(lpszCompare, strSelect) == 0);
}

BOOL CEditView::FindText(LPCTSTR lpszFind, BOOL bNext, BOOL bCase)
{
	ASSERT_VALID(this);
	ASSERT(lpszFind != NULL);
	ASSERT(*lpszFind != '\0');

	UINT nLen = GetBufferLength();
	int nStartChar, nEndChar;
	GetEditCtrl().GetSel(nStartChar, nEndChar);
	UINT nStart = nStartChar;
	int iDir = bNext ? +1 : -1;

	// can't find a match before the first character
	if (nStart == 0 && iDir < 0)
		return FALSE;

	CWaitCursor wait;
	LPCTSTR lpszText = LockBuffer();

	if (iDir < 0)
	{
		// always go back one for search backwards
		nStart -= (lpszText+nStart) -
			_tcsdec(lpszText, lpszText+nStart);
	}
	else if (nStartChar != nEndChar && SameAsSelected(lpszFind, bCase))
	{
		// easy to go backward/forward with SBCS
		if (_istlead(lpszText[nStart]))
			nStart++;
		nStart += iDir;
	}

	// handle search with nStart past end of buffer
	size_t nLenFind = lstrlen(lpszFind);
	if (nStart+nLenFind-1 >= nLen)
	{
		if (iDir < 0 && nLen >= nLenFind)
		{
			if (_afxDBCS)
			{
				// walk back to previous character n times
				nStart = nLen;
				int n = nLenFind;
				while (n--)
				{
					nStart -= (lpszText+nStart) -
						_tcsdec(lpszText, lpszText+nStart);
				}
			}
			else
			{
				// single-byte character set is easy and fast
				nStart = nLen - nLenFind;
			}
			ASSERT(nStart+nLenFind-1 <= nLen);
		}
		else
		{
			UnlockBuffer();
			return FALSE;
		}
	}

	// start the search at nStart
	LPCTSTR lpsz = lpszText + nStart;
	AFX_COMPARE_PROC pfnCompare = bCase ? lstrcmp : lstrcmpi;

	if (_afxDBCS)
	{
		// double-byte string search
		LPCTSTR lpszStop;
		if (iDir > 0)
		{
			// start at current and find _first_ occurrance
			lpszStop = lpszText + nLen - nLenFind + 1;
		}
		else
		{
			// start at top and find _last_ occurrance
			lpszStop = lpsz;
			lpsz = lpszText;
		}

		LPCTSTR lpszFound = NULL;
		while (lpsz <= lpszStop)
		{
			if (!bCase || (*lpsz == *lpszFind &&
				(!_istlead(*lpsz) || lpsz[1] == lpszFind[1])))
			{
				LPTSTR lpch = (LPTSTR)(lpsz + nLenFind);
				TCHAR chSave = *lpch;
				*lpch = '\0';
				int nResult = (*pfnCompare)(lpsz, lpszFind);
				*lpch = chSave;
				if (nResult == 0)
				{
					lpszFound = lpsz;
					if (iDir > 0)
						break;
				}
			}
			lpsz = _tcsinc(lpsz);
		}
		UnlockBuffer();

		if (lpszFound != NULL)
		{
			int n = (int)(lpszFound - lpszText);
			GetEditCtrl().SetSel(n, n+nLenFind);
			return TRUE;
		}
	}
	else
	{
		// single-byte string search
		UINT nCompare;
		if (iDir < 0)
			nCompare = (UINT)(lpsz - lpszText) + 1;
		else
			nCompare = nLen - (UINT)(lpsz - lpszText) - nLenFind + 1;

		while (nCompare > 0)
		{
			ASSERT(lpsz >= lpszText);
			ASSERT(lpsz+nLenFind-1 <= lpszText+nLen-1);

			LPSTR lpch = (LPSTR)(lpsz + nLenFind);
			char chSave = *lpch;
			*lpch = '\0';
			int nResult = (*pfnCompare)(lpsz, lpszFind);
			*lpch = chSave;
			if (nResult == 0)
			{
				UnlockBuffer();
				int n = (int)(lpsz - lpszText);
				GetEditCtrl().SetSel(n, n+nLenFind);
				ASSERT_VALID(this);
				return TRUE;
			}

			// restore character at end of search
			*lpch = chSave;

			// move on to next substring
			nCompare--;
			lpsz += iDir;
		}
		UnlockBuffer();
	}

	ASSERT_VALID(this);
	return FALSE;
}

void CEditView::OnTextNotFound(LPCTSTR)
{
	ASSERT_VALID(this);
	MessageBeep(0);
}

/////////////////////////////////////////////////////////////////////////////
// CEditView Tab Stops

void CEditView::SetTabStops(int nTabStops)
{
	ASSERT_VALID(this);
	m_nTabStops = nTabStops;
	GetEditCtrl().SetTabStops(m_nTabStops);
	Invalidate();
	ASSERT_VALID(this);
}

/////////////////////////////////////////////////////////////////////////////
// CEditView diagnostics

#ifdef _DEBUG
void CEditView::AssertValid() const
{
	CCtrlView::AssertValid();
	ASSERT_VALID(&m_aPageStart);
	if (m_hPrinterFont != NULL)
		ASSERT_VALID(CFont::FromHandle(m_hPrinterFont));
	if (m_hMirrorFont != NULL)
		ASSERT_VALID(CFont::FromHandle(m_hMirrorFont));
	_AFX_EDIT_STATE* pEditState = _afxEditState;
	if (pEditState->pFindReplaceDlg != NULL)
		ASSERT_VALID(pEditState->pFindReplaceDlg);
}

void CEditView::Dump(CDumpContext& dc) const
{
	CCtrlView::Dump(dc);

	dc << "m_nTabStops = " << m_nTabStops;
	if (m_hPrinterFont != NULL)
		dc << "\nm_hPrinterFont " << (UINT)m_hPrinterFont;
	if (m_hMirrorFont != NULL)
		dc << "\nm_hMirrorFont " << (UINT)m_hMirrorFont;
	dc << "\nm_aPageStart: " << &m_aPageStart;
	dc << "\nstatic member data:";
	_AFX_EDIT_STATE* pEditState = _afxEditState;
	if (pEditState->pFindReplaceDlg != NULL)
	{
		dc << "\npFindReplaceDlg = "
			<< (void*)pEditState->pFindReplaceDlg;
		dc << "\nbFindOnly = " << pEditState->bFindOnly;
	}
	dc << "\nstrFind = " << pEditState->strFind;
	dc << "\nstrReplace = " << pEditState->strReplace;
	dc << "\nbCase = " << pEditState->bCase;
	dc << "\nbNext = " << pEditState->bNext;

	dc << "\n";
}
#endif //_DEBUG

#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif

IMPLEMENT_DYNCREATE(CEditView, CCtrlView)

#pragma warning(disable: 4074)
#pragma init_seg(lib)

PROCESS_LOCAL(_AFX_EDIT_STATE, _afxEditState)

/////////////////////////////////////////////////////////////////////////////
