From b5c803df875fba2f9e3e050a9963a72ecf2b6538 Mon Sep 17 00:00:00 2001 From: Anatolii Hrynchuk Date: Sun, 15 Nov 2026 10:29:35 +0200 Subject: [PATCH] Keep Files commands focused during pane navigation --- FILES-UI.md | 33 ++-- resources/terminal/terminal.js | 5 +- src/files_command_history.inl | 3 +- src/files_main.cpp | 313 ++++++++++++++++++++++++++------- 4 files changed, 279 insertions(+), 75 deletions(-) diff --git a/FILES-UI.md b/FILES-UI.md index 559783d..492f67d 100644 --- a/FILES-UI.md +++ b/FILES-UI.md @@ -10,14 +10,22 @@ Far Manager є зразком для оформлення файлового м термінала, а їхній вивід з’являється під панелями. Щоб відкрити файл із панелі LAN, спочатку скопіюй його до локальної папки. -- У командному рядку Ctrl+V і Shift+Insert вставляють текст із буфера обміну - в позицію курсора або замінюють виділення. Якщо фокус на файловій панелі, - ці клавіші переводять його до командного рядка й вставляють текст там. - Enter запускає команду. Escape очищає поле й залишає фокус у командному рядку. +- У головному вікні клавіатурний фокус залишається в командному рядку. + Tab перемикає активну панель, клік вибирає рядок, а стрілки вгору й вниз, + Page Up і Page Down пересувають курсор у панелі навіть під час набору команди. + Меню й діалоги тимчасово приймають фокус. Ctrl+V і Shift+Insert вставляють + текст із буфера обміну в позицію курсора або замінюють виділення. + Enter запускає введену команду або відкриває поточний елемент, якщо рядок + порожній. Escape очищає рядок. Промпт і текст команди вирівняні по спільній базовій лінії. -- Ctrl+E підставляє останню команду та переводить фокус із файлової панелі - до командного рядка. Повторне натискання +- Ctrl+F вставляє повний шлях елемента під курсором активної панелі в позицію + курсора командного рядка або замінює виділення. Подвійні лапки додаються + лише до шляхів із пробілами. Для `..` вставляється шлях батьківської папки. +- Ctrl+C копіює весь введений у командний рядок текст, навіть коли виділено + лише його частину. + +- Ctrl+E підставляє останню команду. Повторне натискання підставляє старішу, доки не досягне початку історії. Alt+F8 відкриває історію з групуванням за датами. Enter або подвійний клік у списку підставляє вибрану команду в поле. Для запуску натисни Enter у самому полі. @@ -47,7 +55,12 @@ Far Manager є зразком для оформлення файлового м Висота списку залежить від кількості результатів. Коли місця бракує, прокручується список. -- Короткий Alt перемикає головне меню Files при відпусканні. F9 також перемикає меню. Після закриття фокус повертається до активної панелі. Alt з літерою верхнього рівня спрацьовує одразу при натисканні літери. Alt+O відкриває Options, а відпускання Alt після комбінації не перемикає меню. +- Alt із символом шукає початок назви файлу чи папки. Після першого збігу + внизу активної панелі відкривається компактне поле Search у подвійній рамці. + Наступні символи можна набирати без Alt. Пошук враховує поточну розкладку + та ігнорує регістр. Backspace прибирає останній символ, Escape закриває пошук, + а Enter відкриває поточний елемент. +- F9 перемикає головне меню Files. Після закриття фокус повертається в командний рядок. Коли меню відкрите, Alt з літерою верхнього рівня відкриває відповідний пункт. - Коли головне меню відкрите, L, F, C, O та R відкривають Left, Files, Commands, Options та Right. У підменю підсвічена літера викликає відповідну команду. Літери унікальні в межах поточного рівня. Усталені мнемоніки мають пріоритет, зокрема X для Exit. - Глобальне скорочення **Open KSwitcher Files** можна змінити або очистити в налаштуваннях. Для профілю без цього поля пропонується Ctrl+Alt+F, якщо Windows реєструє комбінацію і вона вільна серед прив’язок KSwitcher. - Глобальне скорочення повертає до відкритого вікна Files у поточній сесії Windows або запускає Files, якщо такого вікна немає. Пункт **Files** у треї відкриває нове вікно. @@ -60,7 +73,7 @@ Far Manager є зразком для оформлення файлового м - Options у головному меню одразу відкриває стандартний діалог Windows. Ліворуч є пошук за назвою категорії чи параметра й дерево результатів. Вибір дочірнього вузла відкриває лише його редактор праворуч. Пошук за назвою окремої властивості відкриває вузол, що її містить. Перемикання вузлів зберігає незастосовані зміни. - File panel modes має вузли Panel layout, Wide panel columns і Status line columns. У Colors кожен колір має власний вузол. В Appearance шрифт редагується у вузлі Font. Діалог змінює розмір від розміру, який вміщує всі параметри. Ширина лівої навігації стала, дерево росте по висоті, а права область використовує додатковий простір. OK, Cancel та Apply лишаються внизу праворуч. - Reset selected to defaults скидає вибраний рівень дерева. Корінь All settings скидає всі параметри, навіть коли пошук приховує частину дерева. У редакторах із кількома параметрами Reset property скидає лише вибрану властивість. Скидання змінює чернетку до Apply або OK. Стандартний Panel background — `#000080`. -- Apply доступна, коли чернетка відрізняється від застосованих налаштувань. Після Apply або повернення всіх значень до застосованих вона вимикається. Apply одразу зберігає зміни й перемальовує панелі за відкритим діалогом. OK робить те саме й закриває діалог. Cancel і Escape відкидають зміни після останнього Apply та повертають фокус до панелі. +- Apply доступна, коли чернетка відрізняється від застосованих налаштувань. Після Apply або повернення всіх значень до застосованих вона вимикається. Apply одразу зберігає зміни й перемальовує панелі за відкритим діалогом. OK робить те саме й закриває діалог. Cancel і Escape відкидають зміни після останнього Apply та повертають фокус до командного рядка. - Colors → File type styles налаштовує текст, фон, підкреслення й закреслення для правил за розширенням і групи System and hidden. Розширення можна вибрати зі списку Windows або додати вручну з крапкою чи без неї. Вони зберігаються в KSwitcher, незалежно від асоціацій Windows. Курсор, позначки й результат передачі зберігають свої сигнальні кольори. Кольори правила можна перевизначити окремо для звичайного рядка, позначки Insert, курсора у фокусі та курсора без фокуса. Позначка має пріоритет над курсором, а підсвічування передачі має пріоритет над кольорами курсора. Без перевизначення стан зберігає звичайний вигляд панелі. Мініпанель унизу редактора одразу показує всі чотири стани на синтетичних рядках до Apply. @@ -110,7 +123,7 @@ Far Manager є зразком для оформлення файлового м - У широкому режимі ширина під вирівняні розширення залежить від найдовшого розширення в поточній панелі. За вузької панелі залишай місце для імені та колонки Size. -- Бірюзовий курсор поточного рядка видно лише в активній панелі, зокрема коли клавіатурний фокус переходить у командний рядок. Кожна панель зберігає свою позицію курсора. Коли панелі видимі, F5 і F6 з командного рядка діють на активну панель. +- Бірюзовий курсор поточного рядка видно в активній панелі. Кожна панель зберігає свою позицію курсора. Коли панелі видимі, F5 і F6 з командного рядка діють на активну панель. - Клік у панелі нижче списку ставить курсор на останній рядок, а клік вище списку — на перший. - Поточний шлях відображається по центру верхньої подвійної лінії панелі. Лінію видно з обох боків напису. Бірюзове тло активної панелі охоплює лише напис шляху з невеликими відступами. Довгий шлях скорочується в межах панелі. @@ -162,7 +175,7 @@ Far Manager є зразком для оформлення файлового м ## Видалення - F8 показує сіре підтвердження з дією Move. Shift+Delete і підтвердження непорожньої папки мають темно-червоне тло. Вікно прогресу видалення має сіре тло, як у Copy. -- Escape у початковому підтвердженні повертає фокус до панелі, з якої викликано видалення. Поточний рядок зберігається, а стрілки одразу працюють. +- Escape у початковому підтвердженні повертає фокус до командного рядка. Поточний рядок активної панелі зберігається, а стрілки одразу працюють. - F8 переміщує підтверджені елементи в кошик Windows. Shift+Delete видаляє підтверджене дерево від вкладених елементів до батьківської папки. Прогрес і підсумок окремо показують успішні, пропущені й невдалі дії. - Cancel і Esc у вікні прогресу відкривають спільне червоне підтвердження скасування. Yes зупиняє операцію, а No або Esc у підтвердженні повертає до прогресу. Початковий фокус стоїть на No. diff --git a/resources/terminal/terminal.js b/resources/terminal/terminal.js index 67f3564..7b54141 100644 --- a/resources/terminal/terminal.js +++ b/resources/terminal/terminal.js @@ -51,10 +51,13 @@ function anchorOutput() { trace(2); } terminal.attachCustomKeyEventHandler(event => { - if (event.type !== 'keydown' || !event.ctrlKey || event.altKey || event.shiftKey) return true; + if (event.type !== 'keydown' || !event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) return true; if (event.code === 'KeyO') bridge.postMessage('toggle'); else if (event.code === 'ArrowUp') bridge.postMessage('grow'); else if (event.code === 'ArrowDown') bridge.postMessage('shrink'); + else if (event.code === 'KeyF' || event.code === 'KeyC') { + if (!event.repeat) bridge.postMessage(event.code === 'KeyF' ? 'insert-path' : 'copy-command-line'); + } else return true; event.preventDefault(); return false; diff --git a/src/files_command_history.inl b/src/files_command_history.inl index 6cb095b..3800159 100644 --- a/src/files_command_history.inl +++ b/src/files_command_history.inl @@ -11,7 +11,8 @@ bool validHistoryKeys(const std::array &keys) const bool letter=key>='A' && key<='Z'; const bool function=key>=VK_F1 && key<=VK_F12; if(!letter && !function) return false; - if(modifiers==HOTKEYF_CONTROL) return (letter && key!='C' && key!='V' && key!='X' && key!='A' && key!='Z' && key!='Y') || function; + if(modifiers==HOTKEYF_CONTROL) + return (letter && key!='C' && key!='F' && key!='V' && key!='X' && key!='A' && key!='Z' && key!='Y') || function; if(modifiers==HOTKEYF_ALT) return function && key!=VK_F1 && key!=VK_F2 && key!=VK_F4 && key!=VK_F10; return false; }); diff --git a/src/files_main.cpp b/src/files_main.cpp index 7407eee..5ae60da 100644 --- a/src/files_main.cpp +++ b/src/files_main.cpp @@ -22,6 +22,7 @@ #include "files_terminal_view.h" #include "files_lan_ipc.h" #include "lan_files.h" +#include "platform.h" namespace { @@ -336,7 +337,8 @@ bool refreshDriveAddresses{}; ULONGLONG driveOpened{}; int drivePickerWidth{}; int pickerPanel{}; -bool altAlone{}; +std::wstring panelSearchPrefix; +HWND panelSearchPopup{}; enum Color { PanelBlue, Text, Frame, Dialog, Selection, Footer, PanelCursor, ColorCount }; constexpr std::array defaultColors{RGB(0,0,128), RGB(230,255,255), RGB(0,255,255), RGB(0,128,128), RGB(0,0,0), RGB(0,128,128), RGB(0,128,128)}; @@ -738,6 +740,24 @@ void paintScrollPosition(HWND parent, HDC dc, HWND list, COLORREF background) FillRect(dc, &track, static_cast(GetStockObject(DC_BRUSH))); } +void placePanelSearch(HWND window) +{ + RECT panel{}; + GetWindowRect(panels[active].list,&panel); + MapWindowPoints(nullptr,window,reinterpret_cast(&panel),2); + const auto frame=dialogFrameMetrics(GetDpiForWindow(window)); + const int width=std::min(static_cast(panel.right-panel.left),24*cellWidth+2*frame.content()); + const int height=textHeight+2*frame.content(); + SetWindowPos(panelSearchPopup,HWND_TOP,(panel.left+panel.right-width)/2, + std::max(panel.top,panel.bottom+textHeight-height),width,height,SWP_NOACTIVATE); +} + +void closePanelSearch() +{ + panelSearchPrefix.clear(); + if(IsWindowVisible(panelSearchPopup)) ShowWindow(panelSearchPopup,SW_HIDE); +} + void layout(HWND window) { RECT rect{}; @@ -804,6 +824,7 @@ void layout(HWND window) const int count=static_cast(std::size(commands)); place(commands[i],i*width/count,height-textHeight,(i+1)*width/count-i*width/count,textHeight); } + if(IsWindowVisible(panelSearchPopup)) placePanelSearch(window); RedrawWindow(window,nullptr,nullptr,RDW_INVALIDATE|RDW_ERASE|RDW_ALLCHILDREN); } @@ -823,6 +844,76 @@ void showTerminal(HWND owner,bool show) void open(Panel &panel,const fs::path &directory,bool offerElevation); +void activatePanel(int index) +{ + const auto previous=active; + active=index; + showFileDetails(panels[index]); + if(previous==active) return; + for(const auto &panel:panels) + { + InvalidateRect(panel.path,nullptr,FALSE); + InvalidateRect(panel.list,nullptr,FALSE); + } + layout(GetParent(panels[index].list)); +} + +std::wstring terminalInputText() +{ + std::wstring value(GetWindowTextLengthW(terminalInput)+1,L'\0'); + value.resize(GetWindowTextW(terminalInput,value.data(),static_cast(value.size()))); + return value; +} + +void copyTerminalInput(HWND owner) +{ + const auto value=terminalInputText(); + if(value.empty()) return; + try { ks::writeClipboard(owner,ks::textClip(value)); } + catch(const std::exception &) { setStatus(L"Could not copy the command line."); } +} + +LRESULT currentPanelRow(const Panel &panel) +{ + const auto row=SendMessageW(panel.list,LB_GETCURSEL,0,0); + return row>=0 && static_cast(row)(quoted.empty()?path.c_str():quoted.c_str())); +} + bool startTerminal(HWND owner) { if(terminal.alive()) return true; @@ -865,6 +956,8 @@ bool startTerminal(HWND owner) { if(GetForegroundWindow()==owner) SetFocus(terminalInput); } + else if(message==L"insert-path") insertCurrentPanelPath(); + else if(message==L"copy-command-line") SendMessageW(terminalInput,WM_COPY,0,0); else if(message==L"toggle") showTerminal(owner,!terminalVisible); else if(!terminalVisible && (message==L"grow" || message==L"shrink")) { @@ -928,6 +1021,12 @@ bool submitTerminalCommand(const Panel &panel,const std::wstring &value) LRESULT CALLBACK terminalInputKeys(HWND input,UINT message,WPARAM wp,LPARAM lp,UINT_PTR,DWORD_PTR) { + if(message==WM_COPY || (message==WM_CHAR && wp==3)) + { + copyTerminalInput(GetParent(input)); + return 0; + } + if(message==WM_CHAR && wp==VK_TAB && !terminalVisible) return 0; if(message==WM_CHAR && wp==VK_ESCAPE) { recallCommand(terminalHistory.size()); @@ -962,31 +1061,24 @@ LRESULT CALLBACK terminalInputKeys(HWND input,UINT message,WPARAM wp,LPARAM lp,U SendMessageW(panels[active].list,WM_KEYDOWN,VK_RETURN,lp); return 0; } - std::wstring value(GetWindowTextLengthW(input)+1,L'\0'); - value.resize(GetWindowTextW(input,value.data(),static_cast(value.size()))); + const auto value=terminalInputText(); if(!submitTerminalCommand(panels[active],value)) return 0; if(value.find_first_not_of(L" \t\r\n")!=std::wstring::npos) rememberCommand(value); terminalHistoryIndex=terminalHistory.size(); SetWindowTextW(input,L""); return 0; } - if(message==WM_CHAR && wp==3 && terminal.alive()) + if(message==WM_KEYDOWN && !terminalVisible && wp==VK_TAB) { - DWORD first{},last{}; - SendMessageW(input,EM_GETSEL,reinterpret_cast(&first),reinterpret_cast(&last)); - if(first==last) - { - terminal.write(std::wstring(1,L'\x03')); - return 0; - } + activatePanel(1-active); + return 0; } if(message==WM_KEYDOWN && !terminalVisible && - (wp==VK_TAB || (wp>=VK_F1 && wp<=VK_F12) || + ((wp>=VK_F1 && wp<=VK_F12) || wp==VK_UP || wp==VK_DOWN || wp==VK_PRIOR || wp==VK_NEXT || (!GetWindowTextLengthW(input) && - ((wp>=VK_PRIOR && wp<=VK_DOWN) || wp==VK_INSERT || wp==VK_DELETE)))) + (wp==VK_HOME || wp==VK_END || wp==VK_LEFT || wp==VK_RIGHT || wp==VK_INSERT || wp==VK_DELETE)))) { SendMessageW(panels[active].list,WM_KEYDOWN,wp,lp); - if(IsWindow(input)) SetFocus(input); return 0; } if(message==WM_KEYDOWN && (wp==VK_UP || wp==VK_DOWN)) @@ -1586,14 +1678,7 @@ void openPanelFile(const Panel &panel, const fs::path &file, const wchar_t *verb }; if(!verb && (isExtension(L".bat") || isExtension(L".cmd"))) { - std::wstring command=L"& '"; - for(const auto character:file.native()) - { - // PowerShell recognizes typographic apostrophes as quote delimiters too. - if(character==L'\'' || character==L'\u2018' || character==L'\u2019') command+=character; - command+=character; - } - command+=L"'"; + const auto command=L"& "+powerShellPath(file); if(submitTerminalCommand(panel,command) && terminalRows<5) { terminalRows=5; @@ -1609,8 +1694,8 @@ void openPanelFile(const Panel &panel, const fs::path &file, const wchar_t *verb void enter(Panel &panel) { - auto row = SendMessageW(panel.list, LB_GETCURSEL, 0, 0); - if (row >= 0 && static_cast(row) < panel.rows.size()) + const auto row=currentPanelRow(panel); + if(row!=LB_ERR) { auto target = panel.rows[row]; if(panel.sizes[row]==L"Folder" || panel.sizes[row]==L"Up") open(panel,target,true); @@ -3207,7 +3292,7 @@ void deleteSelection(HWND owner,Panel &panel,bool permanent) void selectPanelEdge(Panel &panel,bool first) { - SetFocus(panel.list); + activatePanel(static_cast(&panel-panels)); const auto count=SendMessageW(panel.list,LB_GETCOUNT,0,0); if(count<=0) return; SendMessageW(panel.list,LB_SETCURSEL,first?0:count-1,0); @@ -3256,19 +3341,7 @@ LRESULT CALLBACK panelKeys(HWND window, UINT message, WPARAM wp, LPARAM lp, UINT } if (message == WM_KEYDOWN && (wp == VK_LEFT || wp == VK_RIGHT)) return scrollList(window, message, wp == VK_LEFT ? VK_HOME : VK_END, lp); - if (message == WM_SETFOCUS) - { - const auto previous=active; - active = static_cast(index); - if(previous!=active) - for(auto &panel:panels) - { - InvalidateRect(panel.path,nullptr,FALSE); - InvalidateRect(panel.list,nullptr,FALSE); - } - showFileDetails(panels[index]); - if(previous!=active) layout(GetParent(window)); - } + if(message==WM_SETFOCUS) activatePanel(static_cast(index)); if(message==WM_CHAR && wp>=32 && wp!=127) { SetFocus(terminalInput); @@ -3276,15 +3349,15 @@ LRESULT CALLBACK panelKeys(HWND window, UINT message, WPARAM wp, LPARAM lp, UINT } if (message == WM_KEYDOWN) { - if (wp == VK_TAB) { SetFocus(panels[1 - index].list); return 0; } + if(wp==VK_TAB) { activatePanel(static_cast(1-index)); SetFocus(terminalInput); return 0; } if (wp == VK_RETURN) { enter(panels[index]); return 0; } if (wp == VK_F2) { open(panels[index], panels[index].directory); return 0; } if (wp == VK_F10) { SendMessageW(GetParent(window), WM_CLOSE, 0, 0); return 0; } if (wp == VK_F3 || wp == VK_F4) { const auto &panel = panels[index]; - auto row = SendMessageW(window, LB_GETCURSEL, 0, 0); - if (row < 0 || static_cast(row) >= panel.rows.size()) return 0; + const auto row=currentPanelRow(panel); + if(row==LB_ERR) return 0; const bool edit = wp == VK_F4; openPanelFile(panel, panel.rows[row], edit ? L"edit" : L"open", edit ? L"No editor is registered for this file." : L"Cannot open the selected file."); @@ -3433,27 +3506,19 @@ bool handleFilesMenuKey(HWND window,UINT message,WPARAM key,LPARAM flags,bool mo const bool repeated=(flags & (1LL << 30))!=0; if(message==WM_SYSKEYDOWN && key==VK_MENU) { - if(!repeated) altAlone=!modified; return !modified; } if((message==WM_SYSKEYUP || message==WM_KEYUP) && key==VK_MENU) - { - if(std::exchange(altAlone,false)) - { - toggleFilesMenu(); - return true; - } - return false; - } + return !modified; if(message!=WM_KEYDOWN && message!=WM_SYSKEYDOWN) return false; - altAlone=false; if(modified) return false; if(message==WM_KEYDOWN && key==VK_F9) { if(!repeated) toggleFilesMenu(); return true; } - if(message==WM_SYSKEYDOWN && (flags & (1LL << 29)) && key>=L'A' && key<=L'Z') + if(IsWindowVisible(menuBar) && message==WM_SYSKEYDOWN && + (flags & (1LL << 29)) && key>=L'A' && key<=L'Z') { const auto category=menuAccessKeys.find(static_cast(key)); if(category!=menuAccessKeys.npos) @@ -3465,6 +3530,93 @@ bool handleFilesMenuKey(HWND window,UINT message,WPARAM key,LPARAM flags,bool mo return false; } +void searchPanelByPrefix(HWND window,wchar_t character) +{ + if(character==L'\b') + { + if(!panelSearchPrefix.empty()) panelSearchPrefix.pop_back(); + } + else panelSearchPrefix+=character; + auto &panel=panels[active]; + const auto length=static_cast(panelSearchPrefix.size()); + for(size_t row=0;length && row=32 && message.wParam!=127 && (!alt || message.wParam!=L' ')))) + { + searchPanelByPrefix(window,static_cast(message.wParam)); + return true; + } + return false; + } + if(message.message!=WM_KEYDOWN && message.message!=WM_SYSKEYDOWN) return false; + if(!modified && message.wParam!=VK_SPACE && message.message==WM_SYSKEYDOWN && alt && + (message.wParam==VK_BACK || MapVirtualKeyW(static_cast(message.wParam),MAPVK_VK_TO_CHAR))) + { + TranslateMessage(&message); + return true; + } + if(!searching || message.wParam==VK_MENU || message.wParam==VK_SHIFT) return false; + if(!modified && !alt && (message.wParam==VK_BACK || + MapVirtualKeyW(static_cast(message.wParam),MAPVK_VK_TO_CHAR)) && + message.wParam!=VK_RETURN && message.wParam!=VK_ESCAPE && message.wParam!=VK_TAB) + { + TranslateMessage(&message); + return true; + } + closePanelSearch(); + if(message.wParam==VK_RETURN && !modified && !alt) + { + SendMessageW(panels[active].list,message.message,message.wParam,message.lParam); + return true; + } + return message.wParam==VK_ESCAPE && !modified && !alt; +} + +void restoreCommandFocus() +{ + if(GetFocus()!=terminalInput && !GetCapture() && !copyInProgress && !activeFilesDialog && + !commandHistoryWindow && !IsWindowVisible(menuBar) && !IsWindowVisible(picker)) + SetFocus(terminalInput); +} + LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp) { if(message==filesPreferencesChanged) @@ -3481,8 +3633,11 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp) } switch (message) { + case WM_SETFOCUS: + restoreCommandFocus(); + return 0; case WM_ACTIVATEAPP: - if(!wp) altAlone=false; + if(!wp) closePanelSearch(); break; case WM_COPYDATA: { @@ -3538,6 +3693,8 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp) SendMessageW(picker,LB_SETITEMHEIGHT,0,textHeight); status = CreateWindowW(L"STATIC", L"", WS_CHILD | WS_VISIBLE | SS_PATHELLIPSIS, 0, 0, 0, 0, window, reinterpret_cast(144), instance, nullptr); + panelSearchPopup=CreateWindowW(L"STATIC",L"Search",WS_CHILD|WS_CLIPSIBLINGS|SS_OWNERDRAW|SS_NOTIFY, + 0,0,0,0,window,reinterpret_cast(148),instance,nullptr); terminalInput=CreateWindowW(L"EDIT",L"",WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL, 0,0,0,0,window,nullptr,instance,nullptr); terminalPrompt=CreateWindowW(L"STATIC",L"",WS_CHILD|WS_VISIBLE|SS_PATHELLIPSIS, @@ -3553,7 +3710,7 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp) SetWindowSubclass(terminalInput,terminalInputKeys,1,0); SetTimer(window,terminalTimer,50,nullptr); SetTimer(window,directoryWatchTimer,200,nullptr); - if (!mono || !blue || !teal || !menuBar || !picker || !pickerFrame || !status) return -1; + if (!mono || !blue || !teal || !menuBar || !picker || !pickerFrame || !status || !panelSearchPopup) return -1; SendMessageW(status, WM_SETFONT, reinterpret_cast(mono), FALSE); const wchar_t *labels[]{L"F2 Refresh", L"F3 View", L"F4 Edit", L"F5 Copy", L"F6 Move", L"F10 Exit"}; for (int i = 0; i < static_cast(std::size(commands)); ++i) @@ -3727,6 +3884,21 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp) case WM_DRAWITEM: { const auto &item=*reinterpret_cast(lp); + if(item.hwndItem==panelSearchPopup) + { + const auto frame=dialogFrameMetrics(GetDpiForWindow(window)); + const auto surface=GetSysColorBrush(COLOR_BTNFACE); + const auto border=GetSysColorBrush(COLOR_BTNTEXT); + const auto ink=GetSysColor(COLOR_BTNTEXT); + const auto box=paintDialogFrame(item.hDC,item.rcItem,frame,surface,border); + paintDialogFrameCaptions(item.hDC,item.rcItem,box,L"Search",surface,ink,mono); + auto field=item.rcItem; + InflateRect(&field,-frame.content(),-frame.content()); + const auto cells=static_cast(std::max(0L,field.right-field.left-10)/std::max(1,cellWidth)); + const auto start=panelSearchPrefix.size()>cells?panelSearchPrefix.size()-cells:0; + paintText(item.hDC,field,panelSearchPrefix.c_str()+start,RGB(0,0,0),RGB(0,128,128)); + return TRUE; + } if(item.CtlID==142 || item.CtlID==143) { auto bounds=item.rcItem; @@ -4001,7 +4173,6 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp) if (LOWORD(wp) >= 110 && LOWORD(wp) <= 115 && HIWORD(wp) == BN_CLICKED) { const WPARAM keys[]{VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F10}; - SetFocus(panels[active].list); SendMessageW(panels[active].list, WM_KEYDOWN, keys[LOWORD(wp) - 110], 0); } if (LOWORD(wp) >= 102 && LOWORD(wp) <= 103 && HIWORD(wp) == LBN_DBLCLK) @@ -4111,8 +4282,13 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int show) SetFocus(terminalInput); const auto com=CoInitializeEx(nullptr,COINIT_APARTMENTTHREADED); MSG message{}; - while (GetMessageW(&message, nullptr, 0, 0) > 0) + for(;;) { + if(GetForegroundWindow()==window) restoreCommandFocus(); + if(GetMessageW(&message,nullptr,0,0)<=0) break; + if(message.hwnd==window && message.message>=WM_KEYFIRST && message.message<=WM_KEYLAST && + GetFocus()==terminalInput) + message.hwnd=terminalInput; if(commandHistoryWindow) { if(message.message==WM_KEYDOWN && (message.wParam==VK_RETURN || message.wParam==VK_ESCAPE) && @@ -4128,6 +4304,7 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int show) message.message==WM_CONTEXTMENU; if(ownerClient && clientInput) continue; } + if(!copyInProgress && handlePanelSearchKey(window,message)) continue; if(message.message==WM_KEYDOWN && (message.wParam==VK_UP || message.wParam==VK_DOWN) && (GetKeyState(VK_CONTROL)&0x8000) && !(GetKeyState(VK_MENU)&0x8000) && (message.hwnd==window || IsChild(window,message.hwnd)) && !terminalVisible && !copyInProgress) @@ -4150,8 +4327,9 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int show) if(terminalVisible || startTerminal(window)) showTerminal(window,!terminalVisible); continue; } - if(message.message==WM_KEYDOWN && - (message.hwnd==panels[0].list || message.hwnd==panels[1].list) && + const bool panelFocused=message.hwnd==panels[0].list || message.hwnd==panels[1].list; + const bool mainInput=panelFocused || message.hwnd==terminalInput; + if(message.message==WM_KEYDOWN && mainInput && !terminalVisible && (GetKeyState(VK_CONTROL)&0x8000) && !(GetKeyState(VK_MENU)&0x8000) && !(GetKeyState(VK_LWIN)&0x8000) && !(GetKeyState(VK_RWIN)&0x8000) && message.wParam>='0' && message.wParam<='9' && !copyInProgress && @@ -4161,7 +4339,7 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int show) { hidePicker(); showMenu(false); - auto &panel=panels[message.hwnd==panels[1].list]; + auto &panel=panels[active]; const auto digit=static_cast(message.wParam-'0'); if(GetKeyState(VK_SHIFT)&0x8000) addFolderShortcut(panel,digit); else showFolderShortcuts(panel,digit); @@ -4183,15 +4361,24 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int show) } continue; } - const bool panelFocused=message.hwnd==panels[0].list || message.hwnd==panels[1].list; - if(panelFocused && !copyInProgress && message.message==WM_KEYDOWN && + if(mainInput && + !copyInProgress && message.message==WM_KEYDOWN && !(GetKeyState(VK_MENU)&0x8000) && !(GetKeyState(VK_LWIN)&0x8000) && !(GetKeyState(VK_RWIN)&0x8000)) { const bool ctrl=(GetKeyState(VK_CONTROL)&0x8000)!=0; const bool shift=(GetKeyState(VK_SHIFT)&0x8000)!=0; - if((message.wParam=='V' && ctrl && !shift) || - (message.wParam==VK_INSERT && shift && !ctrl)) + if(ctrl && !shift && (message.wParam=='F' || message.wParam=='C')) + { + if(!(message.lParam & (1LL<<30))) + { + if(message.wParam=='F') insertCurrentPanelPath(); + else SendMessageW(terminalInput,WM_COPY,0,0); + } + continue; + } + if(panelFocused && ((message.wParam=='V' && ctrl && !shift) || + (message.wParam==VK_INSERT && shift && !ctrl))) { SetFocus(terminalInput); SendMessageW(terminalInput,WM_PASTE,0,0);