atlfind.h 22 KB

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