diff foosdk/wtl/Include/atlfind.h @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/wtl/Include/atlfind.h	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,884 @@
+// Windows Template Library - WTL version 10.0
+// Copyright (C) Microsoft Corporation, WTL Team. All rights reserved.
+//
+// This file is a part of the Windows Template Library.
+// The use and distribution terms for this software are covered by the
+// Microsoft Public License (http://opensource.org/licenses/MS-PL)
+// which can be found in the file MS-PL.txt at the root folder.
+
+#ifndef __ATLFIND_H__
+#define __ATLFIND_H__
+
+#pragma once
+
+#ifndef __ATLCTRLS_H__
+	#error atlfind.h requires atlctrls.h to be included first
+#endif
+
+#ifndef __ATLDLGS_H__
+	#error atlfind.h requires atldlgs.h to be included first
+#endif
+
+#ifndef __ATLSTR_H__
+	#error atlfind.h requires CString
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Classes in this file:
+//
+// CEditFindReplaceImplBase<T, TFindReplaceDialog>
+// CEditFindReplaceImpl<T, TFindReplaceDialog>
+// CRichEditFindReplaceImpl<T, TFindReplaceDialog>
+
+
+namespace WTL
+{
+
+///////////////////////////////////////////////////////////////////////////////
+// CEditFindReplaceImplBase - Base class for mixin classes that
+// help implement Find/Replace for CEdit or CRichEditCtrl based window classes.
+
+template <class T, class TFindReplaceDialog = CFindReplaceDialog>
+class CEditFindReplaceImplBase
+{
+protected:
+// Typedefs
+	typedef CEditFindReplaceImplBase<T, TFindReplaceDialog> thisClass;
+
+// Enumerations
+	enum TranslationTextItem
+	{
+		eText_OnReplaceAllMessage   = 0,
+		eText_OnReplaceAllTitle     = 1,
+		eText_OnTextNotFoundMessage = 2,
+		eText_OnTextNotFoundTitle   = 3
+	};
+
+public:
+// Data members
+	TFindReplaceDialog* m_pFindReplaceDialog;
+	ATL::CString m_sFindNext, m_sReplaceWith;
+	BOOL m_bFindOnly, m_bFirstSearch, m_bMatchCase, m_bWholeWord, m_bFindDown;
+	LONG m_nInitialSearchPos;
+	HCURSOR m_hOldCursor;
+
+// Constructors
+	CEditFindReplaceImplBase() :
+		m_pFindReplaceDialog(NULL),
+		m_bFindOnly(TRUE),
+		m_bFirstSearch(TRUE),
+		m_bMatchCase(FALSE),
+		m_bWholeWord(FALSE),
+		m_bFindDown(TRUE),
+		m_nInitialSearchPos(0),
+		m_hOldCursor(NULL)
+	{
+	}
+
+// Message Handlers
+	BEGIN_MSG_MAP(thisClass)
+	ALT_MSG_MAP(1)
+		MESSAGE_HANDLER(TFindReplaceDialog::GetFindReplaceMsg(), OnFindReplaceCmd)
+		COMMAND_ID_HANDLER(ID_EDIT_FIND, OnEditFind)
+		COMMAND_ID_HANDLER(ID_EDIT_REPEAT, OnEditRepeat)
+		COMMAND_ID_HANDLER(ID_EDIT_REPLACE, OnEditReplace)
+	END_MSG_MAP()
+
+	LRESULT OnFindReplaceCmd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+
+		TFindReplaceDialog* pDialog = TFindReplaceDialog::GetNotifier(lParam);
+		if(pDialog == NULL)
+		{
+			ATLASSERT(FALSE);
+			::MessageBeep(MB_ICONERROR);
+			return 1;
+		}
+		ATLASSERT(pDialog == m_pFindReplaceDialog);
+
+		LPFINDREPLACE findReplace = (LPFINDREPLACE)lParam;
+		if((m_pFindReplaceDialog != NULL) && (findReplace != NULL))
+		{
+			if(pDialog->FindNext())
+			{
+				pT->OnFindNext(pDialog->GetFindString(), pDialog->SearchDown(),
+					pDialog->MatchCase(), pDialog->MatchWholeWord());
+			}
+			else if(pDialog->ReplaceCurrent())
+			{
+				pT->OnReplaceSel(pDialog->GetFindString(),
+					pDialog->SearchDown(), pDialog->MatchCase(), pDialog->MatchWholeWord(),
+					pDialog->GetReplaceString());
+			}
+			else if(pDialog->ReplaceAll())
+			{
+				pT->OnReplaceAll(pDialog->GetFindString(), pDialog->GetReplaceString(),
+					pDialog->MatchCase(), pDialog->MatchWholeWord());
+			}
+			else if(pDialog->IsTerminating())
+			{
+				// Dialog is going away (but hasn't gone away yet)
+				// OnFinalMessage will "delete this"
+				pT->OnTerminatingFindReplaceDialog(m_pFindReplaceDialog);
+				m_pFindReplaceDialog = NULL;
+			}
+		}
+
+		return 0;
+	}
+
+	LRESULT OnEditFind(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		pT->FindReplace(TRUE);
+
+		return 0;
+	}
+
+	LRESULT OnEditRepeat(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+
+		// If the user is holding down SHIFT when hitting F3, we'll
+		// search in reverse. Otherwise, we'll search forward.
+		// (be sure to have an accelerator mapped to ID_EDIT_REPEAT
+		// for both F3 and Shift+F3)
+		m_bFindDown = !((::GetKeyState(VK_SHIFT) & 0x8000) == 0x8000);
+
+		if(m_sFindNext.IsEmpty())
+		{
+			pT->FindReplace(TRUE);
+		}
+		else
+		{
+			if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown))
+				pT->TextNotFound(m_sFindNext);
+		}
+
+		return 0;
+	}
+
+	LRESULT OnEditReplace(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& bHandled)
+	{
+		T* pT = static_cast<T*>(this);
+
+		DWORD style = pT->GetStyle();
+		if((style & ES_READONLY) != ES_READONLY)
+		{
+			pT->FindReplace(FALSE);
+		}
+		else
+		{
+			// Don't allow replace when the edit control is read only
+			bHandled = FALSE;
+		}
+
+		return 0;
+	}
+
+// Operations (overrideable)
+	TFindReplaceDialog* CreateFindReplaceDialog(BOOL bFindOnly, // TRUE for Find, FALSE for FindReplace
+			LPCTSTR lpszFindWhat,
+			LPCTSTR lpszReplaceWith = NULL,
+			DWORD dwFlags = FR_DOWN,
+			HWND hWndParent = NULL)
+	{
+		// You can override all of this in a derived class
+
+		TFindReplaceDialog* findReplaceDialog = new TFindReplaceDialog();
+		if(findReplaceDialog == NULL)
+		{
+			::MessageBeep(MB_ICONHAND);
+		}
+		else
+		{
+			HWND hWndFindReplace = findReplaceDialog->Create(bFindOnly,
+				lpszFindWhat, lpszReplaceWith, dwFlags, hWndParent);
+			if(hWndFindReplace == NULL)
+			{
+				delete findReplaceDialog;
+				findReplaceDialog = NULL;
+			}
+			else
+			{
+				findReplaceDialog->SetActiveWindow();
+				findReplaceDialog->ShowWindow(SW_SHOW);
+			}
+		}
+
+		return findReplaceDialog;
+	}
+
+	void AdjustDialogPosition(HWND hWndDialog)
+	{
+		ATLASSERT((hWndDialog != NULL) && ::IsWindow(hWndDialog));
+
+		T* pT = static_cast<T*>(this);
+		LONG nStartChar = 0, nEndChar = 0;
+		// Send EM_GETSEL so we can use both Edit and RichEdit
+		// (CEdit::GetSel uses int&, and CRichEditCtrlT::GetSel uses LONG&)
+		::SendMessage(pT->m_hWnd, EM_GETSEL, (WPARAM)&nStartChar, (LPARAM)&nEndChar);
+		POINT point = pT->PosFromChar(nStartChar);
+		pT->ClientToScreen(&point);
+		RECT rect = {};
+		::GetWindowRect(hWndDialog, &rect);
+		if(::PtInRect(&rect, point) != FALSE)
+		{
+			if(point.y > (rect.bottom - rect.top))
+			{
+				::OffsetRect(&rect, 0, point.y - rect.bottom - 20);
+			}
+			else
+			{
+				int nVertExt = GetSystemMetrics(SM_CYSCREEN);
+				if((point.y + (rect.bottom - rect.top)) < nVertExt)
+					::OffsetRect(&rect, 0, 40 + point.y - rect.top);
+			}
+
+			::MoveWindow(hWndDialog, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
+		}
+	}
+
+	DWORD GetFindReplaceDialogFlags() const
+	{
+		DWORD dwFlags = 0;
+		if(m_bFindDown)
+			dwFlags |= FR_DOWN;
+		if(m_bMatchCase)
+			dwFlags |= FR_MATCHCASE;
+		if(m_bWholeWord)
+			dwFlags |= FR_WHOLEWORD;
+
+		return dwFlags;
+	}
+
+	void FindReplace(BOOL bFindOnly)
+	{
+		T* pT = static_cast<T*>(this);
+		m_bFirstSearch = TRUE;
+		if(m_pFindReplaceDialog != NULL)
+		{
+			if(m_bFindOnly == bFindOnly)
+			{
+				m_pFindReplaceDialog->SetActiveWindow();
+				m_pFindReplaceDialog->ShowWindow(SW_SHOW);
+				return;
+			}
+			else
+			{
+				m_pFindReplaceDialog->SendMessage(WM_CLOSE);
+				ATLASSERT(m_pFindReplaceDialog == NULL);
+			}
+		}
+
+		ATLASSERT(m_pFindReplaceDialog == NULL);
+
+		ATL::CString findNext;
+		pT->GetSelText(findNext);
+		// if selection is empty or spans multiple lines use old find text
+		if(findNext.IsEmpty() || (findNext.FindOneOf(_T("\n\r")) != -1))
+			findNext = m_sFindNext;
+		ATL::CString replaceWith = m_sReplaceWith;
+		DWORD dwFlags = pT->GetFindReplaceDialogFlags();
+
+		m_pFindReplaceDialog = pT->CreateFindReplaceDialog(bFindOnly,
+			findNext, replaceWith, dwFlags, pT->operator HWND());
+		ATLASSERT(m_pFindReplaceDialog != NULL);
+		if(m_pFindReplaceDialog != NULL)
+			m_bFindOnly = bFindOnly;
+	}
+
+	BOOL SameAsSelected(LPCTSTR lpszCompare, BOOL bMatchCase, BOOL /*bWholeWord*/)
+	{
+		T* pT = static_cast<T*>(this);
+
+		// check length first
+		size_t nLen = lstrlen(lpszCompare);
+		LONG nStartChar = 0, nEndChar = 0;
+		// Send EM_GETSEL so we can use both Edit and RichEdit
+		// (CEdit::GetSel uses int&, and CRichEditCtrlT::GetSel uses LONG&)
+		::SendMessage(pT->m_hWnd, EM_GETSEL, (WPARAM)&nStartChar, (LPARAM)&nEndChar);
+		if(nLen != (size_t)(nEndChar - nStartChar))
+			return FALSE;
+
+		// length is the same, check contents
+		ATL::CString selectedText;
+		pT->GetSelText(selectedText);
+
+		return (bMatchCase && (selectedText.Compare(lpszCompare) == 0)) ||
+			(!bMatchCase && (selectedText.CompareNoCase(lpszCompare) == 0));
+	}
+
+	void TextNotFound(LPCTSTR lpszFind)
+	{
+		T* pT = static_cast<T*>(this);
+		m_bFirstSearch = TRUE;
+		pT->OnTextNotFound(lpszFind);
+	}
+
+	ATL::CString GetTranslationText(enum TranslationTextItem eItem) const
+	{
+		ATL::CString text;
+		switch(eItem)
+		{
+		case eText_OnReplaceAllMessage:
+			text = _T("Replaced %d occurances of \"%s\" with \"%s\"");
+			break;
+		case eText_OnReplaceAllTitle:
+			text = _T("Replace All");
+			break;
+		case eText_OnTextNotFoundMessage:
+			text = _T("Unable to find the text \"%s\"");
+			break;
+		case eText_OnTextNotFoundTitle:
+			text = _T("Text not found");
+			break;
+		}
+
+		return text;
+	}
+
+// Overrideable Handlers
+	void OnFindNext(LPCTSTR lpszFind, BOOL bFindDown, BOOL bMatchCase, BOOL bWholeWord)
+	{
+		T* pT = static_cast<T*>(this);
+
+		m_sFindNext = lpszFind;
+		m_bMatchCase = bMatchCase;
+		m_bWholeWord = bWholeWord;
+		m_bFindDown = bFindDown;
+
+		if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown))
+			pT->TextNotFound(m_sFindNext);
+		else
+			pT->AdjustDialogPosition(m_pFindReplaceDialog->operator HWND());
+	}
+
+	void OnReplaceSel(LPCTSTR lpszFind, BOOL bFindDown, BOOL bMatchCase, BOOL bWholeWord, LPCTSTR lpszReplace)
+	{
+		T* pT = static_cast<T*>(this);
+
+		m_sFindNext = lpszFind;
+		m_sReplaceWith = lpszReplace;
+		m_bMatchCase = bMatchCase;
+		m_bWholeWord = bWholeWord;
+		m_bFindDown = bFindDown;
+
+		if(pT->SameAsSelected(m_sFindNext, m_bMatchCase, m_bWholeWord))
+			pT->ReplaceSel(m_sReplaceWith);
+
+		if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown))
+			pT->TextNotFound(m_sFindNext);
+		else
+			pT->AdjustDialogPosition(m_pFindReplaceDialog->operator HWND());
+	}
+
+	void OnReplaceAll(LPCTSTR lpszFind, LPCTSTR lpszReplace, BOOL bMatchCase, BOOL bWholeWord)
+	{
+		T* pT = static_cast<T*>(this);
+
+		m_sFindNext = lpszFind;
+		m_sReplaceWith = lpszReplace;
+		m_bMatchCase = bMatchCase;
+		m_bWholeWord = bWholeWord;
+		m_bFindDown = TRUE;
+
+		// no selection or different than what looking for
+		if(!pT->SameAsSelected(m_sFindNext, m_bMatchCase, m_bWholeWord))
+		{
+			if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown))
+			{
+				pT->TextNotFound(m_sFindNext);
+				return;
+			}
+		}
+
+		pT->OnReplaceAllCoreBegin();
+
+		int replaceCount=0;
+		do
+		{
+			++replaceCount;
+			pT->ReplaceSel(m_sReplaceWith);
+		} while(pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown));
+
+		pT->OnReplaceAllCoreEnd(replaceCount);
+	}
+
+	void OnReplaceAllCoreBegin()
+	{
+		T* pT = static_cast<T*>(this);
+
+		m_hOldCursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
+
+		pT->HideSelection(TRUE, FALSE);
+
+	}
+
+	void OnReplaceAllCoreEnd(int replaceCount)
+	{
+		T* pT = static_cast<T*>(this);
+		pT->HideSelection(FALSE, FALSE);
+
+		::SetCursor(m_hOldCursor);
+
+		ATL::CString message = pT->GetTranslationText(eText_OnReplaceAllMessage);
+		if(message.GetLength() > 0)
+		{
+			ATL::CString formattedMessage;
+			formattedMessage.Format(message, replaceCount, (LPCTSTR)m_sFindNext, (LPCTSTR)m_sReplaceWith);
+			if(m_pFindReplaceDialog != NULL)
+			{
+				m_pFindReplaceDialog->MessageBox(formattedMessage,
+					pT->GetTranslationText(eText_OnReplaceAllTitle),
+					MB_OK | MB_ICONINFORMATION | MB_APPLMODAL);
+			}
+			else
+			{
+				pT->MessageBox(formattedMessage,
+					pT->GetTranslationText(eText_OnReplaceAllTitle),
+					MB_OK | MB_ICONINFORMATION | MB_APPLMODAL);
+			}
+		}
+	}
+
+	void OnTextNotFound(LPCTSTR lpszFind)
+	{
+		T* pT = static_cast<T*>(this);
+		ATL::CString message = pT->GetTranslationText(eText_OnTextNotFoundMessage);
+		if(message.GetLength() > 0)
+		{
+			ATL::CString formattedMessage;
+			formattedMessage.Format(message, lpszFind);
+			if(m_pFindReplaceDialog != NULL)
+			{
+				m_pFindReplaceDialog->MessageBox(formattedMessage,
+					pT->GetTranslationText(eText_OnTextNotFoundTitle),
+					MB_OK | MB_ICONINFORMATION | MB_APPLMODAL);
+			}
+			else
+			{
+				pT->MessageBox(formattedMessage,
+					pT->GetTranslationText(eText_OnTextNotFoundTitle),
+					MB_OK | MB_ICONINFORMATION | MB_APPLMODAL);
+			}
+		}
+		else
+		{
+			::MessageBeep(MB_ICONHAND);
+		}
+	}
+
+	void OnTerminatingFindReplaceDialog(TFindReplaceDialog*& /*findReplaceDialog*/)
+	{
+	}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CEditFindReplaceImpl - Mixin class for implementing Find/Replace for CEdit
+// based window classes.
+
+// Chain to CEditFindReplaceImpl message map. Your class must also derive from CEdit.
+// Example:
+// class CMyEdit : public CWindowImpl<CMyEdit, CEdit>,
+//                 public CEditFindReplaceImpl<CMyEdit>
+// {
+// public:
+//      BEGIN_MSG_MAP(CMyEdit)
+//              // your handlers...
+//              CHAIN_MSG_MAP_ALT(CEditFindReplaceImpl<CMyEdit>, 1)
+//      END_MSG_MAP()
+//      // other stuff...
+// };
+
+template <class T, class TFindReplaceDialog = CFindReplaceDialog>
+class CEditFindReplaceImpl : public CEditFindReplaceImplBase<T, TFindReplaceDialog>
+{
+protected:
+	typedef CEditFindReplaceImpl<T, TFindReplaceDialog> thisClass;
+	typedef CEditFindReplaceImplBase<T, TFindReplaceDialog> baseClass;
+
+public:
+// Message Handlers
+	BEGIN_MSG_MAP(thisClass)
+	ALT_MSG_MAP(1)
+		CHAIN_MSG_MAP_ALT(baseClass, 1)
+	END_MSG_MAP()
+
+// Operations
+	// Supported only for RichEdit, so this does nothing for Edit
+	void HideSelection(BOOL /*bHide*/ = TRUE, BOOL /*bChangeStyle*/ = FALSE)
+	{
+	}
+
+// Operations (overrideable)
+	BOOL FindTextSimple(LPCTSTR lpszFind, BOOL bMatchCase, BOOL bWholeWord, BOOL bFindDown = TRUE)
+	{
+		T* pT = static_cast<T*>(this);
+
+		ATLASSERT(lpszFind != NULL);
+		ATLASSERT(*lpszFind != _T('\0'));
+
+		UINT nLen = pT->GetBufferLength();
+		int nStartChar = 0, nEndChar = 0;
+		pT->GetSel(nStartChar, nEndChar);
+		UINT nStart = nStartChar;
+		int iDir = bFindDown ? +1 : -1;
+
+		// can't find a match before the first character
+		if((nStart == 0) && (iDir < 0))
+			return FALSE;
+
+		LPCTSTR lpszText = pT->LockBuffer();
+
+		bool isDBCS = false;
+#ifdef _MBCS
+		CPINFO info = {};
+		::GetCPInfo(::GetOEMCP(), &info);
+		isDBCS = (info.MaxCharSize > 1);
+#endif
+
+		if(iDir < 0)
+		{
+			// always go back one for search backwards
+			nStart -= int((lpszText + nStart) - ::CharPrev(lpszText, lpszText + nStart));
+		}
+		else if((nStartChar != nEndChar) && (pT->SameAsSelected(lpszFind, bMatchCase, bWholeWord)))
+		{
+			// easy to go backward/forward with SBCS
+#ifndef _UNICODE
+			if(::IsDBCSLeadByte(lpszText[nStart]))
+				nStart++;
+#endif
+			nStart += iDir;
+		}
+
+		// handle search with nStart past end of buffer
+		UINT nLenFind = ::lstrlen(lpszFind);
+		if((nStart + nLenFind - 1) >= nLen)
+		{
+			if((iDir < 0) && (nLen >= nLenFind))
+			{
+				if(isDBCS)
+				{
+					// walk back to previous character n times
+					nStart = nLen;
+					int n = nLenFind;
+					while(n--)
+					{
+						nStart -= int((lpszText + nStart) - ::CharPrev(lpszText, lpszText + nStart));
+					}
+				}
+				else
+				{
+					// single-byte character set is easy and fast
+					nStart = nLen - nLenFind;
+				}
+				ATLASSERT((nStart + nLenFind - 1) <= nLen);
+			}
+			else
+			{
+				pT->UnlockBuffer();
+				return FALSE;
+			}
+		}
+
+		// start the search at nStart
+		LPCTSTR lpsz = lpszText + nStart;
+		typedef int (WINAPI* CompareProc)(LPCTSTR str1, LPCTSTR str2);
+		CompareProc pfnCompare = bMatchCase ? lstrcmp : lstrcmpi;
+
+		if(isDBCS)
+		{
+			// double-byte string search
+			LPCTSTR lpszStop = NULL;
+			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)
+			{
+#ifndef _UNICODE
+				if(!bMatchCase || ((*lpsz == *lpszFind) && (!::IsDBCSLeadByte(*lpsz) || (lpsz[1] == lpszFind[1]))))
+#else
+				if(!bMatchCase || ((*lpsz == *lpszFind) && (lpsz[1] == lpszFind[1])))
+#endif
+				{
+					LPTSTR lpch = (LPTSTR)(lpsz + nLenFind);
+					TCHAR chSave = *lpch;
+					*lpch = _T('\0');
+					int nResult = (*pfnCompare)(lpsz, lpszFind);
+					*lpch = chSave;
+					if(nResult == 0)
+					{
+						lpszFound = lpsz;
+						if(iDir > 0)
+							break;
+					}
+				}
+				lpsz = ::CharNext(lpsz);
+			}
+			pT->UnlockBuffer();
+
+			if(lpszFound != NULL)
+			{
+				int n = (int)(lpszFound - lpszText);
+				pT->SetSel(n, n + nLenFind);
+				return TRUE;
+			}
+		}
+		else
+		{
+			// single-byte string search
+			UINT nCompare = 0;
+			if(iDir < 0)
+				nCompare = (UINT)(lpsz - lpszText) + 1;
+			else
+				nCompare = nLen - (UINT)(lpsz - lpszText) - nLenFind + 1;
+
+			while(nCompare > 0)
+			{
+				ATLASSERT(lpsz >= lpszText);
+				ATLASSERT((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)
+				{
+					pT->UnlockBuffer();
+					int n = (int)(lpsz - lpszText);
+					pT->SetSel(n, n + nLenFind);
+					return TRUE;
+				}
+
+				// restore character at end of search
+				*lpch = chSave;
+
+				// move on to next substring
+				nCompare--;
+				lpsz += iDir;
+			}
+			pT->UnlockBuffer();
+		}
+
+		return FALSE;
+	}
+
+	LPCTSTR LockBuffer() const
+	{
+		const T* pT = static_cast<const T*>(this);
+		ATLASSERT(pT->m_hWnd != NULL);
+
+#ifndef _UNICODE
+		// If common controls version 6 is in use (such as via theming), then EM_GETHANDLE 
+		// will always return a UNICODE string. You're really not supposed to superclass 
+		// or subclass common controls with an ANSI windows procedure.
+		DWORD dwMajor = 0, dwMinor = 0;
+		HRESULT hRet = ATL::AtlGetCommCtrlVersion(&dwMajor, &dwMinor);
+		if(SUCCEEDED(hRet) && (dwMajor >= 6))
+		{
+			ATLTRACE2(atlTraceUI, 0, _T("AtlFind Warning: You have compiled for MBCS/ANSI but are using common controls version 6 or later which are always Unicode.\r\n"));
+			ATLASSERT(FALSE);
+		}
+#endif // !_UNICODE
+
+		HLOCAL hLocal = pT->GetHandle();
+		ATLASSERT(hLocal != NULL);
+		LPCTSTR lpszText = (LPCTSTR)::LocalLock(hLocal);
+		ATLASSERT(lpszText != NULL);
+
+		return lpszText;
+	}
+
+	void UnlockBuffer() const
+	{
+		const T* pT = static_cast<const T*>(this);
+		ATLASSERT(pT->m_hWnd != NULL);
+
+		HLOCAL hLocal = pT->GetHandle();
+		ATLASSERT(hLocal != NULL);
+		::LocalUnlock(hLocal);
+	}
+
+	UINT GetBufferLength() const
+	{
+		const T* pT = static_cast<const T*>(this);
+		ATLASSERT(pT->m_hWnd != NULL);
+
+		UINT nLen = 0;
+		LPCTSTR lpszText = pT->LockBuffer();
+		if(lpszText != NULL)
+			nLen = ::lstrlen(lpszText);
+		pT->UnlockBuffer();
+
+		return nLen;
+	}
+
+	LONG EndOfLine(LPCTSTR lpszText, UINT nLen, UINT nIndex) const
+	{
+		LPCTSTR lpsz = lpszText + nIndex;
+		LPCTSTR lpszStop = lpszText + nLen;
+		while((lpsz < lpszStop) && (*lpsz != _T('\r')))
+			++lpsz;
+		return LONG(lpsz - lpszText);
+	}
+
+	LONG GetSelText(ATL::CString& strText) const
+	{
+		const T* pT = static_cast<const T*>(this);
+
+		int nStartChar = 0, nEndChar = 0;
+		pT->GetSel(nStartChar, nEndChar);
+		ATLASSERT((UINT)nEndChar <= pT->GetBufferLength());
+		LPCTSTR lpszText = pT->LockBuffer();
+		LONG nLen = pT->EndOfLine(lpszText, nEndChar, nStartChar) - nStartChar;
+		ATL::Checked::memcpy_s(strText.GetBuffer(nLen), nLen * sizeof(TCHAR), lpszText + nStartChar, nLen * sizeof(TCHAR));
+		strText.ReleaseBuffer(nLen);
+		pT->UnlockBuffer();
+
+		return nLen;
+	}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CRichEditFindReplaceImpl - Mixin class for implementing Find/Replace for CRichEditCtrl
+// based window classes.
+
+// Chain to CRichEditFindReplaceImpl message map. Your class must also derive from CRichEditCtrl.
+// Example:
+// class CMyRichEdit : public CWindowImpl<CMyRichEdit, CRichEditCtrl>,
+//                     public CRichEditFindReplaceImpl<CMyRichEdit>
+// {
+// public:
+//      BEGIN_MSG_MAP(CMyRichEdit)
+//              // your handlers...
+//              CHAIN_MSG_MAP_ALT(CRichEditFindReplaceImpl<CMyRichEdit>, 1)
+//      END_MSG_MAP()
+//      // other stuff...
+// };
+
+template <class T, class TFindReplaceDialog = CFindReplaceDialog>
+class CRichEditFindReplaceImpl : public CEditFindReplaceImplBase<T, TFindReplaceDialog>
+{
+protected:
+	typedef CRichEditFindReplaceImpl<T, TFindReplaceDialog> thisClass;
+	typedef CEditFindReplaceImplBase<T, TFindReplaceDialog> baseClass;
+
+public:
+	BEGIN_MSG_MAP(thisClass)
+	ALT_MSG_MAP(1)
+		CHAIN_MSG_MAP_ALT(baseClass, 1)
+	END_MSG_MAP()
+
+// Operations (overrideable)
+	BOOL FindTextSimple(LPCTSTR lpszFind, BOOL bMatchCase, BOOL bWholeWord, BOOL bFindDown = TRUE)
+	{
+		T* pT = static_cast<T*>(this);
+
+		ATLASSERT(lpszFind != NULL);
+		FINDTEXTEX ft = {};
+
+		pT->GetSel(ft.chrg);
+		if(this->m_bFirstSearch)
+		{
+			if(bFindDown)
+				this->m_nInitialSearchPos = ft.chrg.cpMin;
+			else
+				this->m_nInitialSearchPos = ft.chrg.cpMax;
+			this->m_bFirstSearch = FALSE;
+		}
+
+		ft.lpstrText = (LPTSTR)lpszFind;
+
+		if(ft.chrg.cpMin != ft.chrg.cpMax) // i.e. there is a selection
+		{
+			if(bFindDown)
+			{
+				ft.chrg.cpMin++;
+			}
+			else
+			{
+				// won't wraparound backwards
+				ft.chrg.cpMin = __max(ft.chrg.cpMin, 0);
+			}
+		}
+
+		DWORD dwFlags = bMatchCase ? FR_MATCHCASE : 0;
+		dwFlags |= bWholeWord ? FR_WHOLEWORD : 0;
+
+		ft.chrg.cpMax = pT->GetTextLength() + this->m_nInitialSearchPos;
+
+		if(bFindDown)
+		{
+			if(this->m_nInitialSearchPos >= 0)
+				ft.chrg.cpMax = pT->GetTextLength();
+
+			dwFlags |= FR_DOWN;
+			ATLASSERT(ft.chrg.cpMax >= ft.chrg.cpMin);
+		}
+		else
+		{
+			if(this->m_nInitialSearchPos >= 0)
+				ft.chrg.cpMax = 0;
+
+			dwFlags &= ~FR_DOWN;
+			ATLASSERT(ft.chrg.cpMax <= ft.chrg.cpMin);
+		}
+
+		BOOL bRet = FALSE;
+		if(pT->FindAndSelect(dwFlags, ft) != -1)
+		{
+			bRet = TRUE;   // we found the text
+		}
+		else if(this->m_nInitialSearchPos > 0)
+		{
+			// if the original starting point was not the beginning
+			// of the buffer and we haven't already been here
+			if(bFindDown)
+			{
+				ft.chrg.cpMin = 0;
+				ft.chrg.cpMax = this->m_nInitialSearchPos;
+			}
+			else
+			{
+				ft.chrg.cpMin = pT->GetTextLength();
+				ft.chrg.cpMax = this->m_nInitialSearchPos;
+			}
+			this->m_nInitialSearchPos = this->m_nInitialSearchPos - pT->GetTextLength();
+
+			bRet = (pT->FindAndSelect(dwFlags, ft) != -1) ? TRUE : FALSE;
+		}
+
+		return bRet;
+	}
+
+	long FindAndSelect(DWORD dwFlags, FINDTEXTEX& ft)
+	{
+		T* pT = static_cast<T*>(this);
+		LONG index = pT->FindText(dwFlags, ft);
+		if(index != -1) // i.e. we found something
+			pT->SetSel(ft.chrgText);
+
+		return index;
+	}
+};
+
+} // namespace WTL
+
+#endif // __ATLFIND_H__