mirror of
https://github.com/notepad-plus-plus/notepad-plus-plus.git
synced 2025-07-24 22:34:54 +02:00
Fix Windows dialog file list not react with keystroke (character match)
Fix #2239, close #16572
This commit is contained in:
parent
b742c540bb
commit
d3e846162a
@ -42,6 +42,9 @@ using namespace std;
|
|||||||
#define WD_MENUCOPYNAME "MenuCopyName"
|
#define WD_MENUCOPYNAME "MenuCopyName"
|
||||||
#define WD_MENUCOPYPATH "MenuCopyPath"
|
#define WD_MENUCOPYPATH "MenuCopyPath"
|
||||||
|
|
||||||
|
// WM_CHAR replacement
|
||||||
|
#define WM_CHAR_REPLACEMENT (WM_USER + WM_CHAR)
|
||||||
|
|
||||||
static const wchar_t *readonlyString = L" [Read Only]";
|
static const wchar_t *readonlyString = L" [Read Only]";
|
||||||
const UINT WDN_NOTIFY = RegisterWindowMessage(L"WDN_NOTIFY");
|
const UINT WDN_NOTIFY = RegisterWindowMessage(L"WDN_NOTIFY");
|
||||||
/*
|
/*
|
||||||
@ -240,6 +243,8 @@ BEGIN_WINDOW_MAP(WindowsDlgMap)
|
|||||||
END_WINDOW_MAP()
|
END_WINDOW_MAP()
|
||||||
|
|
||||||
RECT WindowsDlg::_lastKnownLocation;
|
RECT WindowsDlg::_lastKnownLocation;
|
||||||
|
HHOOK WindowsDlg::_hMsgHook = nullptr;
|
||||||
|
HWND WindowsDlg::_hThisDlg = nullptr;
|
||||||
|
|
||||||
WindowsDlg::WindowsDlg() : MyBaseClass(WindowsDlgMap)
|
WindowsDlg::WindowsDlg() : MyBaseClass(WindowsDlgMap)
|
||||||
{
|
{
|
||||||
@ -258,6 +263,26 @@ void WindowsDlg::init(HINSTANCE hInst, HWND parent)
|
|||||||
_pTab = NULL;
|
_pTab = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK WindowsDlg::getMsgProc(int code, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
if (code >= 0 && wParam == PM_REMOVE)
|
||||||
|
{
|
||||||
|
MSG* pMsg = reinterpret_cast<MSG*>(lParam);
|
||||||
|
if (pMsg->message == WM_CHAR)
|
||||||
|
{
|
||||||
|
// forward a WM_CHAR replacement message to the dialog
|
||||||
|
::PostMessage(_hThisDlg, WM_CHAR_REPLACEMENT, pMsg->wParam, pMsg->lParam);
|
||||||
|
|
||||||
|
// suppress the original one
|
||||||
|
// to prevent it from reaching the list view
|
||||||
|
pMsg->message = WM_NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CallNextHookEx(_hMsgHook, code, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
intptr_t CALLBACK WindowsDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam)
|
intptr_t CALLBACK WindowsDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
switch (message)
|
switch (message)
|
||||||
@ -270,6 +295,10 @@ intptr_t CALLBACK WindowsDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM lP
|
|||||||
NppDarkMode::autoSubclassAndThemeChildControls(_hSelf);
|
NppDarkMode::autoSubclassAndThemeChildControls(_hSelf);
|
||||||
NppDarkMode::autoSubclassAndThemeWindowNotify(_hSelf);
|
NppDarkMode::autoSubclassAndThemeWindowNotify(_hSelf);
|
||||||
|
|
||||||
|
// install message hook to intercept WM_CHAR
|
||||||
|
_hThisDlg = _hSelf;
|
||||||
|
initMessageHook();
|
||||||
|
|
||||||
return MyBaseClass::run_dlgProc(message, wParam, lParam);
|
return MyBaseClass::run_dlgProc(message, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,6 +389,9 @@ intptr_t CALLBACK WindowsDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM lP
|
|||||||
|
|
||||||
case WM_DESTROY:
|
case WM_DESTROY:
|
||||||
{
|
{
|
||||||
|
// uninstall WM_CHAR message hook
|
||||||
|
removeMessageHook();
|
||||||
|
|
||||||
//destroy();
|
//destroy();
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@ -484,6 +516,102 @@ intptr_t CALLBACK WindowsDlg::run_dlgProc(UINT message, WPARAM wParam, LPARAM lP
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case WM_CHAR_REPLACEMENT:
|
||||||
|
{
|
||||||
|
if (!_hList)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
wchar_t ch = static_cast<wchar_t>(wParam);
|
||||||
|
int itemCount = ListView_GetItemCount(_hList);
|
||||||
|
|
||||||
|
// backup current state
|
||||||
|
vector<int> stateMap;
|
||||||
|
stateMap.reserve(itemCount);
|
||||||
|
for (int index = 0; index < itemCount; index++)
|
||||||
|
stateMap.push_back(ListView_GetItemState(_hList, index, LVIS_SELECTED | LVIS_FOCUSED));
|
||||||
|
|
||||||
|
// temporarily clear existing selection
|
||||||
|
ListView_SetItemState(_hList, -1, 0, LVIS_SELECTED | LVIS_FOCUSED);
|
||||||
|
|
||||||
|
LVITEM lvItem {};
|
||||||
|
wchar_t buffer[MAX_PATH] = L"\0";
|
||||||
|
|
||||||
|
int firstMatchFound = -1;
|
||||||
|
int lastMatchSel = -1;
|
||||||
|
int targetedIndex = -1;
|
||||||
|
|
||||||
|
// find items whose first character matches the typed character
|
||||||
|
for (int index = 0; index < itemCount; ++index)
|
||||||
|
{
|
||||||
|
lvItem.iItem = index;
|
||||||
|
lvItem.mask = LVIF_TEXT;
|
||||||
|
lvItem.pszText = buffer;
|
||||||
|
lvItem.cchTextMax = MAX_PATH;
|
||||||
|
|
||||||
|
if (ListView_GetItem(_hList, &lvItem))
|
||||||
|
{
|
||||||
|
if (towlower(buffer[0]) == towlower(ch))
|
||||||
|
{
|
||||||
|
// mark the first matching item index found
|
||||||
|
if (firstMatchFound == -1)
|
||||||
|
firstMatchFound = index;
|
||||||
|
|
||||||
|
// if the item is currenly selected, skip and find the next item
|
||||||
|
if (stateMap.at(index) & LVIS_SELECTED)
|
||||||
|
{
|
||||||
|
lastMatchSel = index;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop at the next matching item
|
||||||
|
// after the last selected matching one
|
||||||
|
if (lastMatchSel != -1)
|
||||||
|
{
|
||||||
|
targetedIndex = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// targeted item is found
|
||||||
|
if (targetedIndex != -1)
|
||||||
|
{
|
||||||
|
ListView_SetItemState(_hList, targetedIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
|
||||||
|
ListView_EnsureVisible(_hList, targetedIndex, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, that means either of these cases:
|
||||||
|
// 1. no matching item is currently selected
|
||||||
|
// 2. the last matching item is currently selected
|
||||||
|
// 3. all matching items are currently selected
|
||||||
|
// 4. no matching item is found
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// case 1 -> 3: select the first matching item
|
||||||
|
if (firstMatchFound != -1)
|
||||||
|
{
|
||||||
|
ListView_SetItemState(_hList, firstMatchFound, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
|
||||||
|
ListView_EnsureVisible(_hList, firstMatchFound, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// case 4:
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// restore previous state
|
||||||
|
for (int index = 0; index < itemCount; index++)
|
||||||
|
ListView_SetItemState(_hList, index, stateMap.at(index), LVIS_SELECTED | LVIS_FOCUSED);
|
||||||
|
|
||||||
|
// make a beep sound
|
||||||
|
const NppGUI& nppGUI = NppParameters::getInstance().getNppGUI();
|
||||||
|
if (!nppGUI._muteSounds)
|
||||||
|
MessageBeep(0xFFFFFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
case WM_CONTEXTMENU:
|
case WM_CONTEXTMENU:
|
||||||
{
|
{
|
||||||
if (!_listMenu.isCreated())
|
if (!_listMenu.isCreated())
|
||||||
|
@ -67,6 +67,22 @@ public :
|
|||||||
void sortFileSizeDSC();
|
void sortFileSizeDSC();
|
||||||
void doRefresh(bool invalidate = false);
|
void doRefresh(bool invalidate = false);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// for message hook
|
||||||
|
static HHOOK _hMsgHook;
|
||||||
|
static HWND _hThisDlg;
|
||||||
|
|
||||||
|
static LRESULT CALLBACK getMsgProc(int code, WPARAM wParam, LPARAM lParam);
|
||||||
|
void initMessageHook() {
|
||||||
|
_hMsgHook = SetWindowsHookEx(WH_GETMESSAGE, getMsgProc, nullptr, GetCurrentThreadId());
|
||||||
|
}
|
||||||
|
void removeMessageHook() {
|
||||||
|
if (_hMsgHook) {
|
||||||
|
UnhookWindowsHookEx(_hMsgHook);
|
||||||
|
_hMsgHook = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected :
|
protected :
|
||||||
intptr_t CALLBACK run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam) override;
|
intptr_t CALLBACK run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam) override;
|
||||||
BOOL onInitDialog() override;
|
BOOL onInitDialog() override;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user