886 lines
23 KiB
C++
886 lines
23 KiB
C++
// Windows Template Library - WTL version 10.0
|
|
// Copyright (C) Microsoft Corporation, WTL Team. 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 Public License (http://opensource.org/licenses/MS-PL)
|
|
// which can be found in the file MS-PL.txt at the root folder.
|
|
|
|
#ifndef __ATLFIND_H__
|
|
#define __ATLFIND_H__
|
|
|
|
#pragma once
|
|
|
|
#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
|
|
|
|
#ifndef __ATLSTR_H__
|
|
#error atlfind.h requires 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;
|
|
|
|
// Enumerations
|
|
enum TranslationTextItem
|
|
{
|
|
eText_OnReplaceAllMessage = 0,
|
|
eText_OnReplaceAllTitle = 1,
|
|
eText_OnTextNotFoundMessage = 2,
|
|
eText_OnTextNotFoundTitle = 3
|
|
};
|
|
|
|
public:
|
|
// Data members
|
|
TFindReplaceDialog* m_pFindReplaceDialog;
|
|
ATL::CString m_sFindNext, m_sReplaceWith;
|
|
BOOL m_bFindOnly, m_bFirstSearch, m_bMatchCase, m_bWholeWord, m_bFindDown;
|
|
LONG m_nInitialSearchPos;
|
|
HCURSOR m_hOldCursor;
|
|
|
|
// 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(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 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 = NULL;
|
|
ATLTRY(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);
|
|
pT->ClientToScreen(&point);
|
|
RECT rect = {};
|
|
::GetWindowRect(hWndDialog, &rect);
|
|
if(::PtInRect(&rect, point) != FALSE)
|
|
{
|
|
if(point.y > (rect.bottom - rect.top))
|
|
{
|
|
::OffsetRect(&rect, 0, point.y - rect.bottom - 20);
|
|
}
|
|
else
|
|
{
|
|
int nVertExt = GetSystemMetrics(SM_CYSCREEN);
|
|
if((point.y + (rect.bottom - rect.top)) < nVertExt)
|
|
::OffsetRect(&rect, 0, 40 + point.y - rect.top);
|
|
}
|
|
|
|
::MoveWindow(hWndDialog, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
|
|
}
|
|
}
|
|
|
|
DWORD GetFindReplaceDialogFlags() 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);
|
|
|
|
ATL::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;
|
|
ATL::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
|
|
ATL::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);
|
|
}
|
|
|
|
ATL::CString GetTranslationText(enum TranslationTextItem eItem) const
|
|
{
|
|
ATL::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);
|
|
|
|
ATL::CString message = pT->GetTranslationText(eText_OnReplaceAllMessage);
|
|
if(message.GetLength() > 0)
|
|
{
|
|
ATL::CString formattedMessage;
|
|
formattedMessage.Format(message, replaceCount, (LPCTSTR)m_sFindNext, (LPCTSTR)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);
|
|
ATL::CString message = pT->GetTranslationText(eText_OnTextNotFoundMessage);
|
|
if(message.GetLength() > 0)
|
|
{
|
|
ATL::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;
|
|
|
|
public:
|
|
// 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 = {};
|
|
::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 = 0;
|
|
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);
|
|
|
|
#ifndef _UNICODE
|
|
// If common controls version 6 is in use (such as via theming), then EM_GETHANDLE
|
|
// will always return a UNICODE string. You're really not supposed to superclass
|
|
// or subclass common controls with an ANSI windows procedure.
|
|
DWORD dwMajor = 0, dwMinor = 0;
|
|
HRESULT hRet = ATL::AtlGetCommCtrlVersion(&dwMajor, &dwMinor);
|
|
if(SUCCEEDED(hRet) && (dwMajor >= 6))
|
|
{
|
|
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"));
|
|
ATLASSERT(FALSE);
|
|
}
|
|
#endif // !_UNICODE
|
|
|
|
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);
|
|
|
|
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(ATL::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;
|
|
ATL::Checked::memcpy_s(strText.GetBuffer(nLen), nLen * sizeof(TCHAR), lpszText + nStartChar, nLen * sizeof(TCHAR));
|
|
strText.ReleaseBuffer(nLen);
|
|
pT->UnlockBuffer();
|
|
|
|
return nLen;
|
|
}
|
|
};
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// 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 = {};
|
|
|
|
pT->GetSel(ft.chrg);
|
|
if(this->m_bFirstSearch)
|
|
{
|
|
if(bFindDown)
|
|
this->m_nInitialSearchPos = ft.chrg.cpMin;
|
|
else
|
|
this->m_nInitialSearchPos = ft.chrg.cpMax;
|
|
this->m_bFirstSearch = FALSE;
|
|
}
|
|
|
|
ft.lpstrText = (LPTSTR)lpszFind;
|
|
|
|
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() + this->m_nInitialSearchPos;
|
|
|
|
if(bFindDown)
|
|
{
|
|
if(this->m_nInitialSearchPos >= 0)
|
|
ft.chrg.cpMax = pT->GetTextLength();
|
|
|
|
dwFlags |= FR_DOWN;
|
|
ATLASSERT(ft.chrg.cpMax >= ft.chrg.cpMin);
|
|
}
|
|
else
|
|
{
|
|
if(this->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(this->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 = this->m_nInitialSearchPos;
|
|
}
|
|
else
|
|
{
|
|
ft.chrg.cpMin = pT->GetTextLength();
|
|
ft.chrg.cpMax = this->m_nInitialSearchPos;
|
|
}
|
|
this->m_nInitialSearchPos = this->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__
|