| // Windows Template Library - WTL version 8.0 |
| // Copyright (C) Microsoft Corporation. All rights reserved. |
| // |
| // This file is a part of the Windows Template Library. |
| // The use and distribution terms for this software are covered by the |
| // Microsoft Permissive License (Ms-PL) which can be found in the file |
| // Ms-PL.txt at the root of this distribution. |
| |
| #ifndef __ATLFIND_H__ |
| #define __ATLFIND_H__ |
| |
| #pragma once |
| |
| #ifndef __cplusplus |
| #error ATL requires C++ compilation (use a .cpp suffix) |
| #endif |
| |
| #ifdef _WIN32_WCE |
| #error atlfind.h is not supported on Windows CE |
| #endif |
| |
| #ifndef __ATLCTRLS_H__ |
| #error atlfind.h requires atlctrls.h to be included first |
| #endif |
| |
| #ifndef __ATLDLGS_H__ |
| #error atlfind.h requires atldlgs.h to be included first |
| #endif |
| |
| #if !((defined(__ATLMISC_H__) && defined(_WTL_USE_CSTRING)) || defined(__ATLSTR_H__)) |
| #error atlfind.h requires CString (either from ATL's atlstr.h or WTL's atlmisc.h with _WTL_USE_CSTRING) |
| #endif |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Classes in this file: |
| // |
| // CEditFindReplaceImplBase<T, TFindReplaceDialog> |
| // CEditFindReplaceImpl<T, TFindReplaceDialog> |
| // CRichEditFindReplaceImpl<T, TFindReplaceDialog> |
| |
| |
| namespace WTL |
| { |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // CEditFindReplaceImplBase - Base class for mixin classes that |
| // help implement Find/Replace for CEdit or CRichEditCtrl based window classes. |
| |
| template <class T, class TFindReplaceDialog = CFindReplaceDialog> |
| class CEditFindReplaceImplBase |
| { |
| protected: |
| // Typedefs |
| typedef CEditFindReplaceImplBase<T, TFindReplaceDialog> thisClass; |
| |
| // Data members |
| TFindReplaceDialog* m_pFindReplaceDialog; |
| _CSTRING_NS::CString m_sFindNext, m_sReplaceWith; |
| BOOL m_bFindOnly, m_bFirstSearch, m_bMatchCase, m_bWholeWord, m_bFindDown; |
| LONG m_nInitialSearchPos; |
| HCURSOR m_hOldCursor; |
| |
| // Enumerations |
| enum TranslationTextItem |
| { |
| eText_OnReplaceAllMessage = 0, |
| eText_OnReplaceAllTitle = 1, |
| eText_OnTextNotFoundMessage = 2, |
| eText_OnTextNotFoundTitle = 3 |
| }; |
| |
| public: |
| // Constructors |
| CEditFindReplaceImplBase() : |
| m_pFindReplaceDialog(NULL), |
| m_bFindOnly(TRUE), |
| m_bFirstSearch(TRUE), |
| m_bMatchCase(FALSE), |
| m_bWholeWord(FALSE), |
| m_bFindDown(TRUE), |
| m_nInitialSearchPos(0), |
| m_hOldCursor(NULL) |
| { |
| } |
| |
| // Message Handlers |
| BEGIN_MSG_MAP(thisClass) |
| ALT_MSG_MAP(1) |
| MESSAGE_HANDLER(WM_DESTROY, OnDestroy) |
| MESSAGE_HANDLER(TFindReplaceDialog::GetFindReplaceMsg(), OnFindReplaceCmd) |
| COMMAND_ID_HANDLER(ID_EDIT_FIND, OnEditFind) |
| COMMAND_ID_HANDLER(ID_EDIT_REPEAT, OnEditRepeat) |
| COMMAND_ID_HANDLER(ID_EDIT_REPLACE, OnEditReplace) |
| END_MSG_MAP() |
| |
| LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) |
| { |
| if(m_pFindReplaceDialog != NULL) |
| { |
| m_pFindReplaceDialog->SendMessage(WM_CLOSE); |
| ATLASSERT(m_pFindReplaceDialog == NULL); |
| } |
| |
| bHandled = FALSE; |
| return 0; |
| } |
| |
| LRESULT OnFindReplaceCmd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/) |
| { |
| T* pT = static_cast<T*>(this); |
| |
| TFindReplaceDialog* pDialog = TFindReplaceDialog::GetNotifier(lParam); |
| if(pDialog == NULL) |
| { |
| ATLASSERT(FALSE); |
| ::MessageBeep(MB_ICONERROR); |
| return 1; |
| } |
| ATLASSERT(pDialog == m_pFindReplaceDialog); |
| |
| LPFINDREPLACE findReplace = (LPFINDREPLACE)lParam; |
| if((m_pFindReplaceDialog != NULL) && (findReplace != NULL)) |
| { |
| if(pDialog->FindNext()) |
| { |
| pT->OnFindNext(pDialog->GetFindString(), pDialog->SearchDown(), |
| pDialog->MatchCase(), pDialog->MatchWholeWord()); |
| } |
| else if(pDialog->ReplaceCurrent()) |
| { |
| pT->OnReplaceSel(pDialog->GetFindString(), |
| pDialog->SearchDown(), pDialog->MatchCase(), pDialog->MatchWholeWord(), |
| pDialog->GetReplaceString()); |
| } |
| else if(pDialog->ReplaceAll()) |
| { |
| pT->OnReplaceAll(pDialog->GetFindString(), pDialog->GetReplaceString(), |
| pDialog->MatchCase(), pDialog->MatchWholeWord()); |
| } |
| else if(pDialog->IsTerminating()) |
| { |
| // Dialog is going away (but hasn't gone away yet) |
| // OnFinalMessage will "delete this" |
| pT->OnTerminatingFindReplaceDialog(m_pFindReplaceDialog); |
| m_pFindReplaceDialog = NULL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| LRESULT OnEditFind(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) |
| { |
| T* pT = static_cast<T*>(this); |
| pT->FindReplace(TRUE); |
| |
| return 0; |
| } |
| |
| LRESULT OnEditRepeat(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) |
| { |
| T* pT = static_cast<T*>(this); |
| |
| // If the user is holding down SHIFT when hitting F3, we'll |
| // search in reverse. Otherwise, we'll search forward. |
| // (be sure to have an accelerator mapped to ID_EDIT_REPEAT |
| // for both F3 and Shift+F3) |
| m_bFindDown = !((::GetKeyState(VK_SHIFT) & 0x8000) == 0x8000); |
| |
| if(m_sFindNext.IsEmpty()) |
| { |
| pT->FindReplace(TRUE); |
| } |
| else |
| { |
| if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown)) |
| pT->TextNotFound(m_sFindNext); |
| } |
| |
| return 0; |
| } |
| |
| LRESULT OnEditReplace(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& bHandled) |
| { |
| T* pT = static_cast<T*>(this); |
| |
| DWORD style = pT->GetStyle(); |
| if((style & ES_READONLY) != ES_READONLY) |
| { |
| pT->FindReplace(FALSE); |
| } |
| else |
| { |
| // Don't allow replace when the edit control is read only |
| bHandled = FALSE; |
| } |
| |
| return 0; |
| } |
| |
| // Operations (overrideable) |
| TFindReplaceDialog* CreateFindReplaceDialog(BOOL bFindOnly, // TRUE for Find, FALSE for FindReplace |
| LPCTSTR lpszFindWhat, |
| LPCTSTR lpszReplaceWith = NULL, |
| DWORD dwFlags = FR_DOWN, |
| HWND hWndParent = NULL) |
| { |
| // You can override all of this in a derived class |
| |
| TFindReplaceDialog* findReplaceDialog = new TFindReplaceDialog(); |
| if(findReplaceDialog == NULL) |
| { |
| ::MessageBeep(MB_ICONHAND); |
| } |
| else |
| { |
| HWND hWndFindReplace = findReplaceDialog->Create(bFindOnly, |
| lpszFindWhat, lpszReplaceWith, dwFlags, hWndParent); |
| if(hWndFindReplace == NULL) |
| { |
| delete findReplaceDialog; |
| findReplaceDialog = NULL; |
| } |
| else |
| { |
| findReplaceDialog->SetActiveWindow(); |
| findReplaceDialog->ShowWindow(SW_SHOW); |
| } |
| } |
| |
| return findReplaceDialog; |
| } |
| |
| void AdjustDialogPosition(HWND hWndDialog) |
| { |
| ATLASSERT((hWndDialog != NULL) && ::IsWindow(hWndDialog)); |
| |
| T* pT = static_cast<T*>(this); |
| LONG nStartChar = 0, nEndChar = 0; |
| // Send EM_GETSEL so we can use both Edit and RichEdit |
| // (CEdit::GetSel uses int&, and CRichEditCtrlT::GetSel uses LONG&) |
| ::SendMessage(pT->m_hWnd, EM_GETSEL, (WPARAM)&nStartChar, (LPARAM)&nEndChar); |
| POINT point = pT->PosFromChar(nStartChar); |
| ::ClientToScreen(pT->GetParent(), &point); |
| CRect rect; |
| ::GetWindowRect(hWndDialog, &rect); |
| if(rect.PtInRect(point)) |
| { |
| if(point.y > rect.Height()) |
| { |
| rect.OffsetRect(0, point.y - rect.bottom - 20); |
| } |
| else |
| { |
| int nVertExt = GetSystemMetrics(SM_CYSCREEN); |
| if(point.y + rect.Height() < nVertExt) |
| rect.OffsetRect(0, 40 + point.y - rect.top); |
| } |
| |
| ::MoveWindow(hWndDialog, rect.left, rect.top, rect.Width(), rect.Height(), TRUE); |
| } |
| } |
| |
| DWORD GetFindReplaceDialogFlags(void) const |
| { |
| DWORD dwFlags = 0; |
| |
| if(m_bFindDown) |
| dwFlags |= FR_DOWN; |
| if(m_bMatchCase) |
| dwFlags |= FR_MATCHCASE; |
| if(m_bWholeWord) |
| dwFlags |= FR_WHOLEWORD; |
| |
| return dwFlags; |
| } |
| |
| void FindReplace(BOOL bFindOnly) |
| { |
| T* pT = static_cast<T*>(this); |
| m_bFirstSearch = TRUE; |
| if(m_pFindReplaceDialog != NULL) |
| { |
| if(m_bFindOnly == bFindOnly) |
| { |
| m_pFindReplaceDialog->SetActiveWindow(); |
| m_pFindReplaceDialog->ShowWindow(SW_SHOW); |
| return; |
| } |
| else |
| { |
| m_pFindReplaceDialog->SendMessage(WM_CLOSE); |
| ATLASSERT(m_pFindReplaceDialog == NULL); |
| } |
| } |
| |
| ATLASSERT(m_pFindReplaceDialog == NULL); |
| |
| _CSTRING_NS::CString findNext; |
| pT->GetSelText(findNext); |
| // if selection is empty or spans multiple lines use old find text |
| if(findNext.IsEmpty() || (findNext.FindOneOf(_T("\n\r")) != -1)) |
| findNext = m_sFindNext; |
| _CSTRING_NS::CString replaceWith = m_sReplaceWith; |
| DWORD dwFlags = pT->GetFindReplaceDialogFlags(); |
| |
| m_pFindReplaceDialog = pT->CreateFindReplaceDialog(bFindOnly, |
| findNext, replaceWith, dwFlags, pT->operator HWND()); |
| ATLASSERT(m_pFindReplaceDialog != NULL); |
| if(m_pFindReplaceDialog != NULL) |
| m_bFindOnly = bFindOnly; |
| } |
| |
| BOOL SameAsSelected(LPCTSTR lpszCompare, BOOL bMatchCase, BOOL /*bWholeWord*/) |
| { |
| T* pT = static_cast<T*>(this); |
| |
| // check length first |
| size_t nLen = lstrlen(lpszCompare); |
| LONG nStartChar = 0, nEndChar = 0; |
| // Send EM_GETSEL so we can use both Edit and RichEdit |
| // (CEdit::GetSel uses int&, and CRichEditCtrlT::GetSel uses LONG&) |
| ::SendMessage(pT->m_hWnd, EM_GETSEL, (WPARAM)&nStartChar, (LPARAM)&nEndChar); |
| if(nLen != (size_t)(nEndChar - nStartChar)) |
| return FALSE; |
| |
| // length is the same, check contents |
| _CSTRING_NS::CString selectedText; |
| pT->GetSelText(selectedText); |
| |
| return (bMatchCase && selectedText.Compare(lpszCompare) == 0) || |
| (!bMatchCase && selectedText.CompareNoCase(lpszCompare) == 0); |
| } |
| |
| void TextNotFound(LPCTSTR lpszFind) |
| { |
| T* pT = static_cast<T*>(this); |
| m_bFirstSearch = TRUE; |
| pT->OnTextNotFound(lpszFind); |
| } |
| |
| _CSTRING_NS::CString GetTranslationText(enum TranslationTextItem eItem) const |
| { |
| _CSTRING_NS::CString text; |
| switch(eItem) |
| { |
| case eText_OnReplaceAllMessage: |
| text = _T("Replaced %d occurances of \"%s\" with \"%s\""); |
| break; |
| case eText_OnReplaceAllTitle: |
| text = _T("Replace All"); |
| break; |
| case eText_OnTextNotFoundMessage: |
| text = _T("Unable to find the text \"%s\""); |
| break; |
| case eText_OnTextNotFoundTitle: |
| text = _T("Text not found"); |
| break; |
| } |
| |
| return text; |
| } |
| |
| // Overrideable Handlers |
| void OnFindNext(LPCTSTR lpszFind, BOOL bFindDown, BOOL bMatchCase, BOOL bWholeWord) |
| { |
| T* pT = static_cast<T*>(this); |
| |
| m_sFindNext = lpszFind; |
| m_bMatchCase = bMatchCase; |
| m_bWholeWord = bWholeWord; |
| m_bFindDown = bFindDown; |
| |
| if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown)) |
| pT->TextNotFound(m_sFindNext); |
| else |
| pT->AdjustDialogPosition(m_pFindReplaceDialog->operator HWND()); |
| } |
| |
| void OnReplaceSel(LPCTSTR lpszFind, BOOL bFindDown, BOOL bMatchCase, BOOL bWholeWord, LPCTSTR lpszReplace) |
| { |
| T* pT = static_cast<T*>(this); |
| |
| m_sFindNext = lpszFind; |
| m_sReplaceWith = lpszReplace; |
| m_bMatchCase = bMatchCase; |
| m_bWholeWord = bWholeWord; |
| m_bFindDown = bFindDown; |
| |
| if(pT->SameAsSelected(m_sFindNext, m_bMatchCase, m_bWholeWord)) |
| pT->ReplaceSel(m_sReplaceWith); |
| |
| if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown)) |
| pT->TextNotFound(m_sFindNext); |
| else |
| pT->AdjustDialogPosition(m_pFindReplaceDialog->operator HWND()); |
| } |
| |
| void OnReplaceAll(LPCTSTR lpszFind, LPCTSTR lpszReplace, BOOL bMatchCase, BOOL bWholeWord) |
| { |
| T* pT = static_cast<T*>(this); |
| |
| m_sFindNext = lpszFind; |
| m_sReplaceWith = lpszReplace; |
| m_bMatchCase = bMatchCase; |
| m_bWholeWord = bWholeWord; |
| m_bFindDown = TRUE; |
| |
| // no selection or different than what looking for |
| if(!pT->SameAsSelected(m_sFindNext, m_bMatchCase, m_bWholeWord)) |
| { |
| if(!pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown)) |
| { |
| pT->TextNotFound(m_sFindNext); |
| return; |
| } |
| } |
| |
| pT->OnReplaceAllCoreBegin(); |
| |
| int replaceCount=0; |
| do |
| { |
| ++replaceCount; |
| pT->ReplaceSel(m_sReplaceWith); |
| } while(pT->FindTextSimple(m_sFindNext, m_bMatchCase, m_bWholeWord, m_bFindDown)); |
| |
| pT->OnReplaceAllCoreEnd(replaceCount); |
| } |
| |
| void OnReplaceAllCoreBegin() |
| { |
| T* pT = static_cast<T*>(this); |
| |
| m_hOldCursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT)); |
| |
| pT->HideSelection(TRUE, FALSE); |
| |
| } |
| |
| void OnReplaceAllCoreEnd(int replaceCount) |
| { |
| T* pT = static_cast<T*>(this); |
| pT->HideSelection(FALSE, FALSE); |
| |
| ::SetCursor(m_hOldCursor); |
| |
| _CSTRING_NS::CString message = pT->GetTranslationText(eText_OnReplaceAllMessage); |
| if(message.GetLength() > 0) |
| { |
| _CSTRING_NS::CString formattedMessage; |
| formattedMessage.Format(message, |
| replaceCount, m_sFindNext, m_sReplaceWith); |
| if(m_pFindReplaceDialog != NULL) |
| { |
| m_pFindReplaceDialog->MessageBox(formattedMessage, |
| pT->GetTranslationText(eText_OnReplaceAllTitle), |
| MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); |
| } |
| else |
| { |
| pT->MessageBox(formattedMessage, |
| pT->GetTranslationText(eText_OnReplaceAllTitle), |
| MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); |
| } |
| } |
| } |
| |
| void OnTextNotFound(LPCTSTR lpszFind) |
| { |
| T* pT = static_cast<T*>(this); |
| _CSTRING_NS::CString message = pT->GetTranslationText(eText_OnTextNotFoundMessage); |
| if(message.GetLength() > 0) |
| { |
| _CSTRING_NS::CString formattedMessage; |
| formattedMessage.Format(message, lpszFind); |
| if(m_pFindReplaceDialog != NULL) |
| { |
| m_pFindReplaceDialog->MessageBox(formattedMessage, |
| pT->GetTranslationText(eText_OnTextNotFoundTitle), |
| MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); |
| } |
| else |
| { |
| pT->MessageBox(formattedMessage, |
| pT->GetTranslationText(eText_OnTextNotFoundTitle), |
| MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); |
| } |
| } |
| else |
| { |
| ::MessageBeep(MB_ICONHAND); |
| } |
| } |
| |
| void OnTerminatingFindReplaceDialog(TFindReplaceDialog*& /*findReplaceDialog*/) |
| { |
| } |
| }; |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // CEditFindReplaceImpl - Mixin class for implementing Find/Replace for CEdit |
| // based window classes. |
| |
| // Chain to CEditFindReplaceImpl message map. Your class must also derive from CEdit. |
| // Example: |
| // class CMyEdit : public CWindowImpl<CMyEdit, CEdit>, |
| // public CEditFindReplaceImpl<CMyEdit> |
| // { |
| // public: |
| // BEGIN_MSG_MAP(CMyEdit) |
| // // your handlers... |
| // CHAIN_MSG_MAP_ALT(CEditFindReplaceImpl<CMyEdit>, 1) |
| // END_MSG_MAP() |
| // // other stuff... |
| // }; |
| |
| template <class T, class TFindReplaceDialog = CFindReplaceDialog> |
| class CEditFindReplaceImpl : public CEditFindReplaceImplBase<T, TFindReplaceDialog> |
| { |
| protected: |
| typedef CEditFindReplaceImpl<T, TFindReplaceDialog> thisClass; |
| typedef CEditFindReplaceImplBase<T, TFindReplaceDialog> baseClass; |
| |
| // Data members |
| LPTSTR m_pShadowBuffer; // Special shadow buffer only used in some cases. |
| UINT m_nShadowSize; |
| int m_bShadowBufferNeeded; // TRUE, FALSE, < 0 => Need to check |
| |
| public: |
| // Constructors |
| CEditFindReplaceImpl() : |
| m_pShadowBuffer(NULL), |
| m_nShadowSize(0), |
| m_bShadowBufferNeeded(-1) |
| { |
| } |
| |
| virtual ~CEditFindReplaceImpl() |
| { |
| if(m_pShadowBuffer != NULL) |
| { |
| delete [] m_pShadowBuffer; |
| m_pShadowBuffer = NULL; |
| } |
| } |
| |
| // Message Handlers |
| BEGIN_MSG_MAP(thisClass) |
| ALT_MSG_MAP(1) |
| CHAIN_MSG_MAP_ALT(baseClass, 1) |
| END_MSG_MAP() |
| |
| // Operations |
| // Supported only for RichEdit, so this does nothing for Edit |
| void HideSelection(BOOL /*bHide*/ = TRUE, BOOL /*bChangeStyle*/ = FALSE) |
| { |
| } |
| |
| // Operations (overrideable) |
| BOOL FindTextSimple(LPCTSTR lpszFind, BOOL bMatchCase, BOOL bWholeWord, BOOL bFindDown = TRUE) |
| { |
| T* pT = static_cast<T*>(this); |
| |
| ATLASSERT(lpszFind != NULL); |
| ATLASSERT(*lpszFind != _T('\0')); |
| |
| UINT nLen = pT->GetBufferLength(); |
| int nStartChar = 0, nEndChar = 0; |
| pT->GetSel(nStartChar, nEndChar); |
| UINT nStart = nStartChar; |
| int iDir = bFindDown ? +1 : -1; |
| |
| // can't find a match before the first character |
| if(nStart == 0 && iDir < 0) |
| return FALSE; |
| |
| LPCTSTR lpszText = pT->LockBuffer(); |
| |
| bool isDBCS = false; |
| #ifdef _MBCS |
| CPINFO info = { 0 }; |
| ::GetCPInfo(::GetOEMCP(), &info); |
| isDBCS = (info.MaxCharSize > 1); |
| #endif |
| |
| if(iDir < 0) |
| { |
| // always go back one for search backwards |
| nStart -= int((lpszText + nStart) - ::CharPrev(lpszText, lpszText + nStart)); |
| } |
| else if(nStartChar != nEndChar && pT->SameAsSelected(lpszFind, bMatchCase, bWholeWord)) |
| { |
| // easy to go backward/forward with SBCS |
| #ifndef _UNICODE |
| if(::IsDBCSLeadByte(lpszText[nStart])) |
| nStart++; |
| #endif |
| nStart += iDir; |
| } |
| |
| // handle search with nStart past end of buffer |
| UINT nLenFind = ::lstrlen(lpszFind); |
| if(nStart + nLenFind - 1 >= nLen) |
| { |
| if(iDir < 0 && nLen >= nLenFind) |
| { |
| if(isDBCS) |
| { |
| // walk back to previous character n times |
| nStart = nLen; |
| int n = nLenFind; |
| while(n--) |
| { |
| nStart -= int((lpszText + nStart) - ::CharPrev(lpszText, lpszText + nStart)); |
| } |
| } |
| else |
| { |
| // single-byte character set is easy and fast |
| nStart = nLen - nLenFind; |
| } |
| ATLASSERT(nStart + nLenFind - 1 <= nLen); |
| } |
| else |
| { |
| pT->UnlockBuffer(); |
| return FALSE; |
| } |
| } |
| |
| // start the search at nStart |
| LPCTSTR lpsz = lpszText + nStart; |
| typedef int (WINAPI* CompareProc)(LPCTSTR str1, LPCTSTR str2); |
| CompareProc pfnCompare = bMatchCase ? lstrcmp : lstrcmpi; |
| |
| if(isDBCS) |
| { |
| // double-byte string search |
| LPCTSTR lpszStop = NULL; |
| if(iDir > 0) |
| { |
| // start at current and find _first_ occurrance |
| lpszStop = lpszText + nLen - nLenFind + 1; |
| } |
| else |
| { |
| // start at top and find _last_ occurrance |
| lpszStop = lpsz; |
| lpsz = lpszText; |
| } |
| |
| LPCTSTR lpszFound = NULL; |
| while(lpsz <= lpszStop) |
| { |
| #ifndef _UNICODE |
| if(!bMatchCase || (*lpsz == *lpszFind && (!::IsDBCSLeadByte(*lpsz) || lpsz[1] == lpszFind[1]))) |
| #else |
| if(!bMatchCase || (*lpsz == *lpszFind && lpsz[1] == lpszFind[1])) |
| #endif |
| { |
| LPTSTR lpch = (LPTSTR)(lpsz + nLenFind); |
| TCHAR chSave = *lpch; |
| *lpch = _T('\0'); |
| int nResult = (*pfnCompare)(lpsz, lpszFind); |
| *lpch = chSave; |
| if(nResult == 0) |
| { |
| lpszFound = lpsz; |
| if(iDir > 0) |
| break; |
| } |
| } |
| lpsz = ::CharNext(lpsz); |
| } |
| pT->UnlockBuffer(); |
| |
| if(lpszFound != NULL) |
| { |
| int n = (int)(lpszFound - lpszText); |
| pT->SetSel(n, n + nLenFind); |
| return TRUE; |
| } |
| } |
| else |
| { |
| // single-byte string search |
| UINT nCompare; |
| if(iDir < 0) |
| nCompare = (UINT)(lpsz - lpszText) + 1; |
| else |
| nCompare = nLen - (UINT)(lpsz - lpszText) - nLenFind + 1; |
| |
| while(nCompare > 0) |
| { |
| ATLASSERT(lpsz >= lpszText); |
| ATLASSERT(lpsz + nLenFind - 1 <= lpszText + nLen - 1); |
| |
| LPSTR lpch = (LPSTR)(lpsz + nLenFind); |
| char chSave = *lpch; |
| *lpch = '\0'; |
| int nResult = (*pfnCompare)(lpsz, lpszFind); |
| *lpch = chSave; |
| if(nResult == 0) |
| { |
| pT->UnlockBuffer(); |
| int n = (int)(lpsz - lpszText); |
| pT->SetSel(n, n + nLenFind); |
| return TRUE; |
| } |
| |
| // restore character at end of search |
| *lpch = chSave; |
| |
| // move on to next substring |
| nCompare--; |
| lpsz += iDir; |
| } |
| pT->UnlockBuffer(); |
| } |
| |
| return FALSE; |
| } |
| |
| LPCTSTR LockBuffer() const |
| { |
| const T* pT = static_cast<const T*>(this); |
| |
| ATLASSERT(pT->m_hWnd != NULL); |
| |
| BOOL useShadowBuffer = pT->UseShadowBuffer(); |
| if(useShadowBuffer) |
| { |
| if(m_pShadowBuffer == NULL || pT->GetModify()) |
| { |
| ATLASSERT(m_pShadowBuffer != NULL || m_nShadowSize == 0); |
| UINT nSize = pT->GetWindowTextLength() + 1; |
| if(nSize > m_nShadowSize) |
| { |
| // need more room for shadow buffer |
| T* pThisNoConst = const_cast<T*>(pT); |
| delete[] m_pShadowBuffer; |
| pThisNoConst->m_pShadowBuffer = NULL; |
| pThisNoConst->m_nShadowSize = 0; |
| pThisNoConst->m_pShadowBuffer = new TCHAR[nSize]; |
| pThisNoConst->m_nShadowSize = nSize; |
| } |
| |
| // update the shadow buffer with GetWindowText |
| ATLASSERT(m_nShadowSize >= nSize); |
| ATLASSERT(m_pShadowBuffer != NULL); |
| pT->GetWindowText(m_pShadowBuffer, nSize); |
| } |
| |
| return m_pShadowBuffer; |
| } |
| |
| HLOCAL hLocal = pT->GetHandle(); |
| ATLASSERT(hLocal != NULL); |
| LPCTSTR lpszText = (LPCTSTR)::LocalLock(hLocal); |
| ATLASSERT(lpszText != NULL); |
| |
| return lpszText; |
| } |
| |
| void UnlockBuffer() const |
| { |
| const T* pT = static_cast<const T*>(this); |
| |
| ATLASSERT(pT->m_hWnd != NULL); |
| |
| BOOL useShadowBuffer = pT->UseShadowBuffer(); |
| if(!useShadowBuffer) |
| { |
| HLOCAL hLocal = pT->GetHandle(); |
| ATLASSERT(hLocal != NULL); |
| ::LocalUnlock(hLocal); |
| } |
| } |
| |
| UINT GetBufferLength() const |
| { |
| const T* pT = static_cast<const T*>(this); |
| |
| ATLASSERT(pT->m_hWnd != NULL); |
| UINT nLen = 0; |
| LPCTSTR lpszText = pT->LockBuffer(); |
| if(lpszText != NULL) |
| nLen = ::lstrlen(lpszText); |
| pT->UnlockBuffer(); |
| |
| return nLen; |
| } |
| |
| LONG EndOfLine(LPCTSTR lpszText, UINT nLen, UINT nIndex) const |
| { |
| LPCTSTR lpsz = lpszText + nIndex; |
| LPCTSTR lpszStop = lpszText + nLen; |
| while(lpsz < lpszStop && *lpsz != _T('\r')) |
| ++lpsz; |
| return LONG(lpsz - lpszText); |
| } |
| |
| LONG GetSelText(_CSTRING_NS::CString& strText) const |
| { |
| const T* pT = static_cast<const T*>(this); |
| |
| int nStartChar = 0, nEndChar = 0; |
| pT->GetSel(nStartChar, nEndChar); |
| ATLASSERT((UINT)nEndChar <= pT->GetBufferLength()); |
| LPCTSTR lpszText = pT->LockBuffer(); |
| LONG nLen = pT->EndOfLine(lpszText, nEndChar, nStartChar) - nStartChar; |
| SecureHelper::memcpy_x(strText.GetBuffer(nLen), nLen * sizeof(TCHAR), lpszText + nStartChar, nLen * sizeof(TCHAR)); |
| strText.ReleaseBuffer(nLen); |
| pT->UnlockBuffer(); |
| |
| return nLen; |
| } |
| |
| BOOL UseShadowBuffer(void) const |
| { |
| const T* pT = static_cast<const T*>(this); |
| |
| if(pT->m_bShadowBufferNeeded < 0) |
| { |
| T* pThisNoConst = const_cast<T*>(pT); |
| |
| OSVERSIONINFO ovi = { 0 }; |
| ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); |
| ::GetVersionEx(&ovi); |
| |
| bool bWin9x = (ovi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); |
| if(bWin9x) |
| { |
| // Windows 95, 98, ME |
| // Under Win9x, it is necessary to maintain a shadow buffer. |
| // It is only updated when the control contents have been changed. |
| pThisNoConst->m_bShadowBufferNeeded = TRUE; |
| } |
| else |
| { |
| // Windows NT, 2000, XP, etc. |
| pThisNoConst->m_bShadowBufferNeeded = FALSE; |
| |
| #ifndef _UNICODE |
| // On Windows XP (or later), if common controls version 6 is in use |
| // (such as via theming), then EM_GETHANDLE will always return a UNICODE string. |
| // If theming is enabled and Common Controls version 6 is in use, |
| // you're really not suppose to superclass or subclass common controls |
| // with an ANSI windows procedure (so its best to only theme if you use UNICODE). |
| // Using a shadow buffer uses GetWindowText instead, so it solves |
| // this problem for us (although it makes it a little less efficient). |
| |
| if((ovi.dwMajorVersion == 5 && ovi.dwMinorVersion >= 1) || (ovi.dwMajorVersion > 5)) |
| { |
| // We use DLLVERSIONINFO_private so we don't have to depend on shlwapi.h |
| typedef struct _DLLVERSIONINFO_private |
| { |
| DWORD cbSize; |
| DWORD dwMajorVersion; |
| DWORD dwMinorVersion; |
| DWORD dwBuildNumber; |
| DWORD dwPlatformID; |
| } DLLVERSIONINFO_private; |
| |
| HMODULE hModule = ::LoadLibrary("comctl32.dll"); |
| if(hModule != NULL) |
| { |
| typedef HRESULT (CALLBACK *LPFN_DllGetVersion)(DLLVERSIONINFO_private *); |
| LPFN_DllGetVersion fnDllGetVersion = (LPFN_DllGetVersion)::GetProcAddress(hModule, "DllGetVersion"); |
| if(fnDllGetVersion != NULL) |
| { |
| DLLVERSIONINFO_private version = { 0 }; |
| version.cbSize = sizeof(DLLVERSIONINFO_private); |
| if(SUCCEEDED(fnDllGetVersion(&version))) |
| { |
| if(version.dwMajorVersion >= 6) |
| { |
| pThisNoConst->m_bShadowBufferNeeded = TRUE; |
| |
| 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")); |
| ATLTRACE2(atlTraceUI, 0, _T("If you use common controls version 6 or later, you should only do so for UNICODE builds.\r\n")); |
| } |
| } |
| } |
| |
| ::FreeLibrary(hModule); |
| hModule = NULL; |
| } |
| } |
| #endif // !_UNICODE |
| } |
| } |
| |
| return (pT->m_bShadowBufferNeeded == TRUE); |
| } |
| }; |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // CRichEditFindReplaceImpl - Mixin class for implementing Find/Replace for CRichEditCtrl |
| // based window classes. |
| |
| // Chain to CRichEditFindReplaceImpl message map. Your class must also derive from CRichEditCtrl. |
| // Example: |
| // class CMyRichEdit : public CWindowImpl<CMyRichEdit, CRichEditCtrl>, |
| // public CRichEditFindReplaceImpl<CMyRichEdit> |
| // { |
| // public: |
| // BEGIN_MSG_MAP(CMyRichEdit) |
| // // your handlers... |
| // CHAIN_MSG_MAP_ALT(CRichEditFindReplaceImpl<CMyRichEdit>, 1) |
| // END_MSG_MAP() |
| // // other stuff... |
| // }; |
| |
| template <class T, class TFindReplaceDialog = CFindReplaceDialog> |
| class CRichEditFindReplaceImpl : public CEditFindReplaceImplBase<T, TFindReplaceDialog> |
| { |
| protected: |
| typedef CRichEditFindReplaceImpl<T, TFindReplaceDialog> thisClass; |
| typedef CEditFindReplaceImplBase<T, TFindReplaceDialog> baseClass; |
| |
| public: |
| BEGIN_MSG_MAP(thisClass) |
| ALT_MSG_MAP(1) |
| CHAIN_MSG_MAP_ALT(baseClass, 1) |
| END_MSG_MAP() |
| |
| // Operations (overrideable) |
| BOOL FindTextSimple(LPCTSTR lpszFind, BOOL bMatchCase, BOOL bWholeWord, BOOL bFindDown = TRUE) |
| { |
| T* pT = static_cast<T*>(this); |
| |
| ATLASSERT(lpszFind != NULL); |
| FINDTEXTEX ft = { 0 }; |
| |
| pT->GetSel(ft.chrg); |
| if(m_bFirstSearch) |
| { |
| if(bFindDown) |
| m_nInitialSearchPos = ft.chrg.cpMin; |
| else |
| m_nInitialSearchPos = ft.chrg.cpMax; |
| m_bFirstSearch = FALSE; |
| } |
| |
| #if (_RICHEDIT_VER >= 0x0200) |
| ft.lpstrText = (LPTSTR)lpszFind; |
| #else // !(_RICHEDIT_VER >= 0x0200) |
| USES_CONVERSION; |
| ft.lpstrText = T2A((LPTSTR)lpszFind); |
| #endif // !(_RICHEDIT_VER >= 0x0200) |
| |
| if(ft.chrg.cpMin != ft.chrg.cpMax) // i.e. there is a selection |
| { |
| if(bFindDown) |
| { |
| ft.chrg.cpMin++; |
| } |
| else |
| { |
| // won't wraparound backwards |
| ft.chrg.cpMin = __max(ft.chrg.cpMin, 0); |
| } |
| } |
| |
| DWORD dwFlags = bMatchCase ? FR_MATCHCASE : 0; |
| dwFlags |= bWholeWord ? FR_WHOLEWORD : 0; |
| |
| ft.chrg.cpMax = pT->GetTextLength() + m_nInitialSearchPos; |
| |
| if(bFindDown) |
| { |
| if(m_nInitialSearchPos >= 0) |
| ft.chrg.cpMax = pT->GetTextLength(); |
| |
| dwFlags |= FR_DOWN; |
| ATLASSERT(ft.chrg.cpMax >= ft.chrg.cpMin); |
| } |
| else |
| { |
| if(m_nInitialSearchPos >= 0) |
| ft.chrg.cpMax = 0; |
| |
| dwFlags &= ~FR_DOWN; |
| ATLASSERT(ft.chrg.cpMax <= ft.chrg.cpMin); |
| } |
| |
| BOOL bRet = FALSE; |
| |
| if(pT->FindAndSelect(dwFlags, ft) != -1) |
| { |
| bRet = TRUE; // we found the text |
| } |
| else if(m_nInitialSearchPos > 0) |
| { |
| // if the original starting point was not the beginning |
| // of the buffer and we haven't already been here |
| if(bFindDown) |
| { |
| ft.chrg.cpMin = 0; |
| ft.chrg.cpMax = m_nInitialSearchPos; |
| } |
| else |
| { |
| ft.chrg.cpMin = pT->GetTextLength(); |
| ft.chrg.cpMax = m_nInitialSearchPos; |
| } |
| m_nInitialSearchPos = m_nInitialSearchPos - pT->GetTextLength(); |
| |
| bRet = (pT->FindAndSelect(dwFlags, ft) != -1) ? TRUE : FALSE; |
| } |
| |
| return bRet; |
| } |
| |
| long FindAndSelect(DWORD dwFlags, FINDTEXTEX& ft) |
| { |
| T* pT = static_cast<T*>(this); |
| LONG index = pT->FindText(dwFlags, ft); |
| if(index != -1) // i.e. we found something |
| pT->SetSel(ft.chrgText); |
| |
| return index; |
| } |
| }; |
| |
| }; // namespace WTL |
| |
| #endif // __ATLFIND_H__ |