Use modern subclass on FindReplaceDlg combo boxes

- fix regression with temp search text not recovered
- fix warnings with overloaded virtual functions

Fix #17202, close #17203
This commit is contained in:
ozone10 2025-11-19 17:39:08 +01:00 committed by Don Ho
parent 0745cd922d
commit ebf3657d67
2 changed files with 159 additions and 94 deletions

View File

@ -15,7 +15,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <shlwapi.h>
#include "FindReplaceDlg.h" #include "FindReplaceDlg.h"
#include "ScintillaEditView.h" #include "ScintillaEditView.h"
#include "Notepad_plus_msgs.h" #include "Notepad_plus_msgs.h"
@ -23,6 +22,16 @@
#include "Common.h" #include "Common.h"
#include "Utf8.h" #include "Utf8.h"
#include <windows.h>
#include <commctrl.h>
#include <cstring>
#include <memory>
#include <string>
#include "NppConstants.h"
using namespace std; using namespace std;
FindOption * FindReplaceDlg::_env; FindOption * FindReplaceDlg::_env;
@ -257,7 +266,6 @@ void Searching::displaySectionCentered(size_t posStart, size_t posEnd, Scintilla
} }
WNDPROC FindReplaceDlg::originalFinderProc = nullptr; WNDPROC FindReplaceDlg::originalFinderProc = nullptr;
WNDPROC FindReplaceDlg::originalComboEditProc = nullptr;
FindReplaceDlg::~FindReplaceDlg() FindReplaceDlg::~FindReplaceDlg()
{ {
@ -1572,20 +1580,13 @@ intptr_t CALLBACK FindReplaceDlg::run_dlgProc(UINT message, WPARAM wParam, LPARA
// Change handler of edit element in the comboboxes to support Ctrl+Backspace // Change handler of edit element in the comboboxes to support Ctrl+Backspace
COMBOBOXINFO cbinfo{}; COMBOBOXINFO cbinfo{};
cbinfo.cbSize = sizeof(COMBOBOXINFO); cbinfo.cbSize = sizeof(COMBOBOXINFO);
GetComboBoxInfo(hFindCombo, &cbinfo); for (const auto& hCombo : { hFindCombo, hReplaceCombo, hFiltersCombo, hDirCombo })
if (!cbinfo.hwndItem) return FALSE; {
if (::GetComboBoxInfo(hCombo, &cbinfo) == FALSE || cbinfo.hwndItem == nullptr)
return FALSE;
originalComboEditProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(comboEditProc))); ::SetWindowSubclass(cbinfo.hwndItem, FindReplaceDlg::ComboEditProc, static_cast<UINT_PTR>(SubclassID::first), reinterpret_cast<DWORD_PTR>(cbinfo.hwndCombo));
SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(cbinfo.hwndCombo)); }
GetComboBoxInfo(hReplaceCombo, &cbinfo);
SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(comboEditProc));
SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(cbinfo.hwndCombo));
GetComboBoxInfo(hFiltersCombo, &cbinfo);
SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(comboEditProc));
SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(cbinfo.hwndCombo));
GetComboBoxInfo(hDirCombo, &cbinfo);
SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(comboEditProc));
SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(cbinfo.hwndCombo));
setDpi(); setDpi();
@ -4881,22 +4882,57 @@ LRESULT FAR PASCAL FindReplaceDlg::finderProc(HWND hwnd, UINT message, WPARAM wP
return CallWindowProc(originalFinderProc, hwnd, message, wParam, lParam); return CallWindowProc(originalFinderProc, hwnd, message, wParam, lParam);
} }
LRESULT FAR PASCAL FindReplaceDlg::comboEditProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) LRESULT CALLBACK FindReplaceDlg::ComboEditProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData
)
{ {
HWND hwndCombo = reinterpret_cast<HWND>(GetWindowLongPtr(hwnd, GWLP_USERDATA)); auto* hwndCombo = reinterpret_cast<HWND>(dwRefData);
bool isDropped = ::SendMessage(hwndCombo, CB_GETDROPPEDSTATE, 0, 0) != 0; static constexpr size_t strSize = FINDREPLACE_MAXLENGTH;
static auto draftString = []() -> std::unique_ptr<wchar_t[]>
const size_t strSize = FINDREPLACE_MAXLENGTH;
auto draftString = std::make_unique<wchar_t[]>(strSize);
std::fill_n(draftString.get(), strSize, L'\0');
if (isDropped && (message == WM_KEYDOWN) && (wParam == VK_DELETE))
{ {
auto ptr = std::make_unique<wchar_t[]>(strSize);
std::fill_n(ptr.get(), strSize, L'\0');
return ptr;
}();
switch (uMsg)
{
case WM_NCDESTROY:
{
::RemoveWindowSubclass(hWnd, FindReplaceDlg::ComboEditProc, uIdSubclass);
draftString.reset(nullptr);
break;
}
case WM_KEYDOWN:
{
if (wParam != VK_DELETE && wParam != VK_DOWN && wParam != VK_UP)
{
break;
}
auto curSel = ::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0); auto curSel = ::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0);
if (curSel != CB_ERR) switch (wParam)
{ {
auto itemsRemaining = ::SendMessage(hwndCombo, CB_DELETESTRING, curSel, 0); case VK_DELETE:
{
if (::SendMessage(hwndCombo, CB_GETDROPPEDSTATE, 0, 0) == FALSE) // isNotDropped
{
break;
}
if (curSel == CB_ERR)
{
break;
}
const auto itemsRemaining = ::SendMessage(hwndCombo, CB_DELETESTRING, curSel, 0);
// if we close the dropdown and reopen it, it will be correctly-sized for remaining items // if we close the dropdown and reopen it, it will be correctly-sized for remaining items
::SendMessage(hwndCombo, CB_SHOWDROPDOWN, FALSE, 0); ::SendMessage(hwndCombo, CB_SHOWDROPDOWN, FALSE, 0);
if (itemsRemaining > 0) if (itemsRemaining > 0)
@ -4910,37 +4946,60 @@ LRESULT FAR PASCAL FindReplaceDlg::comboEditProc(HWND hwnd, UINT message, WPARAM
} }
return 0; return 0;
} }
}
else if (message == WM_CHAR && wParam == 0x7F) // ASCII "DEL" (Ctrl+Backspace) case VK_DOWN:
{ {
delLeftWordInEdit(hwnd); if (curSel == CB_ERR)
return 0;
}
else if (message == WM_SETFOCUS)
{
draftString[0] = '\0';
}
else if ((message == WM_KEYDOWN) && (wParam == VK_DOWN) && (::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0) == CB_ERR))
{ {
// down key on unselected combobox item -> store current edit text as draft // down key on unselected combobox item -> store current edit text as draft
::SendMessage(hwndCombo, WM_GETTEXT, FINDREPLACE_MAXLENGTH, reinterpret_cast<LPARAM>(draftString.get())); ::SendMessage(hwndCombo, WM_GETTEXT, WPARAM{ strSize }, reinterpret_cast<LPARAM>(draftString.get()));
} }
else if ((message == WM_KEYDOWN) && (wParam == VK_UP) && (::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0) == CB_ERR)) break;
}
case VK_UP:
{
if (curSel == CB_ERR)
{ {
// up key on unselected combobox item -> no change but select current edit text // up key on unselected combobox item -> no change but select current edit text
::SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1)); ::SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
return 0; return 0;
} }
else if ((message == WM_KEYDOWN) && (wParam == VK_UP) && (::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0) == 0) && std::wcslen(draftString.get()) > 0)
if ((curSel == 0) && std::wcslen(draftString.get()) > 0)
{ {
// up key on top selected combobox item -> restore draft to edit text // up key on top selected combobox item -> restore draft to edit text
::SendMessage(hwndCombo, CB_SETCURSEL, WPARAM(-1), 0); ::SendMessage(hwndCombo, CB_SETCURSEL, static_cast<WPARAM>(-1), 0);
::SendMessage(hwndCombo, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(draftString.get())); ::SendMessage(hwndCombo, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(draftString.get()));
::SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1)); ::SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
return 0; return 0;
} }
else if (message == WM_PASTE) break;
}
default:
break;
}
break;
}
case WM_CHAR:
{
if (wParam == 0x7F) // ASCII DEL (Ctrl+Backspace)
{
delLeftWordInEdit(hWnd);
return 0;
}
break;
}
case WM_SETFOCUS:
{
draftString[0] = L'\0';
break;
}
case WM_PASTE:
{ {
// needed to allow CR (i.e., multiline) into combobox text; // needed to allow CR (i.e., multiline) into combobox text;
// (the default functionality terminates the paste at the first CR character) // (the default functionality terminates the paste at the first CR character)
@ -4950,24 +5009,24 @@ LRESULT FAR PASCAL FindReplaceDlg::comboEditProc(HWND hwnd, UINT message, WPARAM
HWND hReplaceWithCombo = ::GetDlgItem(hParent, IDREPLACEWITH); HWND hReplaceWithCombo = ::GetDlgItem(hParent, IDREPLACEWITH);
if ((hwndCombo == hFindWhatCombo) || (hwndCombo == hReplaceWithCombo)) if ((hwndCombo == hFindWhatCombo) || (hwndCombo == hReplaceWithCombo))
{ {
CLIPFORMAT cfColumnSelect = static_cast<CLIPFORMAT>(::RegisterClipboardFormat(L"MSDEVColumnSelect")); const auto cfColumnSelect = static_cast<CLIPFORMAT>(::RegisterClipboardFormatW(L"MSDEVColumnSelect"));
if (!::IsClipboardFormatAvailable(cfColumnSelect)) if (::IsClipboardFormatAvailable(cfColumnSelect) == FALSE)
{ {
wstring clipboardText = strFromClipboard(); const auto clipboardText = std::wstring{ strFromClipboard() };
if (!clipboardText.empty()) if (!clipboardText.empty())
{ {
HWND hEdit = GetWindow(hwndCombo, GW_CHILD); ::SendMessage(hWnd, EM_REPLACESEL, TRUE, reinterpret_cast<LPARAM>(clipboardText.c_str()));
if (hEdit)
{
::SendMessage(hEdit, EM_REPLACESEL, TRUE, (LPARAM)clipboardText.c_str());
} }
} }
}
return 0; return 0;
} }
break;
} }
return CallWindowProc(originalComboEditProc, hwnd, message, wParam, lParam);
default:
break;
}
return ::DefSubclassProc(hWnd, uMsg, wParam, lParam);
} }
void FindReplaceDlg::hideOrShowCtrl4reduceOrNormalMode(DIALOG_TYPE dlgT) void FindReplaceDlg::hideOrShowCtrl4reduceOrNormalMode(DIALOG_TYPE dlgT)

