root/third_party/wtl/include/atlfind.h

/* [<][>][^][v][top][bottom][index][help] */
// 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__

/* [<][>][^][v][top][bottom][index][help] */