From ebf3657d673170bf29a5c10088417d03a5a2bc6d Mon Sep 17 00:00:00 2001 From: ozone10 Date: Wed, 19 Nov 2025 17:39:08 +0100 Subject: [PATCH] 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 --- .../src/ScintillaComponent/FindReplaceDlg.cpp | 235 +++++++++++------- .../src/ScintillaComponent/FindReplaceDlg.h | 18 +- 2 files changed, 159 insertions(+), 94 deletions(-) diff --git a/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp b/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp index fcfe22637..2433fc56c 100644 --- a/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp +++ b/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp @@ -15,7 +15,6 @@ // along with this program. If not, see . -#include #include "FindReplaceDlg.h" #include "ScintillaEditView.h" #include "Notepad_plus_msgs.h" @@ -23,6 +22,16 @@ #include "Common.h" #include "Utf8.h" +#include + +#include + +#include +#include +#include + +#include "NppConstants.h" + using namespace std; FindOption * FindReplaceDlg::_env; @@ -257,7 +266,6 @@ void Searching::displaySectionCentered(size_t posStart, size_t posEnd, Scintilla } WNDPROC FindReplaceDlg::originalFinderProc = nullptr; -WNDPROC FindReplaceDlg::originalComboEditProc = nullptr; FindReplaceDlg::~FindReplaceDlg() { @@ -1572,21 +1580,14 @@ intptr_t CALLBACK FindReplaceDlg::run_dlgProc(UINT message, WPARAM wParam, LPARA // Change handler of edit element in the comboboxes to support Ctrl+Backspace COMBOBOXINFO cbinfo{}; cbinfo.cbSize = sizeof(COMBOBOXINFO); - GetComboBoxInfo(hFindCombo, &cbinfo); - if (!cbinfo.hwndItem) return FALSE; - - originalComboEditProc = reinterpret_cast(SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast(comboEditProc))); - SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast(cbinfo.hwndCombo)); - GetComboBoxInfo(hReplaceCombo, &cbinfo); - SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast(comboEditProc)); - SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast(cbinfo.hwndCombo)); - GetComboBoxInfo(hFiltersCombo, &cbinfo); - SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast(comboEditProc)); - SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast(cbinfo.hwndCombo)); - GetComboBoxInfo(hDirCombo, &cbinfo); - SetWindowLongPtr(cbinfo.hwndItem, GWLP_WNDPROC, reinterpret_cast(comboEditProc)); - SetWindowLongPtr(cbinfo.hwndItem, GWLP_USERDATA, reinterpret_cast(cbinfo.hwndCombo)); + for (const auto& hCombo : { hFindCombo, hReplaceCombo, hFiltersCombo, hDirCombo }) + { + if (::GetComboBoxInfo(hCombo, &cbinfo) == FALSE || cbinfo.hwndItem == nullptr) + return FALSE; + ::SetWindowSubclass(cbinfo.hwndItem, FindReplaceDlg::ComboEditProc, static_cast(SubclassID::first), reinterpret_cast(cbinfo.hwndCombo)); + } + setDpi(); HFONT hFont = nullptr; @@ -4881,93 +4882,151 @@ LRESULT FAR PASCAL FindReplaceDlg::finderProc(HWND hwnd, UINT message, WPARAM wP 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(GetWindowLongPtr(hwnd, GWLP_USERDATA)); + auto* hwndCombo = reinterpret_cast(dwRefData); - bool isDropped = ::SendMessage(hwndCombo, CB_GETDROPPEDSTATE, 0, 0) != 0; - - const size_t strSize = FINDREPLACE_MAXLENGTH; - auto draftString = std::make_unique(strSize); - std::fill_n(draftString.get(), strSize, L'\0'); - - if (isDropped && (message == WM_KEYDOWN) && (wParam == VK_DELETE)) + static constexpr size_t strSize = FINDREPLACE_MAXLENGTH; + static auto draftString = []() -> std::unique_ptr { - auto curSel = ::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0); - if (curSel != CB_ERR) + auto ptr = std::make_unique(strSize); + std::fill_n(ptr.get(), strSize, L'\0'); + return ptr; + }(); + + switch (uMsg) + { + case WM_NCDESTROY: { - auto itemsRemaining = ::SendMessage(hwndCombo, CB_DELETESTRING, curSel, 0); - // if we close the dropdown and reopen it, it will be correctly-sized for remaining items - ::SendMessage(hwndCombo, CB_SHOWDROPDOWN, FALSE, 0); - if (itemsRemaining > 0) - { - if (itemsRemaining == curSel) - { - --curSel; - } - ::SendMessage(hwndCombo, CB_SETCURSEL, curSel, 0); - ::SendMessage(hwndCombo, CB_SHOWDROPDOWN, TRUE, 0); - } - return 0; + ::RemoveWindowSubclass(hWnd, FindReplaceDlg::ComboEditProc, uIdSubclass); + draftString.reset(nullptr); + break; } - } - else if (message == WM_CHAR && wParam == 0x7F) // ASCII "DEL" (Ctrl+Backspace) - { - delLeftWordInEdit(hwnd); - 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 - ::SendMessage(hwndCombo, WM_GETTEXT, FINDREPLACE_MAXLENGTH, reinterpret_cast(draftString.get())); - } - else if ((message == WM_KEYDOWN) && (wParam == VK_UP) && (::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0) == CB_ERR)) - { - // up key on unselected combobox item -> no change but select current edit text - ::SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1)); - return 0; - } - else if ((message == WM_KEYDOWN) && (wParam == VK_UP) && (::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0) == 0) && std::wcslen(draftString.get()) > 0) - { - // up key on top selected combobox item -> restore draft to edit text - ::SendMessage(hwndCombo, CB_SETCURSEL, WPARAM(-1), 0); - ::SendMessage(hwndCombo, WM_SETTEXT, 0, reinterpret_cast(draftString.get())); - ::SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1)); - return 0; - } - else if (message == WM_PASTE) - { - // needed to allow CR (i.e., multiline) into combobox text; - // (the default functionality terminates the paste at the first CR character) - - HWND hParent = ::GetParent(hwndCombo); - HWND hFindWhatCombo = ::GetDlgItem(hParent, IDFINDWHAT); - HWND hReplaceWithCombo = ::GetDlgItem(hParent, IDREPLACEWITH); - if ((hwndCombo == hFindWhatCombo) || (hwndCombo == hReplaceWithCombo)) + case WM_KEYDOWN: { - CLIPFORMAT cfColumnSelect = static_cast(::RegisterClipboardFormat(L"MSDEVColumnSelect")); - if (!::IsClipboardFormatAvailable(cfColumnSelect)) + if (wParam != VK_DELETE && wParam != VK_DOWN && wParam != VK_UP) { - wstring clipboardText = strFromClipboard(); - if (!clipboardText.empty()) + break; + } + + auto curSel = ::SendMessage(hwndCombo, CB_GETCURSEL, 0, 0); + switch (wParam) + { + case VK_DELETE: { - HWND hEdit = GetWindow(hwndCombo, GW_CHILD); - if (hEdit) + if (::SendMessage(hwndCombo, CB_GETDROPPEDSTATE, 0, 0) == FALSE) // isNotDropped { - ::SendMessage(hEdit, EM_REPLACESEL, TRUE, (LPARAM)clipboardText.c_str()); + 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 + ::SendMessage(hwndCombo, CB_SHOWDROPDOWN, FALSE, 0); + if (itemsRemaining > 0) + { + if (itemsRemaining == curSel) + { + --curSel; + } + ::SendMessage(hwndCombo, CB_SETCURSEL, curSel, 0); + ::SendMessage(hwndCombo, CB_SHOWDROPDOWN, TRUE, 0); + } + return 0; + } + + case VK_DOWN: + { + if (curSel == CB_ERR) + { + // down key on unselected combobox item -> store current edit text as draft + ::SendMessage(hwndCombo, WM_GETTEXT, WPARAM{ strSize }, reinterpret_cast(draftString.get())); + } + break; + } + + case VK_UP: + { + if (curSel == CB_ERR) + { + // up key on unselected combobox item -> no change but select current edit text + ::SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1)); + return 0; + } + + if ((curSel == 0) && std::wcslen(draftString.get()) > 0) + { + // up key on top selected combobox item -> restore draft to edit text + ::SendMessage(hwndCombo, CB_SETCURSEL, static_cast(-1), 0); + ::SendMessage(hwndCombo, WM_SETTEXT, 0, reinterpret_cast(draftString.get())); + ::SendMessage(hwndCombo, CB_SETEDITSEL, 0, MAKELPARAM(0, -1)); + return 0; + } + 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; + // (the default functionality terminates the paste at the first CR character) + + HWND hParent = ::GetParent(hwndCombo); + HWND hFindWhatCombo = ::GetDlgItem(hParent, IDFINDWHAT); + HWND hReplaceWithCombo = ::GetDlgItem(hParent, IDREPLACEWITH); + if ((hwndCombo == hFindWhatCombo) || (hwndCombo == hReplaceWithCombo)) + { + const auto cfColumnSelect = static_cast(::RegisterClipboardFormatW(L"MSDEVColumnSelect")); + if (::IsClipboardFormatAvailable(cfColumnSelect) == FALSE) + { + const auto clipboardText = std::wstring{ strFromClipboard() }; + if (!clipboardText.empty()) + { + ::SendMessage(hWnd, EM_REPLACESEL, TRUE, reinterpret_cast(clipboardText.c_str())); } } + return 0; } - - return 0; + break; } + + default: + break; } - return CallWindowProc(originalComboEditProc, hwnd, message, wParam, lParam); + return ::DefSubclassProc(hWnd, uMsg, wParam, lParam); } void FindReplaceDlg::hideOrShowCtrl4reduceOrNormalMode(DIALOG_TYPE dlgT) diff --git a/PowerEditor/src/ScintillaComponent/FindReplaceDlg.h b/PowerEditor/src/ScintillaComponent/FindReplaceDlg.h index acd16f193..57b47dc9b 100644 --- a/PowerEditor/src/ScintillaComponent/FindReplaceDlg.h +++ b/PowerEditor/src/ScintillaComponent/FindReplaceDlg.h @@ -112,12 +112,13 @@ friend class FindReplaceDlg; public: Finder() : DockingDlgInterface(IDD_FINDRESULT) { _markingsStruct._length = 0; - _markingsStruct._markings = NULL; + _markingsStruct._markings = nullptr; } ~Finder() override { _scintView.destroy(); } + void init(HINSTANCE hInst, HWND hPere, ScintillaEditView **ppEditView) { DockingDlgInterface::init(hInst, hPere); _ppEditView = ppEditView; @@ -186,6 +187,8 @@ private: std::wstring _prefixLineStr; + using DockingDlgInterface::init; + void setFinderReadOnly(bool isReadOnly) { _scintView.execute(SCI_SETREADONLY, isReadOnly); } @@ -246,8 +249,6 @@ private: void writeOptions(); }; -LRESULT run_swapButtonProc(WNDPROC oldEditProc, HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); - class FindReplaceDlg : public StaticDialog { friend class FindIncrementDlg; @@ -328,7 +329,7 @@ public : void changeTabName(DIALOG_TYPE index, const wchar_t *name2change) { TCITEM tie{}; tie.mask = TCIF_TEXT; - tie.pszText = (wchar_t *)name2change; + tie.pszText = const_cast(name2change); TabCtrl_SetItem(_tab.getHSelf(), index, &tie); wchar_t label[MAX_PATH]{}; @@ -426,9 +427,8 @@ protected : void resizeDialogElements(); intptr_t CALLBACK run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam) override; 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 static LRESULT FAR PASCAL finderProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); @@ -486,6 +486,9 @@ private: ControlInfoTip _maxLenOnSearchTip; + using Window::init; + using StaticDialog::create; + void enableFindDlgItem(int dlgItemID, bool isEnable = true); void showFindDlgItem(int dlgItemID, bool isShow = true); @@ -538,6 +541,7 @@ class FindIncrementDlg : public StaticDialog { public : FindIncrementDlg() = default; + void init(HINSTANCE hInst, HWND hPere, FindReplaceDlg *pFRDlg, bool isRTL = false); void destroy() override; void display(bool toShow = true) const override; @@ -561,6 +565,8 @@ private : ReBar* _pRebar = nullptr; REBARBANDINFO _rbBand{}; + using Window::init; + intptr_t CALLBACK run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam) override; void markSelectedTextInc(bool enable, FindOption *opt = NULL); };