View File

@ -112,12 +112,13 @@ friend class FindReplaceDlg;
public: public:
Finder() : DockingDlgInterface(IDD_FINDRESULT) { Finder() : DockingDlgInterface(IDD_FINDRESULT) {
_markingsStruct._length = 0; _markingsStruct._length = 0;
_markingsStruct._markings = NULL; _markingsStruct._markings = nullptr;
} }
~Finder() override { ~Finder() override {
_scintView.destroy(); _scintView.destroy();
} }
void init(HINSTANCE hInst, HWND hPere, ScintillaEditView **ppEditView) { void init(HINSTANCE hInst, HWND hPere, ScintillaEditView **ppEditView) {
DockingDlgInterface::init(hInst, hPere); DockingDlgInterface::init(hInst, hPere);
_ppEditView = ppEditView; _ppEditView = ppEditView;
@ -186,6 +187,8 @@ private:
std::wstring _prefixLineStr; std::wstring _prefixLineStr;
using DockingDlgInterface::init;
void setFinderReadOnly(bool isReadOnly) { void setFinderReadOnly(bool isReadOnly) {
_scintView.execute(SCI_SETREADONLY, isReadOnly); _scintView.execute(SCI_SETREADONLY, isReadOnly);
} }
@ -246,8 +249,6 @@ private:
void writeOptions(); void writeOptions();
}; };
LRESULT run_swapButtonProc(WNDPROC oldEditProc, HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
class FindReplaceDlg : public StaticDialog class FindReplaceDlg : public StaticDialog
{ {
friend class FindIncrementDlg; friend class FindIncrementDlg;
@ -328,7 +329,7 @@ public :
void changeTabName(DIALOG_TYPE index, const wchar_t *name2change) { void changeTabName(DIALOG_TYPE index, const wchar_t *name2change) {
TCITEM tie{}; TCITEM tie{};
tie.mask = TCIF_TEXT; tie.mask = TCIF_TEXT;
tie.pszText = (wchar_t *)name2change; tie.pszText = const_cast<wchar_t*>(name2change);
TabCtrl_SetItem(_tab.getHSelf(), index, &tie); TabCtrl_SetItem(_tab.getHSelf(), index, &tie);
wchar_t label[MAX_PATH]{}; wchar_t label[MAX_PATH]{};
@ -426,9 +427,8 @@ protected :
void resizeDialogElements(); void resizeDialogElements();
intptr_t CALLBACK run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam) override; intptr_t CALLBACK run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam) override;
static WNDPROC originalFinderProc; static WNDPROC originalFinderProc;
static WNDPROC originalComboEditProc;
static LRESULT FAR PASCAL comboEditProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK ComboEditProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
// Window procedure for the finder // Window procedure for the finder
static LRESULT FAR PASCAL finderProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static LRESULT FAR PASCAL finderProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
@ -486,6 +486,9 @@ private:
ControlInfoTip _maxLenOnSearchTip; ControlInfoTip _maxLenOnSearchTip;
using Window::init;
using StaticDialog::create;
void enableFindDlgItem(int dlgItemID, bool isEnable = true); void enableFindDlgItem(int dlgItemID, bool isEnable = true);
void showFindDlgItem(int dlgItemID, bool isShow = true); void showFindDlgItem(int dlgItemID, bool isShow = true);
@ -538,6 +541,7 @@ class FindIncrementDlg : public StaticDialog
{ {
public : public :
FindIncrementDlg() = default; FindIncrementDlg() = default;
void init(HINSTANCE hInst, HWND hPere, FindReplaceDlg *pFRDlg, bool isRTL = false); void init(HINSTANCE hInst, HWND hPere, FindReplaceDlg *pFRDlg, bool isRTL = false);
void destroy() override; void destroy() override;
void display(bool toShow = true) const override; void display(bool toShow = true) const override;
@ -561,6 +565,8 @@ private :
ReBar* _pRebar = nullptr; ReBar* _pRebar = nullptr;
REBARBANDINFO _rbBand{}; REBARBANDINFO _rbBand{};
using Window::init;
intptr_t CALLBACK run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam) override; intptr_t CALLBACK run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam) override;
void markSelectedTextInc(bool enable, FindOption *opt = NULL); void markSelectedTextInc(bool enable, FindOption *opt = NULL);
}; };