|
1
|
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__
|