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