Keep Files commands focused during pane navigation

This commit is contained in:
agrynco committed 2026-11-15 10:29:35 +02:00
1 parent 983e6f7b1a
commit b5c803df87
4 files changed
+279 -75

No files matched your search

+23 -10
View File
@@ -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.
+4 -1
View File
@@ -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;
+2 -1
View File
@@ -11,7 +11,8 @@ bool validHistoryKeys(const std::array<WORD,2> &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;
});
+250 -63
View File
@@ -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<COLORREF,ColorCount> 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<HBRUSH>(GetStockObject(DC_BRUSH)));
}
void placePanelSearch(HWND window)
{
RECT panel{};
GetWindowRect(panels[active].list,&panel);
MapWindowPoints(nullptr,window,reinterpret_cast<POINT *>(&panel),2);
const auto frame=dialogFrameMetrics(GetDpiForWindow(window));
const int width=std::min(static_cast<int>(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<int>(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<int>(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<size_t>(row)<panel.rows.size()?row:LB_ERR;
}
std::wstring powerShellPath(const fs::path &path)
{
std::wstring quoted=L"'";
for(const auto character:path.native())
{
// PowerShell recognizes typographic apostrophes as quote delimiters too.
if(character==L'\'' || character==L'\u2018' || character==L'\u2019') quoted+=character;
quoted+=character;
}
return quoted+L"'";
}
void insertCurrentPanelPath()
{
const auto &panel=panels[active];
const auto row=currentPanelRow(panel);
if(row==LB_ERR) return;
const auto &path=panel.rows[row].native();
std::wstring quoted;
if(path.find(L' ')!=std::wstring::npos)
{
quoted=L"\"";
for(const auto character:path)
{
if(character==L'`' || character==L'$' || character==L'"' ||
character==L'\u201c' || character==L'\u201d') quoted+=L'`';
quoted+=character;
}
quoted+=L'"';
}
SetFocus(terminalInput);
SendMessageW(terminalInput,EM_REPLACESEL,TRUE,
reinterpret_cast<LPARAM>(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<int>(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<WPARAM>(&first),reinterpret_cast<LPARAM>(&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<size_t>(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<int>(&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<int>(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<int>(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<int>(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<size_t>(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<wchar_t>(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<int>(panelSearchPrefix.size());
for(size_t row=0;length && row<panel.rows.size();++row)
{
if(panel.sizes[row]==L"Up") continue;
const auto name=panel.rows[row].filename().native();
if(name.size()<panelSearchPrefix.size() ||
CompareStringOrdinal(name.data(),length,panelSearchPrefix.data(),length,TRUE)!=CSTR_EQUAL)
continue;
SendMessageW(panel.list,LB_SETCURSEL,row,0);
InvalidateRect(panel.list,nullptr,FALSE);
showFileDetails(panel);
if(!IsWindowVisible(panelSearchPopup))
{
placePanelSearch(window);
ShowWindow(panelSearchPopup,SW_SHOWNA);
}
break;
}
if(IsWindowVisible(panelSearchPopup)) InvalidateRect(panelSearchPopup,nullptr,FALSE);
else panelSearchPrefix.clear();
}
bool handlePanelSearchKey(HWND window,MSG &message)
{
const bool searching=IsWindowVisible(panelSearchPopup);
if(message.message==WM_LBUTTONDOWN || message.message==WM_RBUTTONDOWN ||
message.message==WM_NCLBUTTONDOWN || message.message==WM_MOUSEWHEEL)
{
if(searching && message.hwnd!=panelSearchPopup) closePanelSearch();
return false;
}
const bool input=message.hwnd==terminalInput || message.hwnd==panels[0].list ||
message.hwnd==panels[1].list;
if(!input || terminalVisible || IsWindowVisible(menuBar) || IsWindowVisible(picker)) return false;
const bool modified=(GetKeyState(VK_CONTROL)&0x8000) || (GetKeyState(VK_LWIN)&0x8000) ||
(GetKeyState(VK_RWIN)&0x8000);
const bool alt=(message.lParam & (1LL<<29))!=0;
if((message.message==WM_SYSCHAR && alt) || (searching && message.message==WM_CHAR))
{
if(!modified && (message.wParam==L'\b' ||
(message.wParam>=32 && message.wParam!=127 && (!alt || message.wParam!=L' '))))
{
searchPanelByPrefix(window,static_cast<wchar_t>(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<UINT>(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<UINT>(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<HMENU>(144), instance, nullptr);
panelSearchPopup=CreateWindowW(L"STATIC",L"Search",WS_CHILD|WS_CLIPSIBLINGS|SS_OWNERDRAW|SS_NOTIFY,
0,0,0,0,window,reinterpret_cast<HMENU>(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<WPARAM>(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<int>(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<const DRAWITEMSTRUCT *>(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<size_t>(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<int>(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);