1 // Windows Template Library - WTL version 9.10
2 // Copyright (C) Microsoft Corporation, WTL Team. All rights reserved.
3 //
4 // This file is a part of the Windows Template Library.
5 // The use and distribution terms for this software are covered by the
6 // Microsoft Public License (http://opensource.org/licenses/MS-PL)
7 // which can be found in the file MS-PL.txt at the root folder.
8 
9 #ifndef __ATLFIND_H__
10 #define __ATLFIND_H__
11 
12 #pragma once
13 
14 #ifdef _WIN32_WCE
15 	#error atlfind.h is not supported on Windows CE
16 #endif
17 
18 #ifndef __ATLCTRLS_H__
19 	#error atlfind.h requires atlctrls.h to be included first
20 #endif
21 
22 #ifndef __ATLDLGS_H__
23 	#error atlfind.h requires atldlgs.h to be included first
24 #endif
25 
26 #if !((defined(__ATLMISC_H__) && defined(_WTL_USE_CSTRING)) || defined(__ATLSTR_H__))
27 	#error atlfind.h requires CString (either from ATL's atlstr.h or WTL's atlmisc.h with _WTL_USE_CSTRING)
28 #endif
29 
30 
31 ///////////////////////////////////////////////////////////////////////////////
32 // Classes in this file:
33 //
34 // CEditFindReplaceImplBase<T, TFindReplaceDialog>
35 // CEditFindReplaceImpl<T, TFindReplaceDialog>
36 // CRichEditFindReplaceImpl<T, TFindReplaceDialog>
37 
38 
39 namespace WTL
40 {
41 
42 ///////////////////////////////////////////////////////////////////////////////
43 // CEditFindReplaceImplBase - Base class for mixin classes that
44 // help implement Find/Replace for CEdit or CRichEditCtrl based window classes.
45 
46 template <class T, class TFindReplaceDialog = CFindReplaceDialog>
47 class CEditFindReplaceImplBase
48 {
49 protected:
50 // Typedefs
51 	typedef CEditFindReplaceImplBase<T, TFindReplaceDialog> thisClass;
52 
53 // Data members
54 	TFindReplaceDialog* m_pFindReplaceDialog;
55 	_CSTRING_NS::CString m_sFindNext, m_sReplaceWith;
56 	BOOL m_bFindOnly, m_bFirstSearch, m_bMatchCase, m_bWholeWord, m_bFindDown;
57 	LONG m_nInitialSearchPos;
58 	HCURSOR m_hOldCursor;
59 
60 // Enumerations
61 	enum TranslationTextItem
62 	{
63 		eText_OnReplaceAllMessage   = 0,
64 		eText_OnReplaceAllTitle     = 1,
65 		eText_OnTextNotFoundMessage = 2,
66 		eText_OnTextNotFoundTitle   = 3
67 	};
68 
69 public:
70 // Constructors
CEditFindReplaceImplBase()71 	CEditFindReplaceImplBase() :
72 		m_pFindReplaceDialog(NULL),
73 		m_bFindOnly(TRUE),
74 		m_bFirstSearch(TRUE),
75 		m_bMatchCase(FALSE),
76 		m_bWholeWord(FALSE),
77 		m_bFindDown(TRUE),
78 		m_nInitialSearchPos(0),
79 		m_hOldCursor(NULL)
80 	{
81 	}
82 
83 // Message Handlers
84 	BEGIN_MSG_MAP(thisClass)
85 	ALT_MSG_MAP(1)
MESSAGE_HANDLER(WM_DESTROY,OnDestroy)86 		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
87 		MESSAGE_HANDLER(TFindReplaceDialog::GetFindReplaceMsg(), OnFindReplaceCmd)
88 		COMMAND_ID_HANDLER(ID_EDIT_FIND, OnEditFind)
89 		COMMAND_ID_HANDLER(ID_EDIT_REPEAT, OnEditRepeat)
90 		COMMAND_ID_HANDLER(ID_EDIT_REPLACE, OnEditReplace)
91 	END_MSG_MAP()
92 
93 	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
94 	{
95 		if(m_pFindReplaceDialog != NULL)
96 		{
97 			m_pFindReplaceDialog->SendMessage(WM_CLOSE);
98 			ATLASSERT(m_pFindReplaceDialog == NULL);
99 		}
100 
101 		bHandled = FALSE;
102 		return 0;
103 	}
104 
OnFindReplaceCmd(UINT,WPARAM,LPARAM lParam,BOOL &)105 	LRESULT OnFindReplaceCmd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
106 	{
107 		T* pT = static_cast<T*>(this);
108 
109 		TFindReplaceDialog* pDialog = TFindReplaceDialog::GetNotifier(lParam);
110 		if(pDialog == NULL)
111 		{
112 			ATLASSERT(FALSE);
113 			::MessageBeep(MB_ICONERROR);
114 			return 1;
115 		}
116 		ATLASSERT(pDialog == m_pFindReplaceDialog);
117 
118 		LPFINDREPLACE findReplace = (LPFINDREPLACE)lParam;
119 		if((m_pFindReplaceDialog != NULL) && (findReplace != NULL))
120 		{
121 			if(pDialog->FindNext())
122 			{
123 				pT->OnFindNext(pDialog->GetFindString(), pDialog->SearchDown(),
124 					pDialog->MatchCase(), pDialog->MatchWholeWord());
125 			}
126 			else if(pDialog->ReplaceCurrent())
127 			{
128 				pT->OnReplaceSel(pDialog->GetFindString(),
129 					pDialog->SearchDown(), pDialog->MatchCase(), pDialog->MatchWholeWord(),
130 					pDialog->GetReplaceString());
131 			}
132 			else if(pDialog->ReplaceAll())
133 			{
134 				pT->OnReplaceAll(pDialog->GetFindString(), pDialog->GetReplaceString(),
135 					pDialog->MatchCase(), pDialog->MatchWholeWord());
136 			}
137 			else if(pDialog->IsTerminating())
138 			{
139 				// Dialog is going away (but hasn't gone away yet)
140 				// OnFinalMessage will "delete this"
141 				pT->OnTerminatingFindReplaceDialog(m_pFindReplaceDialog);
142 				m_pFindReplaceDialog = NULL;
143 			}
144 		}
145 
146 		return 0;
147 	}
148 
OnEditFind(WORD,WORD,HWND,BOOL &)149 	LRESULT OnEditFind(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
150 	{
151 		T* pT = static_cast<T*>(this);
152 		pT->FindReplace(TRUE);
153 
154 		return 0;
155 	}
156 
OnEditRepeat(WORD,WORD,HWND,BOOL &)157 	LRESULT OnEditRepeat(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
158 	{
159 		T* pT = static_cast<T*>(this);
160 
161 		// If the user is holding down SHIFT when hitting F3, we'll
162 		// search in reverse. Otherwise, we'll search forward.
163 		// (be sure to have an accelerator mapped to ID_EDIT_REPEAT
164 		// for both F3 and Shift+F3)
165 		m_bFindDown = !((::GetKeyState(VK_SHIFT) & 0x8000) == 0x8000);
166 
167 		if(m_sFindNext.IsEmpty())
168 		{
169 			pT->FindReplace(TRUE);
170 		}
171 		else
172 		{
173 			if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown))
174 				pT->TextNotFound(m_sFindNext);
175 		}
176 
177 		return 0;
178 	}
179 
OnEditReplace(WORD,WORD,HWND,BOOL & bHandled)180 	LRESULT OnEditReplace(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& bHandled)
181 	{
182 		T* pT = static_cast<T*>(this);
183 
184 		DWORD style = pT->GetStyle();
185 		if((style & ES_READONLY) != ES_READONLY)
186 		{
187 			pT->FindReplace(FALSE);
188 		}
189 		else
190 		{
191 			// Don't allow replace when the edit control is read only
192 			bHandled = FALSE;
193 		}
194 
195 		return 0;
196 	}
197 
198 // Operations (overrideable)
199 	TFindReplaceDialog* CreateFindReplaceDialog(BOOL bFindOnly, // TRUE for Find, FALSE for FindReplace
200 			LPCTSTR lpszFindWhat,
201 			LPCTSTR lpszReplaceWith = NULL,
202 			DWORD dwFlags = FR_DOWN,
203 			HWND hWndParent = NULL)
204 	{
205 		// You can override all of this in a derived class
206 
207 		TFindReplaceDialog* findReplaceDialog = new TFindReplaceDialog();
208 		if(findReplaceDialog == NULL)
209 		{
210 			::MessageBeep(MB_ICONHAND);
211 		}
212 		else
213 		{
214 			HWND hWndFindReplace = findReplaceDialog->Create(bFindOnly,
215 				lpszFindWhat, lpszReplaceWith, dwFlags, hWndParent);
216 			if(hWndFindReplace == NULL)
217 			{
218 				delete findReplaceDialog;
219 				findReplaceDialog = NULL;
220 			}
221 			else
222 			{
223 				findReplaceDialog->SetActiveWindow();
224 				findReplaceDialog->ShowWindow(SW_SHOW);
225 			}
226 		}
227 
228 		return findReplaceDialog;
229 	}
230 
AdjustDialogPosition(HWND hWndDialog)231 	void AdjustDialogPosition(HWND hWndDialog)
232 	{
233 		ATLASSERT((hWndDialog != NULL) && ::IsWindow(hWndDialog));
234 
235 		T* pT = static_cast<T*>(this);
236 		LONG nStartChar = 0, nEndChar = 0;
237 		// Send EM_GETSEL so we can use both Edit and RichEdit
238 		// (CEdit::GetSel uses int&, and CRichEditCtrlT::GetSel uses LONG&)
239 		::SendMessage(pT->m_hWnd, EM_GETSEL, (WPARAM)&nStartChar, (LPARAM)&nEndChar);
240 		POINT point = pT->PosFromChar(nStartChar);
241 		::ClientToScreen(pT->GetParent(), &point);
242 		CRect rect;
243 		::GetWindowRect(hWndDialog, &rect);
244 		if(rect.PtInRect(point))
245 		{
246 			if(point.y > rect.Height())
247 			{
248 				rect.OffsetRect(0, point.y - rect.bottom - 20);
249 			}
250 			else
251 			{
252 				int nVertExt = GetSystemMetrics(SM_CYSCREEN);
253 				if(point.y + rect.Height() < nVertExt)
254 					rect.OffsetRect(0, 40 + point.y - rect.top);
255 			}
256 
257 			::MoveWindow(hWndDialog, rect.left, rect.top, rect.Width(), rect.Height(), TRUE);
258 		}
259 	}
260 
GetFindReplaceDialogFlags(void)261 	DWORD GetFindReplaceDialogFlags(void) const
262 	{
263 		DWORD dwFlags = 0;
264 		if(m_bFindDown)
265 			dwFlags |= FR_DOWN;
266 		if(m_bMatchCase)
267 			dwFlags |= FR_MATCHCASE;
268 		if(m_bWholeWord)
269 			dwFlags |= FR_WHOLEWORD;
270 
271 		return dwFlags;
272 	}
273 
FindReplace(BOOL bFindOnly)274 	void FindReplace(BOOL bFindOnly)
275 	{
276 		T* pT = static_cast<T*>(this);
277 		m_bFirstSearch = TRUE;
278 		if(m_pFindReplaceDialog != NULL)
279 		{
280 			if(m_bFindOnly == bFindOnly)
281 			{
282 				m_pFindReplaceDialog->SetActiveWindow();
283 				m_pFindReplaceDialog->ShowWindow(SW_SHOW);
284 				return;
285 			}
286 			else
287 			{
288 				m_pFindReplaceDialog->SendMessage(WM_CLOSE);
289 				ATLASSERT(m_pFindReplaceDialog == NULL);
290 			}
291 		}
292 
293 		ATLASSERT(m_pFindReplaceDialog == NULL);
294 
295 		_CSTRING_NS::CString findNext;
296 		pT->GetSelText(findNext);
297 		// if selection is empty or spans multiple lines use old find text
298 		if(findNext.IsEmpty() || (findNext.FindOneOf(_T("\n\r")) != -1))
299 			findNext = m_sFindNext;
300 		_CSTRING_NS::CString replaceWith = m_sReplaceWith;
301 		DWORD dwFlags = pT->GetFindReplaceDialogFlags();
302 
303 		m_pFindReplaceDialog = pT->CreateFindReplaceDialog(bFindOnly,
304 			findNext, replaceWith, dwFlags, pT->operator HWND());
305 		ATLASSERT(m_pFindReplaceDialog != NULL);
306 		if(m_pFindReplaceDialog != NULL)
307 			m_bFindOnly = bFindOnly;
308 	}
309 
SameAsSelected(LPCTSTR lpszCompare,BOOL bMatchCase,BOOL)310 	BOOL SameAsSelected(LPCTSTR lpszCompare, BOOL bMatchCase, BOOL /*bWholeWord*/)
311 	{
312 		T* pT = static_cast<T*>(this);
313 
314 		// check length first
315 		size_t nLen = lstrlen(lpszCompare);
316 		LONG nStartChar = 0, nEndChar = 0;
317 		// Send EM_GETSEL so we can use both Edit and RichEdit
318 		// (CEdit::GetSel uses int&, and CRichEditCtrlT::GetSel uses LONG&)
319 		::SendMessage(pT->m_hWnd, EM_GETSEL, (WPARAM)&nStartChar, (LPARAM)&nEndChar);
320 		if(nLen != (size_t)(nEndChar - nStartChar))
321 			return FALSE;
322 
323 		// length is the same, check contents
324 		_CSTRING_NS::CString selectedText;
325 		pT->GetSelText(selectedText);
326 
327 		return (bMatchCase && selectedText.Compare(lpszCompare) == 0) ||
328 			(!bMatchCase && selectedText.CompareNoCase(lpszCompare) == 0);
329 	}
330 
TextNotFound(LPCTSTR lpszFind)331 	void TextNotFound(LPCTSTR lpszFind)
332 	{
333 		T* pT = static_cast<T*>(this);
334 		m_bFirstSearch = TRUE;
335 		pT->OnTextNotFound(lpszFind);
336 	}
337 
GetTranslationText(enum TranslationTextItem eItem)338 	_CSTRING_NS::CString GetTranslationText(enum TranslationTextItem eItem) const
339 	{
340 		_CSTRING_NS::CString text;
341 		switch(eItem)
342 		{
343 		case eText_OnReplaceAllMessage:
344 			text = _T("Replaced %d occurances of \"%s\" with \"%s\"");
345 			break;
346 		case eText_OnReplaceAllTitle:
347 			text = _T("Replace All");
348 			break;
349 		case eText_OnTextNotFoundMessage:
350 			text = _T("Unable to find the text \"%s\"");
351 			break;
352 		case eText_OnTextNotFoundTitle:
353 			text = _T("Text not found");
354 			break;
355 		}
356 
357 		return text;
358 	}
359 
360 // Overrideable Handlers
OnFindNext(LPCTSTR lpszFind,BOOL bFindDown,BOOL bMatchCase,BOOL bWholeWord)361 	void OnFindNext(LPCTSTR lpszFind, BOOL bFindDown, BOOL bMatchCase, BOOL bWholeWord)
362 	{
363 		T* pT = static_cast<T*>(this);
364 
365 		m_sFindNext = lpszFind;
366 		m_bMatchCase = bMatchCase;
367 		m_bWholeWord = bWholeWord;
368 		m_bFindDown = bFindDown;
369 
370 		if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown))
371 			pT->TextNotFound(m_sFindNext);
372 		else
373 			pT->AdjustDialogPosition(m_pFindReplaceDialog->operator HWND());
374 	}
375 
OnReplaceSel(LPCTSTR lpszFind,BOOL bFindDown,BOOL bMatchCase,BOOL bWholeWord,LPCTSTR lpszReplace)376 	void OnReplaceSel(LPCTSTR lpszFind, BOOL bFindDown, BOOL bMatchCase, BOOL bWholeWord, LPCTSTR lpszReplace)
377 	{
378 		T* pT = static_cast<T*>(this);
379 
380 		m_sFindNext = lpszFind;
381 		m_sReplaceWith = lpszReplace;
382 		m_bMatchCase = bMatchCase;
383 		m_bWholeWord = bWholeWord;
384 		m_bFindDown = bFindDown;
385 
386 		if(pT->SameAsSelected(m_sFindNext, m_bMatchCase, m_bWholeWord))
387 			pT->ReplaceSel(m_sReplaceWith);
388 
389 		if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown))
390 			pT->TextNotFound(m_sFindNext);
391 		else
392 			pT->AdjustDialogPosition(m_pFindReplaceDialog->operator HWND());
393 	}
394 
OnReplaceAll(LPCTSTR lpszFind,LPCTSTR lpszReplace,BOOL bMatchCase,BOOL bWholeWord)395 	void OnReplaceAll(LPCTSTR lpszFind, LPCTSTR lpszReplace, BOOL bMatchCase, BOOL bWholeWord)
396 	{
397 		T* pT = static_cast<T*>(this);
398 
399 		m_sFindNext = lpszFind;
400 		m_sReplaceWith = lpszReplace;
401 		m_bMatchCase = bMatchCase;
402 		m_bWholeWord = bWholeWord;
403 		m_bFindDown = TRUE;
404 
405 		// no selection or different than what looking for
406 		if(!pT->SameAsSelected(m_sFindNext, m_bMatchCase, m_bWholeWord))
407 		{
408 			if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown))
409 			{
410 				pT->TextNotFound(m_sFindNext);
411 				return;
412 			}
413 		}
414 
415 		pT->OnReplaceAllCoreBegin();
416 
417 		int replaceCount=0;
418 		do
419 		{
420 			++replaceCount;
421 			pT->ReplaceSel(m_sReplaceWith);
422 		} while(pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown));
423 
424 		pT->OnReplaceAllCoreEnd(replaceCount);
425 	}
426 
OnReplaceAllCoreBegin()427 	void OnReplaceAllCoreBegin()
428 	{
429 		T* pT = static_cast<T*>(this);
430 
431 		m_hOldCursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
432 
433 		pT->HideSelection(TRUE, FALSE);
434 
435 	}
436 
OnReplaceAllCoreEnd(int replaceCount)437 	void OnReplaceAllCoreEnd(int replaceCount)
438 	{
439 		T* pT = static_cast<T*>(this);
440 		pT->HideSelection(FALSE, FALSE);
441 
442 		::SetCursor(m_hOldCursor);
443 
444 		_CSTRING_NS::CString message = pT->GetTranslationText(eText_OnReplaceAllMessage);
445 		if(message.GetLength() > 0)
446 		{
447 			_CSTRING_NS::CString formattedMessage;
448 			formattedMessage.Format(message, replaceCount, m_sFindNext, m_sReplaceWith);
449 			if(m_pFindReplaceDialog != NULL)
450 			{
451 				m_pFindReplaceDialog->MessageBox(formattedMessage,
452 					pT->GetTranslationText(eText_OnReplaceAllTitle),
453 					MB_OK | MB_ICONINFORMATION | MB_APPLMODAL);
454 			}
455 			else
456 			{
457 				pT->MessageBox(formattedMessage,
458 					pT->GetTranslationText(eText_OnReplaceAllTitle),
459 					MB_OK | MB_ICONINFORMATION | MB_APPLMODAL);
460 			}
461 		}
462 	}
463 
OnTextNotFound(LPCTSTR lpszFind)464 	void OnTextNotFound(LPCTSTR lpszFind)
465 	{
466 		T* pT = static_cast<T*>(this);
467 		_CSTRING_NS::CString message = pT->GetTranslationText(eText_OnTextNotFoundMessage);
468 		if(message.GetLength() > 0)
469 		{
470 			_CSTRING_NS::CString formattedMessage;
471 			formattedMessage.Format(message, lpszFind);
472 			if(m_pFindReplaceDialog != NULL)
473 			{
474 				m_pFindReplaceDialog->MessageBox(formattedMessage,
475 					pT->GetTranslationText(eText_OnTextNotFoundTitle),
476 					MB_OK | MB_ICONINFORMATION | MB_APPLMODAL);
477 			}
478 			else
479 			{
480 				pT->MessageBox(formattedMessage,
481 					pT->GetTranslationText(eText_OnTextNotFoundTitle),
482 					MB_OK | MB_ICONINFORMATION | MB_APPLMODAL);
483 			}
484 		}
485 		else
486 		{
487 			::MessageBeep(MB_ICONHAND);
488 		}
489 	}
490 
OnTerminatingFindReplaceDialog(TFindReplaceDialog * &)491 	void OnTerminatingFindReplaceDialog(TFindReplaceDialog*& /*findReplaceDialog*/)
492 	{
493 	}
494 };
495 
496 
497 ///////////////////////////////////////////////////////////////////////////////
498 // CEditFindReplaceImpl - Mixin class for implementing Find/Replace for CEdit
499 // based window classes.
500 
501 // Chain to CEditFindReplaceImpl message map. Your class must also derive from CEdit.
502 // Example:
503 // class CMyEdit : public CWindowImpl<CMyEdit, CEdit>,
504 //                 public CEditFindReplaceImpl<CMyEdit>
505 // {
506 // public:
507 //      BEGIN_MSG_MAP(CMyEdit)
508 //              // your handlers...
509 //              CHAIN_MSG_MAP_ALT(CEditFindReplaceImpl<CMyEdit>, 1)
510 //      END_MSG_MAP()
511 //      // other stuff...
512 // };
513 
514 template <class T, class TFindReplaceDialog = CFindReplaceDialog>
515 class CEditFindReplaceImpl : public CEditFindReplaceImplBase<T, TFindReplaceDialog>
516 {
517 protected:
518 	typedef CEditFindReplaceImpl<T, TFindReplaceDialog> thisClass;
519 	typedef CEditFindReplaceImplBase<T, TFindReplaceDialog> baseClass;
520 
521 // Data members
522 	LPTSTR m_pShadowBuffer;     // Special shadow buffer only used in some cases.
523 	UINT m_nShadowSize;
524 	int m_bShadowBufferNeeded;  // TRUE, FALSE, < 0 => Need to check
525 
526 public:
527 // Constructors
CEditFindReplaceImpl()528 	CEditFindReplaceImpl() :
529 		m_pShadowBuffer(NULL),
530 		m_nShadowSize(0),
531 		m_bShadowBufferNeeded(-1)
532 	{
533 	}
534 
~CEditFindReplaceImpl()535 	virtual ~CEditFindReplaceImpl()
536 	{
537 		if(m_pShadowBuffer != NULL)
538 		{
539 			delete [] m_pShadowBuffer;
540 			m_pShadowBuffer = NULL;
541 		}
542 	}
543 
544 // Message Handlers
545 	BEGIN_MSG_MAP(thisClass)
546 	ALT_MSG_MAP(1)
547 		CHAIN_MSG_MAP_ALT(baseClass, 1)
END_MSG_MAP()548 	END_MSG_MAP()
549 
550 // Operations
551 	// Supported only for RichEdit, so this does nothing for Edit
552 	void HideSelection(BOOL /*bHide*/ = TRUE, BOOL /*bChangeStyle*/ = FALSE)
553 	{
554 	}
555 
556 // Operations (overrideable)
557 	BOOL FindTextSimple(LPCTSTR lpszFind, BOOL bMatchCase, BOOL bWholeWord, BOOL bFindDown = TRUE)
558 	{
559 		T* pT = static_cast<T*>(this);
560 
561 		ATLASSERT(lpszFind != NULL);
562 		ATLASSERT(*lpszFind != _T('\0'));
563 
564 		UINT nLen = pT->GetBufferLength();
565 		int nStartChar = 0, nEndChar = 0;
566 		pT->GetSel(nStartChar, nEndChar);
567 		UINT nStart = nStartChar;
568 		int iDir = bFindDown ? +1 : -1;
569 
570 		// can't find a match before the first character
571 		if((nStart == 0) && (iDir < 0))
572 			return FALSE;
573 
574 		LPCTSTR lpszText = pT->LockBuffer();
575 
576 		bool isDBCS = false;
577 #ifdef _MBCS
578 		CPINFO info = { 0 };
579 		::GetCPInfo(::GetOEMCP(), &info);
580 		isDBCS = (info.MaxCharSize > 1);
581 #endif
582 
583 		if(iDir < 0)
584 		{
585 			// always go back one for search backwards
586 			nStart -= int((lpszText + nStart) - ::CharPrev(lpszText, lpszText + nStart));
587 		}
588 		else if((nStartChar != nEndChar) && (pT->SameAsSelected(lpszFind, bMatchCase, bWholeWord)))
589 		{
590 			// easy to go backward/forward with SBCS
591 #ifndef _UNICODE
592 			if(::IsDBCSLeadByte(lpszText[nStart]))
593 				nStart++;
594 #endif
595 			nStart += iDir;
596 		}
597 
598 		// handle search with nStart past end of buffer
599 		UINT nLenFind = ::lstrlen(lpszFind);
600 		if((nStart + nLenFind - 1) >= nLen)
601 		{
602 			if((iDir < 0) && (nLen >= nLenFind))
603 			{
604 				if(isDBCS)
605 				{
606 					// walk back to previous character n times
607 					nStart = nLen;
608 					int n = nLenFind;
609 					while(n--)
610 					{
611 						nStart -= int((lpszText + nStart) - ::CharPrev(lpszText, lpszText + nStart));
612 					}
613 				}
614 				else
615 				{
616 					// single-byte character set is easy and fast
617 					nStart = nLen - nLenFind;
618 				}
619 				ATLASSERT((nStart + nLenFind - 1) <= nLen);
620 			}
621 			else
622 			{
623 				pT->UnlockBuffer();
624 				return FALSE;
625 			}
626 		}
627 
628 		// start the search at nStart
629 		LPCTSTR lpsz = lpszText + nStart;
630 		typedef int (WINAPI* CompareProc)(LPCTSTR str1, LPCTSTR str2);
631 		CompareProc pfnCompare = bMatchCase ? lstrcmp : lstrcmpi;
632 
633 		if(isDBCS)
634 		{
635 			// double-byte string search
636 			LPCTSTR lpszStop = NULL;
637 			if(iDir > 0)
638 			{
639 				// start at current and find _first_ occurrance
640 				lpszStop = lpszText + nLen - nLenFind + 1;
641 			}
642 			else
643 			{
644 				// start at top and find _last_ occurrance
645 				lpszStop = lpsz;
646 				lpsz = lpszText;
647 			}
648 
649 			LPCTSTR lpszFound = NULL;
650 			while(lpsz <= lpszStop)
651 			{
652 #ifndef _UNICODE
653 				if(!bMatchCase || (*lpsz == *lpszFind && (!::IsDBCSLeadByte(*lpsz) || lpsz[1] == lpszFind[1])))
654 #else
655 				if(!bMatchCase || (*lpsz == *lpszFind && lpsz[1] == lpszFind[1]))
656 #endif
657 				{
658 					LPTSTR lpch = (LPTSTR)(lpsz + nLenFind);
659 					TCHAR chSave = *lpch;
660 					*lpch = _T('\0');
661 					int nResult = (*pfnCompare)(lpsz, lpszFind);
662 					*lpch = chSave;
663 					if(nResult == 0)
664 					{
665 						lpszFound = lpsz;
666 						if(iDir > 0)
667 							break;
668 					}
669 				}
670 				lpsz = ::CharNext(lpsz);
671 			}
672 			pT->UnlockBuffer();
673 
674 			if(lpszFound != NULL)
675 			{
676 				int n = (int)(lpszFound - lpszText);
677 				pT->SetSel(n, n + nLenFind);
678 				return TRUE;
679 			}
680 		}
681 		else
682 		{
683 			// single-byte string search
684 			UINT nCompare = 0;
685 			if(iDir < 0)
686 				nCompare = (UINT)(lpsz - lpszText) + 1;
687 			else
688 				nCompare = nLen - (UINT)(lpsz - lpszText) - nLenFind + 1;
689 
690 			while(nCompare > 0)
691 			{
692 				ATLASSERT(lpsz >= lpszText);
693 				ATLASSERT((lpsz + nLenFind - 1) <= (lpszText + nLen - 1));
694 
695 				LPSTR lpch = (LPSTR)(lpsz + nLenFind);
696 				char chSave = *lpch;
697 				*lpch = '\0';
698 				int nResult = (*pfnCompare)(lpsz, lpszFind);
699 				*lpch = chSave;
700 				if(nResult == 0)
701 				{
702 					pT->UnlockBuffer();
703 					int n = (int)(lpsz - lpszText);
704 					pT->SetSel(n, n + nLenFind);
705 					return TRUE;
706 				}
707 
708 				// restore character at end of search
709 				*lpch = chSave;
710 
711 				// move on to next substring
712 				nCompare--;
713 				lpsz += iDir;
714 			}
715 			pT->UnlockBuffer();
716 		}
717 
718 		return FALSE;
719 	}
720 
LockBuffer()721 	LPCTSTR LockBuffer() const
722 	{
723 		const T* pT = static_cast<const T*>(this);
724 		ATLASSERT(pT->m_hWnd != NULL);
725 
726 		BOOL useShadowBuffer = pT->UseShadowBuffer();
727 		if(useShadowBuffer)
728 		{
729 			if((m_pShadowBuffer == NULL) || pT->GetModify())
730 			{
731 				ATLASSERT((m_pShadowBuffer != NULL) || (m_nShadowSize == 0));
732 				UINT nSize = pT->GetWindowTextLength() + 1;
733 				if(nSize > m_nShadowSize)
734 				{
735 					// need more room for shadow buffer
736 					T* pThisNoConst = const_cast<T*>(pT);
737 					delete[] m_pShadowBuffer;
738 					pThisNoConst->m_pShadowBuffer = NULL;
739 					pThisNoConst->m_nShadowSize = 0;
740 					pThisNoConst->m_pShadowBuffer = new TCHAR[nSize];
741 					pThisNoConst->m_nShadowSize = nSize;
742 				}
743 
744 				// update the shadow buffer with GetWindowText
745 				ATLASSERT(m_nShadowSize >= nSize);
746 				ATLASSERT(m_pShadowBuffer != NULL);
747 				pT->GetWindowText(m_pShadowBuffer, nSize);
748 			}
749 
750 			return m_pShadowBuffer;
751 		}
752 
753 		HLOCAL hLocal = pT->GetHandle();
754 		ATLASSERT(hLocal != NULL);
755 		LPCTSTR lpszText = (LPCTSTR)::LocalLock(hLocal);
756 		ATLASSERT(lpszText != NULL);
757 
758 		return lpszText;
759 	}
760 
UnlockBuffer()761 	void UnlockBuffer() const
762 	{
763 		const T* pT = static_cast<const T*>(this);
764 		ATLASSERT(pT->m_hWnd != NULL);
765 
766 		BOOL useShadowBuffer = pT->UseShadowBuffer();
767 		if(!useShadowBuffer)
768 		{
769 			HLOCAL hLocal = pT->GetHandle();
770 			ATLASSERT(hLocal != NULL);
771 			::LocalUnlock(hLocal);
772 		}
773 	}
774 
GetBufferLength()775 	UINT GetBufferLength() const
776 	{
777 		const T* pT = static_cast<const T*>(this);
778 		ATLASSERT(pT->m_hWnd != NULL);
779 
780 		UINT nLen = 0;
781 		LPCTSTR lpszText = pT->LockBuffer();
782 		if(lpszText != NULL)
783 			nLen = ::lstrlen(lpszText);
784 		pT->UnlockBuffer();
785 
786 		return nLen;
787 	}
788 
EndOfLine(LPCTSTR lpszText,UINT nLen,UINT nIndex)789 	LONG EndOfLine(LPCTSTR lpszText, UINT nLen, UINT nIndex) const
790 	{
791 		LPCTSTR lpsz = lpszText + nIndex;
792 		LPCTSTR lpszStop = lpszText + nLen;
793 		while(lpsz < lpszStop && *lpsz != _T('\r'))
794 			++lpsz;
795 		return LONG(lpsz - lpszText);
796 	}
797 
GetSelText(_CSTRING_NS::CString & strText)798 	LONG GetSelText(_CSTRING_NS::CString& strText) const
799 	{
800 		const T* pT = static_cast<const T*>(this);
801 
802 		int nStartChar = 0, nEndChar = 0;
803 		pT->GetSel(nStartChar, nEndChar);
804 		ATLASSERT((UINT)nEndChar <= pT->GetBufferLength());
805 		LPCTSTR lpszText = pT->LockBuffer();
806 		LONG nLen = pT->EndOfLine(lpszText, nEndChar, nStartChar) - nStartChar;
807 		SecureHelper::memcpy_x(strText.GetBuffer(nLen), nLen * sizeof(TCHAR), lpszText + nStartChar, nLen * sizeof(TCHAR));
808 		strText.ReleaseBuffer(nLen);
809 		pT->UnlockBuffer();
810 
811 		return nLen;
812 	}
813 
UseShadowBuffer(void)814 	BOOL UseShadowBuffer(void) const
815 	{
816 		const T* pT = static_cast<const T*>(this);
817 
818 		if(pT->m_bShadowBufferNeeded < 0)
819 		{
820 			T* pThisNoConst = const_cast<T*>(pT);
821 
822 #ifdef _versionhelpers_H_INCLUDED_
823 			OSVERSIONINFOEX ovi = { sizeof(OSVERSIONINFOEX) };
824 			ovi.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS;
825 			DWORDLONG const dwlConditionMask = ::VerSetConditionMask(0, VER_PLATFORMID, VER_EQUAL);
826 			bool bWin9x = (::VerifyVersionInfo(&ovi, VER_PLATFORMID, dwlConditionMask) != FALSE);
827 #else // !_versionhelpers_H_INCLUDED_
828 			OSVERSIONINFO ovi = { sizeof(OSVERSIONINFO) };
829 			::GetVersionEx(&ovi);
830 
831 			bool bWin9x = (ovi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);
832 #endif // _versionhelpers_H_INCLUDED_
833 			if(bWin9x)
834 			{
835 				// Windows 95, 98, ME
836 				// Under Win9x, it is necessary to maintain a shadow buffer.
837 				// It is only updated when the control contents have been changed.
838 				pThisNoConst->m_bShadowBufferNeeded = TRUE;
839 			}
840 			else
841 			{
842 				// Windows NT, 2000, XP, etc.
843 				pThisNoConst->m_bShadowBufferNeeded = FALSE;
844 
845 #ifndef _UNICODE
846 				// On Windows XP (or later), if common controls version 6 is in use
847 				// (such as via theming), then EM_GETHANDLE will always return a UNICODE string.
848 				// If theming is enabled and Common Controls version 6 is in use,
849 				// you're really not suppose to superclass or subclass common controls
850 				// with an ANSI windows procedure (so its best to only theme if you use UNICODE).
851 				// Using a shadow buffer uses GetWindowText instead, so it solves
852 				// this problem for us (although it makes it a little less efficient).
853 
854 #ifdef _versionhelpers_H_INCLUDED_
855 				if(::IsWindowsXPOrGreater())
856 #else // !_versionhelpers_H_INCLUDED_
857 				if ((ovi.dwMajorVersion == 5 && ovi.dwMinorVersion >= 1) || (ovi.dwMajorVersion > 5))
858 #endif // _versionhelpers_H_INCLUDED_
859 				{
860 					DWORD dwMajor = 0, dwMinor = 0;
861 					HRESULT hRet = ATL::AtlGetCommCtrlVersion(&dwMajor, &dwMinor);
862 					if(SUCCEEDED(hRet))
863 					{
864 						if(dwMajor >= 6)
865 						{
866 							pThisNoConst->m_bShadowBufferNeeded = TRUE;
867 
868 							ATLTRACE2(atlTraceUI, 0, _T("Warning: You have compiled for MBCS/ANSI but are using common controls version 6 or later (likely through a manifest file).\r\n"));
869 							ATLTRACE2(atlTraceUI, 0, _T("If you use common controls version 6 or later, you should only do so for UNICODE builds.\r\n"));
870 						}
871 					}
872 				}
873 #endif // !_UNICODE
874 			}
875 		}
876 
877 		return (pT->m_bShadowBufferNeeded != FALSE);
878 	}
879 };
880 
881 
882 ///////////////////////////////////////////////////////////////////////////////
883 // CRichEditFindReplaceImpl - Mixin class for implementing Find/Replace for CRichEditCtrl
884 // based window classes.
885 
886 // Chain to CRichEditFindReplaceImpl message map. Your class must also derive from CRichEditCtrl.
887 // Example:
888 // class CMyRichEdit : public CWindowImpl<CMyRichEdit, CRichEditCtrl>,
889 //                     public CRichEditFindReplaceImpl<CMyRichEdit>
890 // {
891 // public:
892 //      BEGIN_MSG_MAP(CMyRichEdit)
893 //              // your handlers...
894 //              CHAIN_MSG_MAP_ALT(CRichEditFindReplaceImpl<CMyRichEdit>, 1)
895 //      END_MSG_MAP()
896 //      // other stuff...
897 // };
898 
899 template <class T, class TFindReplaceDialog = CFindReplaceDialog>
900 class CRichEditFindReplaceImpl : public CEditFindReplaceImplBase<T, TFindReplaceDialog>
901 {
902 protected:
903 	typedef CRichEditFindReplaceImpl<T, TFindReplaceDialog> thisClass;
904 	typedef CEditFindReplaceImplBase<T, TFindReplaceDialog> baseClass;
905 
906 public:
BEGIN_MSG_MAP(thisClass)907 	BEGIN_MSG_MAP(thisClass)
908 	ALT_MSG_MAP(1)
909 		CHAIN_MSG_MAP_ALT(baseClass, 1)
910 	END_MSG_MAP()
911 
912 // Operations (overrideable)
913 	BOOL FindTextSimple(LPCTSTR lpszFind, BOOL bMatchCase, BOOL bWholeWord, BOOL bFindDown = TRUE)
914 	{
915 		T* pT = static_cast<T*>(this);
916 
917 		ATLASSERT(lpszFind != NULL);
918 		FINDTEXTEX ft = { 0 };
919 
920 		pT->GetSel(ft.chrg);
921 		if(m_bFirstSearch)
922 		{
923 			if(bFindDown)
924 				m_nInitialSearchPos = ft.chrg.cpMin;
925 			else
926 				m_nInitialSearchPos = ft.chrg.cpMax;
927 			m_bFirstSearch = FALSE;
928 		}
929 
930 #if (_RICHEDIT_VER >= 0x0200)
931 		ft.lpstrText = (LPTSTR)lpszFind;
932 #else // !(_RICHEDIT_VER >= 0x0200)
933 		USES_CONVERSION;
934 		ft.lpstrText = T2A((LPTSTR)lpszFind);
935 #endif // !(_RICHEDIT_VER >= 0x0200)
936 
937 		if(ft.chrg.cpMin != ft.chrg.cpMax) // i.e. there is a selection
938 		{
939 			if(bFindDown)
940 			{
941 				ft.chrg.cpMin++;
942 			}
943 			else
944 			{
945 				// won't wraparound backwards
946 				ft.chrg.cpMin = __max(ft.chrg.cpMin, 0);
947 			}
948 		}
949 
950 		DWORD dwFlags = bMatchCase ? FR_MATCHCASE : 0;
951 		dwFlags |= bWholeWord ? FR_WHOLEWORD : 0;
952 
953 		ft.chrg.cpMax = pT->GetTextLength() + m_nInitialSearchPos;
954 
955 		if(bFindDown)
956 		{
957 			if(m_nInitialSearchPos >= 0)
958 				ft.chrg.cpMax = pT->GetTextLength();
959 
960 			dwFlags |= FR_DOWN;
961 			ATLASSERT(ft.chrg.cpMax >= ft.chrg.cpMin);
962 		}
963 		else
964 		{
965 			if(m_nInitialSearchPos >= 0)
966 				ft.chrg.cpMax = 0;
967 
968 			dwFlags &= ~FR_DOWN;
969 			ATLASSERT(ft.chrg.cpMax <= ft.chrg.cpMin);
970 		}
971 
972 		BOOL bRet = FALSE;
973 		if(pT->FindAndSelect(dwFlags, ft) != -1)
974 		{
975 			bRet = TRUE;   // we found the text
976 		}
977 		else if(m_nInitialSearchPos > 0)
978 		{
979 			// if the original starting point was not the beginning
980 			// of the buffer and we haven't already been here
981 			if(bFindDown)
982 			{
983 				ft.chrg.cpMin = 0;
984 				ft.chrg.cpMax = m_nInitialSearchPos;
985 			}
986 			else
987 			{
988 				ft.chrg.cpMin = pT->GetTextLength();
989 				ft.chrg.cpMax = m_nInitialSearchPos;
990 			}
991 			m_nInitialSearchPos = m_nInitialSearchPos - pT->GetTextLength();
992 
993 			bRet = (pT->FindAndSelect(dwFlags, ft) != -1) ? TRUE : FALSE;
994 		}
995 
996 		return bRet;
997 	}
998 
FindAndSelect(DWORD dwFlags,FINDTEXTEX & ft)999 	long FindAndSelect(DWORD dwFlags, FINDTEXTEX& ft)
1000 	{
1001 		T* pT = static_cast<T*>(this);
1002 		LONG index = pT->FindText(dwFlags, ft);
1003 		if(index != -1) // i.e. we found something
1004 			pT->SetSel(ft.chrgText);
1005 
1006 		return index;
1007 	}
1008 };
1009 
1010 }; // namespace WTL
1011 
1012 #endif // __ATLFIND_H__
1013