Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad01951956 | ||
|
|
bf092d2764 |
No files matched your search
@@ -1,5 +1,11 @@
|
||||
# Project instructions
|
||||
|
||||
## Reuse shared code
|
||||
|
||||
Reuse existing shared code wherever possible. When the same behavior,
|
||||
component, or logic is needed in two or more places, extract it into a
|
||||
shared component or function and use it at every applicable call site.
|
||||
|
||||
## Mandatory Wiki updates for every release
|
||||
|
||||
A release is not complete until the published project Wiki reflects that release.
|
||||
|
||||
+8
-2
@@ -8,6 +8,9 @@ Far Manager є зразком для оформлення файлового м
|
||||
- Коли головне меню відкрите, L, F, C, O та R відкривають Left, Files, Commands, Options та Right. У підменю підсвічена літера викликає відповідну команду. Літери унікальні в межах поточного рівня. Усталені мнемоніки мають пріоритет, зокрема X для Exit.
|
||||
- Глобальне скорочення **Open KSwitcher Files** можна змінити або очистити в налаштуваннях. Для профілю без цього поля пропонується Ctrl+Alt+F, якщо Windows реєструє комбінацію і вона вільна серед прив’язок KSwitcher.
|
||||
|
||||
- Ctrl+Shift+0–9 відкриває діалог Title і Path із поточною папкою. OK додає запис без дублювання, Escape скасовує. Title необов’язковий, порожнє значення показує шлях. Записи LAN зберігають ID ПК, а редагування Path залишає запис на тому самому ПК.
|
||||
- Ctrl+0–9 одразу відкриває єдину папку. Для кількох папок відкривається компактний попап Far з одним заголовком Folder shortcuts у подвійній рамці та коротким рядом клавіш у нижній рамці. Ширина залежить від найдовшого запису й обмежується розміром вікна, а висота — кількістю видимих рядків. Нижня рамка показує Ins Del F4 Ctrl+Up Ctrl+Down. Insert додає поточну папку через Title і Path, Delete відв’язує, F4 редагує, Ctrl+Up і Ctrl+Down змінюють і зберігають порядок. Enter відкриває папку. Escape або клік поза попапом закриває його й повертає фокус до панелі. Commands → Folder shortcuts дає вибрати зайняту цифру й відкрити той самий попап навіть для єдиного запису. Brief і Wide доступні в меню Left/Right та Options.
|
||||
|
||||
## Налаштування Files
|
||||
|
||||
- Options у головному меню одразу відкриває стандартний діалог Windows. Ліворуч є пошук за назвою категорії чи параметра й дерево результатів. Вибір дочірнього вузла відкриває лише його редактор праворуч. Пошук за назвою окремої властивості відкриває вузол, що її містить. Перемикання вузлів зберігає незастосовані зміни.
|
||||
@@ -44,10 +47,13 @@ Far Manager є зразком для оформлення файлового м
|
||||
- Enter активує кнопку у фокусі. Для кнопок із власним малюванням використовуй спільний обробник `dialogButtonEnter`. У підтвердженні скасування початковий фокус стоїть на No.
|
||||
|
||||
- При перенесенні вікна між моніторами з різним DPI оновлюй шрифт, висоту рядків панелей і компонування відкритого прогресу копіювання. Перевір перенесення під час активного копіювання.
|
||||
- Для власних діалогів використовуй подвійну рамку з одним заголовком у верхній лінії. Звичайний діалог має сіре тло, попередження — темно-червоне тло та білий текст.
|
||||
- Для власних діалогів і списків Far використовуй спільні метрики та малювання подвійної рамки. Еталон Delete задає 8 px від краю тла до першої лінії та 14 px від внутрішньої лінії до бічної межі вмісту при 96 DPI. Обидва відступи масштабуються за DPI. Вертикальний відступ враховує висоту заголовка та нижніх підказок. Діалоги й списки зберігають свою палітру та поведінку.
|
||||
- Appearance → Dialog frame налаштовує зовнішній і внутрішній відступи від 0 до 24 px при 96 DPI. Row text padding додає від 0 до 4 ширин символу з кожного боку тексту в рядках Folder shortcuts. Inner padding задає відстань від рамки до краю списку. Ширина попапа враховує обидва текстові відступи, а довгі шляхи скорочуються в межах доступної ширини. Живий зразок показує чернетку. Apply зберігає значення, Cancel відкидає незастосовані зміни, Reset selected to defaults повертає 8 px, 14 px і 0 символів. Профілі без цих параметрів використовують ті самі значення.
|
||||
- У ресурсі чи виклику задавай назву кнопки й клавішу доступу. Спільна функція `paintDialogButton` центрує текст, підсвічує фокус і додає фігурні дужки для дії за замовчуванням та квадратні для решти кнопок.
|
||||
- Нижні секції з кнопками у Copy, Warning і вікні прогресу мають однакову висоту, прив’язану до висоти рядка шрифту. Використовуй `layoutDialogButtons` для такої секції та `centerDialogRow` для інших рядків між роздільниками. Межі секції визначають положення рядка.
|
||||
- Поля вводу й випадаючі списки використовують бірюзове тло палітри менеджера. Текст і стрілку вирівнюй вертикально по центру. Прапорці в діалогах операцій використовують позначки `[ ]` та `[x]` і видимий клавіатурний фокус.
|
||||
- Для текстових полів Far використовуй ресурс `FAR_EDIT` і спільну підготовку та палітру діалогів. Поля призначення Copy/Rename і Title/Path використовують нативний Win32 Edit без рамки, моноширинний шрифт і вертикальне центрування. Кольори Text і Popup background діють на всі ці поля, зокрема на поле Copy лише для читання. У відповідних розділах Options → Colors зміни видно на зразку поля. Existing files у Copy є списком без редагування. Поля та редагований список Windows Options мають системне оформлення.
|
||||
- Для позиціонування роздільника використовуй невидимий маркер, якщо межу не задає інший елемент секції. Спільний обробник малює лінію.
|
||||
- Випадаючі списки Far використовують бірюзове тло палітри менеджера. Текст і стрілку вирівнюй вертикально по центру. Прапорці в діалогах операцій використовують позначки `[ ]` та `[x]` і видимий клавіатурний фокус.
|
||||
- Повторно використовуй спільне оформлення. Текст, дії та стан операції передавай із коду конкретної операції. Наприклад, Warning повертає відповідь і стан прапорця, а копіювання вирішує, чи застосувати відповідь до наступних конфліктів.
|
||||
|
||||
## Діалог Copy
|
||||
|
||||
+31
-1
@@ -1,6 +1,9 @@
|
||||
#include <windows.h>
|
||||
#include <commctrl.h>
|
||||
|
||||
#define FAR_EDIT(id, mode, x, y, width, height) \
|
||||
CONTROL "", id, "Edit", ES_AUTOHSCROLL | WS_TABSTOP | mode, x, y, width, height
|
||||
|
||||
1 ICON "kswitcher-files.ico"
|
||||
1 RT_MANIFEST "app.manifest"
|
||||
|
||||
@@ -30,6 +33,12 @@ BEGIN
|
||||
LTEXT "", 280, 222, 50, 155, 12
|
||||
LTEXT "", 281, 131, 30, 254, 14
|
||||
PUSHBUTTON "Change &font…", 282, 131, 52, 82, 15
|
||||
LTEXT "&Outer padding (px at 96 DPI)", 302, 131, 31, 173, 12
|
||||
COMBOBOX 303, 311, 28, 56, 125, CBS_DROPDOWNLIST | WS_TABSTOP | WS_VSCROLL
|
||||
LTEXT "&Inner padding (px at 96 DPI)", 304, 131, 54, 173, 12
|
||||
COMBOBOX 305, 311, 51, 56, 125, CBS_DROPDOWNLIST | WS_TABSTOP | WS_VSCROLL
|
||||
LTEXT "&Row text padding (characters)", 306, 131, 77, 173, 12
|
||||
COMBOBOX 307, 311, 74, 56, 76, CBS_DROPDOWNLIST | WS_TABSTOP | WS_VSCROLL
|
||||
PUSHBUTTON "Reset selected to defaults", 284, 131, 181, 125, 15
|
||||
COMBOBOX 285, 131, 183, 139, 95, CBS_DROPDOWNLIST | WS_TABSTOP | WS_VSCROLL
|
||||
PUSHBUTTON "Reset property", 286, 278, 183, 108, 15
|
||||
@@ -59,7 +68,7 @@ CAPTION "Copy"
|
||||
FONT 10, "Consolas"
|
||||
BEGIN
|
||||
LTEXT "Copy to", 210, 14, 15, 322, 12, SS_PATHELLIPSIS
|
||||
CONTROL "", 211, "Edit", ES_MULTILINE | ES_AUTOHSCROLL | WS_TABSTOP, 14, 29, 322, 18
|
||||
FAR_EDIT(211, ES_MULTILINE, 14, 29, 322, 18)
|
||||
LTEXT "Existing files:", 212, 14, 61, 122, 14, SS_CENTERIMAGE
|
||||
COMBOBOX 213, 140, 61, 196, 100, CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP
|
||||
CONTROL "Also ask on read-only files", 214, "Button", BS_OWNERDRAW | WS_TABSTOP, 14, 81, 322, 14
|
||||
@@ -135,3 +144,24 @@ BEGIN
|
||||
LTEXT "", 248, 14, 73, 1, 1
|
||||
CONTROL "&Cancel", IDCANCEL, "Button", BS_OWNERDRAW | WS_TABSTOP, 157, 74, 86, 18
|
||||
END
|
||||
109 DIALOGEX 0, 0, 250, 85
|
||||
STYLE DS_SETFONT | DS_CENTER | WS_POPUP | WS_CLIPCHILDREN
|
||||
CAPTION "Folder shortcuts"
|
||||
FONT 10, "Consolas"
|
||||
BEGIN
|
||||
CONTROL "", 310, "ListBox", LBS_NOTIFY | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | WS_TABSTOP, 8, 14, 234, 50
|
||||
END
|
||||
|
||||
110 DIALOGEX 0, 0, 370, 116
|
||||
STYLE DS_SETFONT | DS_CENTER | WS_POPUP
|
||||
CAPTION "Folder shortcuts"
|
||||
FONT 10, "Consolas"
|
||||
BEGIN
|
||||
LTEXT "&Title", 316, 16, 18, 338, 12
|
||||
FAR_EDIT(314, ES_LEFT, 16, 31, 338, 15)
|
||||
LTEXT "&Path", 317, 16, 49, 338, 12
|
||||
FAR_EDIT(211, ES_LEFT, 16, 62, 338, 15)
|
||||
LTEXT "", 315, 10, 82, 1, 1, NOT WS_VISIBLE
|
||||
CONTROL "&OK", IDOK, "Button", BS_OWNERDRAW | WS_TABSTOP, 97, 86, 86, 18
|
||||
CONTROL "&Cancel", IDCANCEL, "Button", BS_OWNERDRAW | WS_TABSTOP, 187, 86, 86, 18
|
||||
END
|
||||
@@ -0,0 +1,449 @@
|
||||
struct FolderShortcut
|
||||
{
|
||||
fs::path path;
|
||||
std::string peerId;
|
||||
std::wstring peerName,title;
|
||||
};
|
||||
using FolderShortcuts=std::array<std::vector<FolderShortcut>,10>;
|
||||
FolderShortcuts folderShortcuts;
|
||||
constexpr wchar_t folderShortcutsKey[]=L"FolderShortcuts";
|
||||
|
||||
bool sameFolderShortcut(const FolderShortcut &left,const FolderShortcut &right)
|
||||
{
|
||||
return left.peerId==right.peerId &&
|
||||
CompareStringOrdinal(left.path.c_str(),-1,right.path.c_str(),-1,TRUE)==CSTR_EQUAL;
|
||||
}
|
||||
|
||||
bool saveFolderShortcuts(const FolderShortcuts &shortcuts)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto groups=Json::array();
|
||||
for(const auto &group:shortcuts)
|
||||
{
|
||||
auto rows=Json::array();
|
||||
for(const auto &entry:group)
|
||||
rows.push_back({{"path",ks::utf8(entry.path.native())},{"peer",entry.peerId},
|
||||
{"name",ks::utf8(entry.peerName)},{"title",ks::utf8(entry.title)}});
|
||||
groups.push_back(std::move(rows));
|
||||
}
|
||||
const auto data=groups.dump();
|
||||
return data.size()<=1024*1024 &&
|
||||
RegSetKeyValueW(HKEY_CURRENT_USER,schemeKey,folderShortcutsKey,REG_BINARY,data.data(),
|
||||
static_cast<DWORD>(data.size()))==ERROR_SUCCESS;
|
||||
}
|
||||
catch(const std::exception &){return false;}
|
||||
}
|
||||
|
||||
void loadFolderShortcuts()
|
||||
{
|
||||
DWORD bytes{};
|
||||
if(RegGetValueW(HKEY_CURRENT_USER,schemeKey,folderShortcutsKey,RRF_RT_REG_BINARY,nullptr,nullptr,&bytes)!=ERROR_SUCCESS ||
|
||||
!bytes || bytes>1024*1024) return;
|
||||
std::string data(bytes,'\0');
|
||||
if(RegGetValueW(HKEY_CURRENT_USER,schemeKey,folderShortcutsKey,RRF_RT_REG_BINARY,nullptr,data.data(),&bytes)!=ERROR_SUCCESS)
|
||||
return;
|
||||
try
|
||||
{
|
||||
const auto groups=Json::parse(data.begin(),data.begin()+bytes);
|
||||
if(!groups.is_array() || groups.size()!=folderShortcuts.size()) return;
|
||||
FolderShortcuts loaded;
|
||||
for(size_t digit=0;digit<loaded.size();++digit)
|
||||
{
|
||||
if(!groups[digit].is_array()) return;
|
||||
for(const auto &row:groups[digit])
|
||||
{
|
||||
FolderShortcut entry{filePath(row.at("path")),row.at("peer"),ks::wide(row.at("name")),ks::wide(row.value("title",""))};
|
||||
if(!entry.path.is_absolute() || entry.path.native().find(L'\0')!=std::wstring::npos) continue;
|
||||
loaded[digit].push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
folderShortcuts=std::move(loaded);
|
||||
}
|
||||
catch(const std::exception &) {}
|
||||
}
|
||||
|
||||
constexpr int shortcutTitle=314,shortcutPath=211;
|
||||
|
||||
struct FolderShortcutEdit
|
||||
{
|
||||
FolderShortcut entry;
|
||||
int digit;
|
||||
std::optional<size_t> index;
|
||||
};
|
||||
|
||||
std::wstring shortcutField(HWND dialog,int id)
|
||||
{
|
||||
const auto field=GetDlgItem(dialog,id);
|
||||
const auto length=GetWindowTextLengthW(field);
|
||||
std::wstring result(static_cast<size_t>(length)+1,L'\0');
|
||||
result.resize(GetWindowTextW(field,result.data(),length+1));
|
||||
return result;
|
||||
}
|
||||
|
||||
INT_PTR CALLBACK folderShortcutEditDialog(HWND dialog,UINT message,WPARAM wp,LPARAM lp)
|
||||
{
|
||||
if(const auto painted=paintCopyDialog(dialog,message,wp,lp,L" Folder shortcuts ",false,{315}))
|
||||
return painted;
|
||||
if(message==WM_INITDIALOG)
|
||||
{
|
||||
auto &edit=*reinterpret_cast<FolderShortcutEdit*>(lp);
|
||||
SetWindowLongPtrW(dialog,DWLP_USER,lp);
|
||||
prepareCopyDialog(dialog);
|
||||
layoutDialogContent(dialog,316);
|
||||
for(const auto id:{316,317,IDOK,IDCANCEL})
|
||||
SendDlgItemMessageW(dialog,id,WM_SETFONT,reinterpret_cast<WPARAM>(mono),FALSE);
|
||||
SendDlgItemMessageW(dialog,shortcutTitle,EM_SETLIMITTEXT,255,0);
|
||||
SendDlgItemMessageW(dialog,shortcutPath,EM_SETLIMITTEXT,32767,0);
|
||||
SetDlgItemTextW(dialog,shortcutTitle,edit.entry.title.c_str());
|
||||
SetDlgItemTextW(dialog,shortcutPath,edit.entry.path.c_str());
|
||||
if(!edit.entry.peerId.empty())
|
||||
SetDlgItemTextW(dialog,317,(L"Path on "+edit.entry.peerName).c_str());
|
||||
layoutDialogButtons(dialog,315,{IDOK,IDCANCEL});
|
||||
return TRUE;
|
||||
}
|
||||
if(message==WM_CLOSE || (message==WM_COMMAND && LOWORD(wp)==IDCANCEL))
|
||||
{
|
||||
EndDialog(dialog,IDCANCEL);
|
||||
return TRUE;
|
||||
}
|
||||
if(message!=WM_COMMAND || LOWORD(wp)!=IDOK) return FALSE;
|
||||
auto &edit=*reinterpret_cast<FolderShortcutEdit*>(GetWindowLongPtrW(dialog,DWLP_USER));
|
||||
auto path=fs::path(shortcutField(dialog,shortcutPath)).lexically_normal();
|
||||
if(path.has_relative_path() && !path.has_filename()) path=path.parent_path();
|
||||
const auto root=path.root_name().native();
|
||||
const bool lanDrive=root.size()==2 && root[1]==L':' &&
|
||||
((root[0]>=L'A' && root[0]<=L'Z') || (root[0]>=L'a' && root[0]<=L'z'));
|
||||
if(!path.is_absolute() || (!edit.entry.peerId.empty() && !lanDrive))
|
||||
{
|
||||
MessageBoxW(dialog,L"Enter an absolute folder path on the original computer.",L"Folder shortcuts",MB_OK|MB_ICONWARNING);
|
||||
SetFocus(GetDlgItem(dialog,shortcutPath));
|
||||
return TRUE;
|
||||
}
|
||||
auto entry=edit.entry;
|
||||
entry.path=std::move(path);
|
||||
entry.title=shortcutField(dialog,shortcutTitle);
|
||||
const auto &group=folderShortcuts[edit.digit];
|
||||
for(size_t i=0;i<group.size();++i)
|
||||
if((!edit.index || i!=*edit.index) && sameFolderShortcut(group[i],entry))
|
||||
{
|
||||
MessageBoxW(dialog,L"This folder is already assigned to this digit.",L"Folder shortcuts",MB_OK|MB_ICONINFORMATION);
|
||||
return TRUE;
|
||||
}
|
||||
auto updated=folderShortcuts;
|
||||
if(edit.index) updated[edit.digit][*edit.index]=std::move(entry);
|
||||
else updated[edit.digit].push_back(std::move(entry));
|
||||
if(!saveFolderShortcuts(updated))
|
||||
{
|
||||
MessageBoxW(dialog,L"Could not save folder shortcuts.",L"Folder shortcuts",MB_OK|MB_ICONWARNING);
|
||||
return TRUE;
|
||||
}
|
||||
folderShortcuts=std::move(updated);
|
||||
EndDialog(dialog,IDOK);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool editFolderShortcut(HWND owner,FolderShortcutEdit &edit)
|
||||
{
|
||||
return DialogBoxParamW(GetModuleHandleW(nullptr),MAKEINTRESOURCEW(110),owner,
|
||||
folderShortcutEditDialog,reinterpret_cast<LPARAM>(&edit))==IDOK;
|
||||
}
|
||||
|
||||
void addFolderShortcut(const Panel &panel,int digit)
|
||||
{
|
||||
if(panel.directory.empty()) return;
|
||||
FolderShortcutEdit edit{{panel.directory,panel.peer?panel.peer->id:"",
|
||||
panel.peer?panel.peer->name:L""},digit,{}};
|
||||
if(editFolderShortcut(GetParent(panel.list),edit))
|
||||
setStatus(L"Folder added to Ctrl+"+std::to_wstring(digit)+L".");
|
||||
SetFocus(panel.list);
|
||||
}
|
||||
|
||||
void openFolderShortcut(Panel &panel,const FolderShortcut &entry)
|
||||
{
|
||||
if(entry.peerId.empty())
|
||||
{
|
||||
openOn(panel,entry.path,true,{});
|
||||
return;
|
||||
}
|
||||
queryDrivePeers();
|
||||
const auto peer=std::find_if(drivePeers.begin(),drivePeers.end(),
|
||||
[&](const auto &candidate){return candidate.id==entry.peerId && !candidate.address.empty();});
|
||||
if(peer==drivePeers.end())
|
||||
{
|
||||
setStatus(L"Paired computer unavailable: "+entry.peerName);
|
||||
return;
|
||||
}
|
||||
openOn(panel,entry.path,false,*peer);
|
||||
}
|
||||
|
||||
constexpr int shortcutList=310,quickRemove=313,quickEdit=318,quickInsert=319,quickMoveUp=320,quickMoveDown=321;
|
||||
constexpr wchar_t quickFolderHints[]=L"Ins Del F4 Ctrl+Up Ctrl+Down";
|
||||
struct FolderShortcutView
|
||||
{
|
||||
int digit;
|
||||
std::optional<FolderShortcut> chosen;
|
||||
std::vector<int> digits;
|
||||
int rowHeight{};
|
||||
Panel *panel{};
|
||||
HHOOK mouseHook{};
|
||||
};
|
||||
|
||||
void layoutQuickFolders(HWND dialog,FolderShortcutView &view,int selection)
|
||||
{
|
||||
const auto list=GetDlgItem(dialog,shortcutList);
|
||||
SendMessageW(list,LB_RESETCONTENT,0,0);
|
||||
view.digits.clear();
|
||||
const auto owner=GetWindow(dialog,GW_OWNER);
|
||||
const auto dc=GetDC(dialog);
|
||||
const auto font=SelectObject(dc,mono);
|
||||
const auto dpi=GetDpiForWindow(dialog);
|
||||
const auto px=[&](int value){return MulDiv(value,dpi,96);};
|
||||
TEXTMETRICW metrics{};
|
||||
GetTextMetricsW(dc,&metrics);
|
||||
view.rowHeight=std::max(1L,metrics.tmHeight);
|
||||
const auto frame=dialogFrameMetrics(dpi);
|
||||
const int inset=frame.content();
|
||||
const int listTop=dialogListInset(frame,view.rowHeight);
|
||||
const auto measure=[&](std::wstring_view label)
|
||||
{
|
||||
SIZE size{};
|
||||
GetTextExtentPoint32W(dc,label.data(),static_cast<int>(label.size()),&size);
|
||||
return static_cast<int>(size.cx);
|
||||
};
|
||||
int width=std::max(measure(L"Folder shortcuts"),measure(view.digit<0?L"Enter Esc":quickFolderHints))+3*cellWidth;
|
||||
const int textInset=dialogRowTextInset(cellWidth);
|
||||
const auto add=[&](const std::wstring &label)
|
||||
{
|
||||
width=std::max(width,measure(label)+2*textInset);
|
||||
SendMessageW(list,LB_ADDSTRING,0,reinterpret_cast<LPARAM>(label.c_str()));
|
||||
};
|
||||
if(view.digit<0)
|
||||
{
|
||||
for(int digit=0;digit<10;++digit)
|
||||
if(!folderShortcuts[digit].empty())
|
||||
{
|
||||
view.digits.push_back(digit);
|
||||
add(L"Ctrl+"+std::to_wstring(digit));
|
||||
}
|
||||
}
|
||||
else for(const auto &entry:folderShortcuts[view.digit])
|
||||
add(entry.title.empty()?(entry.peerId.empty()?L"":entry.peerName+L" · ")+entry.path.native():entry.title);
|
||||
SelectObject(dc,font);
|
||||
ReleaseDC(dialog,dc);
|
||||
MONITORINFO monitor{sizeof(monitor)};
|
||||
GetMonitorInfoW(MonitorFromWindow(owner,MONITOR_DEFAULTTONEAREST),&monitor);
|
||||
const auto &work=monitor.rcWork;
|
||||
RECT parent{};
|
||||
GetWindowRect(owner,&parent);
|
||||
const auto windowWidth=std::max(px(260),static_cast<int>(parent.right-parent.left));
|
||||
const auto windowHeight=std::max(px(140),static_cast<int>(parent.bottom-parent.top));
|
||||
width=std::min(width,std::max(1,std::min(windowWidth,static_cast<int>(work.right-work.left))-2*inset));
|
||||
const auto maxRows=std::max(1,(std::min(windowHeight,static_cast<int>(work.bottom-work.top))-2*listTop)/view.rowHeight);
|
||||
const auto count=static_cast<int>(SendMessageW(list,LB_GETCOUNT,0,0));
|
||||
const int rows=std::min(count,maxRows);
|
||||
SendMessageW(list,LB_SETITEMHEIGHT,0,view.rowHeight);
|
||||
const int listHeight=rows*view.rowHeight;
|
||||
const int w=width+2*inset,h=listHeight+2*listTop;
|
||||
const int x=std::clamp(static_cast<int>((parent.left+parent.right-w)/2),
|
||||
static_cast<int>(work.left),std::max(static_cast<int>(work.left),static_cast<int>(work.right)-w));
|
||||
const int y=std::clamp(static_cast<int>((parent.top+parent.bottom-h)/2),
|
||||
static_cast<int>(work.top),std::max(static_cast<int>(work.top),static_cast<int>(work.bottom)-h));
|
||||
SetWindowPos(dialog,nullptr,x,y,w,h,SWP_NOZORDER|SWP_NOACTIVATE);
|
||||
SetWindowPos(list,nullptr,inset,listTop,width,listHeight,SWP_NOZORDER|SWP_NOACTIVATE);
|
||||
SendMessageW(list,LB_SETCURSEL,std::clamp(selection,0,std::max(0,count-1)),0);
|
||||
InvalidateRect(dialog,nullptr,TRUE);
|
||||
}
|
||||
|
||||
LRESULT CALLBACK quickFolderKeys(HWND list,UINT message,WPARAM key,LPARAM flags,UINT_PTR,DWORD_PTR)
|
||||
{
|
||||
if(message==WM_KEYDOWN)
|
||||
{
|
||||
int command=0;
|
||||
if(key==VK_RETURN) command=IDOK;
|
||||
if(key==VK_DELETE) command=quickRemove;
|
||||
if(key==VK_F4) command=quickEdit;
|
||||
if(key==VK_INSERT) command=quickInsert;
|
||||
if((key==VK_UP || key==VK_DOWN) && (GetKeyState(VK_CONTROL)&0x8000))
|
||||
command=key==VK_UP?quickMoveUp:quickMoveDown;
|
||||
if(command)
|
||||
{
|
||||
if(!(flags & (1LL<<30))) SendMessageW(GetParent(list),WM_COMMAND,command,0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return scrollList(list,message,key,flags);
|
||||
}
|
||||
|
||||
HWND quickFolderWindow{};
|
||||
|
||||
bool dismissQuickFoldersAt(HWND dialog,POINT point)
|
||||
{
|
||||
RECT bounds{};
|
||||
if(!dialog || !IsWindowVisible(dialog) || !IsWindowEnabled(dialog) ||
|
||||
GetForegroundWindow()!=dialog || !GetWindowRect(dialog,&bounds) || PtInRect(&bounds,point))
|
||||
return false;
|
||||
return PostMessageW(dialog,WM_COMMAND,IDCANCEL,0)!=FALSE;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK quickFolderMouse(int code,WPARAM message,LPARAM data)
|
||||
{
|
||||
if(code==HC_ACTION && (message==WM_LBUTTONDOWN || message==WM_RBUTTONDOWN ||
|
||||
message==WM_MBUTTONDOWN || message==WM_XBUTTONDOWN) &&
|
||||
dismissQuickFoldersAt(quickFolderWindow,reinterpret_cast<const MSLLHOOKSTRUCT*>(data)->pt))
|
||||
return 1;
|
||||
return CallNextHookEx(nullptr,code,message,data);
|
||||
}
|
||||
|
||||
INT_PTR CALLBACK quickFolderDialog(HWND dialog,UINT message,WPARAM wp,LPARAM lp)
|
||||
{
|
||||
if(message==WM_INITDIALOG)
|
||||
{
|
||||
auto &view=*reinterpret_cast<FolderShortcutView*>(lp);
|
||||
SetWindowLongPtrW(dialog,DWLP_USER,lp);
|
||||
prepareCopyDialog(dialog);
|
||||
layoutQuickFolders(dialog,view,0);
|
||||
const auto list=GetDlgItem(dialog,shortcutList);
|
||||
SetWindowSubclass(list,quickFolderKeys,1,0);
|
||||
quickFolderWindow=dialog;
|
||||
view.mouseHook=SetWindowsHookExW(WH_MOUSE_LL,quickFolderMouse,GetModuleHandleW(nullptr),0);
|
||||
SetFocus(list);
|
||||
return FALSE;
|
||||
}
|
||||
auto *view=reinterpret_cast<FolderShortcutView*>(GetWindowLongPtrW(dialog,DWLP_USER));
|
||||
if(!view) return FALSE;
|
||||
if(message==WM_DESTROY)
|
||||
{
|
||||
if(view->mouseHook) UnhookWindowsHookEx(view->mouseHook);
|
||||
quickFolderWindow=nullptr;
|
||||
return FALSE;
|
||||
}
|
||||
if(message==WM_PAINT)
|
||||
{
|
||||
PAINTSTRUCT ps{};
|
||||
const auto dc=BeginPaint(dialog,&ps);
|
||||
RECT client{};
|
||||
GetClientRect(dialog,&client);
|
||||
const auto frame=CreateSolidBrush(colors[Text]);
|
||||
const auto bounds=paintDialogFrame(dc,client,dialogFrameMetrics(GetDpiForWindow(dialog)),teal,frame);
|
||||
DeleteObject(frame);
|
||||
paintDialogFrameCaptions(dc,client,bounds,L"Folder shortcuts",teal,colors[Text],mono,
|
||||
view->digit<0?L"Enter Esc":quickFolderHints);
|
||||
EndPaint(dialog,&ps);
|
||||
return TRUE;
|
||||
}
|
||||
if(message==WM_DRAWITEM && wp==shortcutList)
|
||||
{
|
||||
const auto &item=*reinterpret_cast<const DRAWITEMSTRUCT*>(lp);
|
||||
if(item.itemID==UINT(-1)) return TRUE;
|
||||
const auto length=SendMessageW(item.hwndItem,LB_GETTEXTLEN,item.itemID,0);
|
||||
if(length<0) return TRUE;
|
||||
std::wstring label(static_cast<size_t>(length)+1,L'\0');
|
||||
SendMessageW(item.hwndItem,LB_GETTEXT,item.itemID,reinterpret_cast<LPARAM>(label.data()));
|
||||
const auto background=colors[item.itemState&ODS_SELECTED?Selection:Dialog];
|
||||
paintDialogListRow(item.hDC,item.rcItem,std::wstring_view(label.data(),static_cast<size_t>(length)),
|
||||
mono,colors[Text],background,dialogRowTextInset(cellWidth));
|
||||
return TRUE;
|
||||
}
|
||||
if(message==WM_CLOSE || (message==WM_COMMAND && LOWORD(wp)==IDCANCEL))
|
||||
{
|
||||
EndDialog(dialog,IDCANCEL);
|
||||
return TRUE;
|
||||
}
|
||||
if(message!=WM_COMMAND) return FALSE;
|
||||
const auto action=LOWORD(wp);
|
||||
if(action!=quickRemove && action!=quickEdit && action!=quickInsert && action!=quickMoveUp &&
|
||||
action!=quickMoveDown && action!=IDOK && !(action==shortcutList && HIWORD(wp)==LBN_DBLCLK)) return FALSE;
|
||||
const auto row=SendDlgItemMessageW(dialog,shortcutList,LB_GETCURSEL,0,0);
|
||||
if(view->digit<0)
|
||||
{
|
||||
if((action==IDOK || action==shortcutList) && row>=0 && static_cast<size_t>(row)<view->digits.size())
|
||||
{
|
||||
view->digit=view->digits[row];
|
||||
layoutQuickFolders(dialog,*view,0);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
const auto &group=folderShortcuts[view->digit];
|
||||
if(action==quickInsert)
|
||||
{
|
||||
const auto &panel=*view->panel;
|
||||
FolderShortcutEdit edit{{panel.directory,panel.peer?panel.peer->id:"",
|
||||
panel.peer?panel.peer->name:L""},view->digit,{}};
|
||||
if(!panel.directory.empty() && editFolderShortcut(dialog,edit))
|
||||
layoutQuickFolders(dialog,*view,static_cast<int>(group.size())-1);
|
||||
SetFocus(GetDlgItem(dialog,shortcutList));
|
||||
return TRUE;
|
||||
}
|
||||
if(row<0 || static_cast<size_t>(row)>=group.size()) return TRUE;
|
||||
if(action==quickMoveUp || action==quickMoveDown)
|
||||
{
|
||||
const auto target=row+(action==quickMoveUp?-1:1);
|
||||
if(target<0 || static_cast<size_t>(target)>=group.size()) return TRUE;
|
||||
auto updated=folderShortcuts;
|
||||
std::swap(updated[view->digit][row],updated[view->digit][target]);
|
||||
if(!saveFolderShortcuts(updated))
|
||||
MessageBoxW(dialog,L"Could not save folder shortcuts.",L"Folder shortcuts",MB_OK|MB_ICONWARNING);
|
||||
else
|
||||
{
|
||||
folderShortcuts=std::move(updated);
|
||||
layoutQuickFolders(dialog,*view,static_cast<int>(target));
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
if(action==quickEdit)
|
||||
{
|
||||
FolderShortcutEdit edit{group[row],view->digit,static_cast<size_t>(row)};
|
||||
if(editFolderShortcut(dialog,edit)) layoutQuickFolders(dialog,*view,static_cast<int>(row));
|
||||
SetFocus(GetDlgItem(dialog,shortcutList));
|
||||
return TRUE;
|
||||
}
|
||||
if(action==quickRemove)
|
||||
{
|
||||
auto updated=folderShortcuts;
|
||||
updated[view->digit].erase(updated[view->digit].begin()+row);
|
||||
if(!saveFolderShortcuts(updated))
|
||||
{
|
||||
MessageBoxW(dialog,L"Could not save folder shortcuts.",L"Folder shortcuts",MB_OK|MB_ICONWARNING);
|
||||
return TRUE;
|
||||
}
|
||||
folderShortcuts=std::move(updated);
|
||||
if(folderShortcuts[view->digit].empty()) EndDialog(dialog,IDCANCEL);
|
||||
else layoutQuickFolders(dialog,*view,static_cast<int>(row));
|
||||
return TRUE;
|
||||
}
|
||||
view->chosen=group[row];
|
||||
EndDialog(dialog,IDOK);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
void showFolderShortcuts(Panel &panel,int digit)
|
||||
{
|
||||
if(digit>=0)
|
||||
{
|
||||
const auto &group=folderShortcuts[digit];
|
||||
if(group.empty())
|
||||
{
|
||||
setStatus(L"No folders assigned to Ctrl+"+std::to_wstring(digit)+L".");
|
||||
return;
|
||||
}
|
||||
if(group.size()==1)
|
||||
{
|
||||
openFolderShortcut(panel,group.front());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(digit<0 && std::all_of(folderShortcuts.begin(),folderShortcuts.end(),[](const auto &group){return group.empty();}))
|
||||
{
|
||||
setStatus(L"No folder shortcuts.");
|
||||
return;
|
||||
}
|
||||
FolderShortcutView view{digit};
|
||||
view.panel=&panel;
|
||||
const auto result=DialogBoxParamW(GetModuleHandleW(nullptr),MAKEINTRESOURCEW(109),GetParent(panel.list),
|
||||
quickFolderDialog,reinterpret_cast<LPARAM>(&view));
|
||||
SetFocus(panel.list);
|
||||
if(result==IDOK && view.chosen) openFolderShortcut(panel,*view.chosen);
|
||||
else if(result==-1) setStatus(L"Could not open Folder shortcuts.");
|
||||
}
|
||||
+212
-72
@@ -353,6 +353,7 @@ void chooseDrive(int index, bool fromMenu = false);
|
||||
void openMenu(HWND window, int index);
|
||||
void showFileDetails(const Panel &panel);
|
||||
void editOptions(HWND owner);
|
||||
void showFolderShortcuts(Panel &panel,int digit);
|
||||
void layout(HWND window);
|
||||
|
||||
DWORD preference(const wchar_t *name, DWORD fallback)
|
||||
@@ -541,22 +542,99 @@ void paintMenuLabel(HDC dc,RECT rect,const std::wstring &text,size_t access,COLO
|
||||
SetBkMode(dc,mode);
|
||||
}
|
||||
|
||||
void paintDriveCaption(HDC dc,RECT row,const wchar_t *caption)
|
||||
struct DialogFramePadding
|
||||
{
|
||||
DWORD outer,inner,rowText{};
|
||||
bool operator==(const DialogFramePadding &) const=default;
|
||||
};
|
||||
// Delete at 96 DPI: outer line at x=8, inner line at x=10, content at x=25.
|
||||
constexpr DialogFramePadding defaultDialogFramePadding{8,14};
|
||||
constexpr DWORD maxDialogFramePadding=24;
|
||||
constexpr DWORD maxDialogRowTextPadding=4;
|
||||
constexpr wchar_t dialogFrameOuterKey[]=L"DialogFrameOuterPadding";
|
||||
constexpr wchar_t dialogFrameInnerKey[]=L"DialogFrameInnerPadding";
|
||||
constexpr wchar_t dialogRowTextKey[]=L"DialogRowTextPadding";
|
||||
auto dialogFramePadding=defaultDialogFramePadding;
|
||||
|
||||
struct DialogFrameMetrics
|
||||
{
|
||||
int outer,gap,inner;
|
||||
int innerLine() const {return outer+gap;}
|
||||
int content() const {return innerLine()+1+inner;}
|
||||
};
|
||||
|
||||
DialogFrameMetrics dialogFrameMetrics(UINT dpi,DialogFramePadding padding=dialogFramePadding)
|
||||
{
|
||||
return {MulDiv(padding.outer,dpi,96),std::max(2,MulDiv(2,dpi,96)),MulDiv(padding.inner,dpi,96)};
|
||||
}
|
||||
|
||||
void paintDialogCaption(HDC dc,RECT row,const wchar_t *caption,HBRUSH surface,COLORREF ink,HFONT font)
|
||||
{
|
||||
const int saved=SaveDC(dc);
|
||||
SelectObject(dc,mono);
|
||||
SelectObject(dc,font);
|
||||
SIZE extent{};
|
||||
GetTextExtentPoint32W(dc,caption,lstrlenW(caption),&extent);
|
||||
const LONG width=std::min(row.right-row.left,extent.cx+2*cellWidth);
|
||||
TEXTMETRICW metrics{};
|
||||
GetTextMetricsW(dc,&metrics);
|
||||
const LONG width=std::min(row.right-row.left,extent.cx+2*metrics.tmAveCharWidth);
|
||||
row.left+=(row.right-row.left-width)/2;
|
||||
row.right=row.left+width;
|
||||
FillRect(dc,&row,teal);
|
||||
FillRect(dc,&row,surface);
|
||||
SetBkMode(dc,TRANSPARENT);
|
||||
SetTextColor(dc,colors[Text]);
|
||||
SetTextColor(dc,ink);
|
||||
DrawTextW(dc,caption,-1,&row,DT_CENTER|DT_VCENTER|DT_SINGLELINE|DT_END_ELLIPSIS|DT_NOPREFIX);
|
||||
if(saved) RestoreDC(dc,saved);
|
||||
}
|
||||
|
||||
RECT paintDialogFrame(HDC dc,RECT bounds,DialogFrameMetrics metrics,HBRUSH surface,HBRUSH border)
|
||||
{
|
||||
FillRect(dc,&bounds,surface);
|
||||
InflateRect(&bounds,-metrics.outer,-metrics.outer);
|
||||
FrameRect(dc,&bounds,border);
|
||||
InflateRect(&bounds,-metrics.gap,-metrics.gap);
|
||||
FrameRect(dc,&bounds,border);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
void paintDialogFrameCaptions(HDC dc,RECT client,RECT frame,const wchar_t *title,
|
||||
HBRUSH surface,COLORREF ink,HFONT font,const wchar_t *hints=nullptr)
|
||||
{
|
||||
const auto previous=SelectObject(dc,font);
|
||||
TEXTMETRICW text{};
|
||||
GetTextMetricsW(dc,&text);
|
||||
SelectObject(dc,previous);
|
||||
const auto top=std::max(client.top,frame.top-text.tmHeight/2);
|
||||
if(title) paintDialogCaption(dc,{frame.left,top,frame.right,top+text.tmHeight},title,surface,ink,font);
|
||||
const auto bottom=std::min(client.bottom,frame.bottom+(text.tmHeight+1)/2);
|
||||
if(hints) paintDialogCaption(dc,{frame.left,bottom-text.tmHeight,frame.right,bottom},hints,surface,ink,font);
|
||||
}
|
||||
|
||||
int dialogListInset(DialogFrameMetrics metrics,int fontHeight)
|
||||
{
|
||||
return std::max(metrics.content(),metrics.innerLine()+(fontHeight+1)/2);
|
||||
}
|
||||
|
||||
int dialogRowTextInset(int characterWidth,DialogFramePadding padding=dialogFramePadding)
|
||||
{
|
||||
return characterWidth*static_cast<int>(padding.rowText);
|
||||
}
|
||||
|
||||
void paintDialogListRow(HDC dc,RECT bounds,std::wstring_view label,HFONT font,
|
||||
COLORREF foreground,COLORREF background,int inset)
|
||||
{
|
||||
const auto saved=SaveDC(dc);
|
||||
SetBkColor(dc,background);
|
||||
ExtTextOutW(dc,0,0,ETO_OPAQUE,&bounds,nullptr,0,nullptr);
|
||||
SelectObject(dc,font);
|
||||
SetTextColor(dc,foreground);
|
||||
SetBkMode(dc,TRANSPARENT);
|
||||
InflateRect(&bounds,-inset,0);
|
||||
if(bounds.right>bounds.left)
|
||||
DrawTextW(dc,label.data(),static_cast<int>(label.size()),&bounds,
|
||||
DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX|DT_PATH_ELLIPSIS);
|
||||
if(saved) RestoreDC(dc,saved);
|
||||
}
|
||||
|
||||
void showMenu(bool show);
|
||||
|
||||
void hidePicker()
|
||||
@@ -1049,6 +1127,7 @@ void selectDrive()
|
||||
if(row==2) chooseDrive(index);
|
||||
else setMode(index,row==1);
|
||||
}
|
||||
else if(menuIndex==2 && row==3) showFolderShortcuts(panels[active],-1);
|
||||
else
|
||||
{
|
||||
const int command=menuIndex==1?113+static_cast<int>(row):110+static_cast<int>(row);
|
||||
@@ -1156,12 +1235,14 @@ void layoutDrivePicker(int selected,bool focus)
|
||||
GetWindowRect(panels[pickerPanel].list,&panel);
|
||||
MapWindowPoints(nullptr,owner,reinterpret_cast<POINT*>(&panel),2);
|
||||
const int panelWidth=panel.right-panel.left;
|
||||
const int width=std::clamp(drivePickerWidth,90,std::max(90,panelWidth*4/5-12));
|
||||
const int height=std::min(std::max(1,static_cast<int>(panel.bottom-panel.top)/textHeight),
|
||||
const auto frame=dialogFrameMetrics(GetDpiForWindow(owner));
|
||||
const int inset=frame.content(),vertical=dialogListInset(frame,textHeight);
|
||||
const int width=std::clamp(drivePickerWidth,90,std::max(90,panelWidth*4/5-2*inset));
|
||||
const int height=std::min(std::max(1,(static_cast<int>(panel.bottom-panel.top)-2*vertical)/textHeight),
|
||||
static_cast<int>(SendMessageW(picker,LB_GETCOUNT,0,0)))*textHeight;
|
||||
const int x=panel.left+(panel.right-panel.left-width)/2;
|
||||
const int y=panel.top+(panel.bottom-panel.top-height)/2;
|
||||
SetWindowPos(pickerFrame,HWND_TOP,x-10,y-textHeight,width+20,height+2*textHeight,SWP_SHOWWINDOW|SWP_NOACTIVATE);
|
||||
SetWindowPos(pickerFrame,HWND_TOP,x-inset,y-vertical,width+2*inset,height+2*vertical,SWP_SHOWWINDOW|SWP_NOACTIVATE);
|
||||
SetWindowPos(picker,HWND_TOP,x,y,width,height,SWP_SHOWWINDOW|SWP_NOACTIVATE);
|
||||
SendMessageW(picker,LB_SETCURSEL,selected,0);
|
||||
if(focus) SetFocus(picker);
|
||||
@@ -1385,9 +1466,44 @@ void installDialogButtonKeys(HWND dialog)
|
||||
},0);
|
||||
}
|
||||
|
||||
bool isDialogEdit(HWND control)
|
||||
{
|
||||
wchar_t name[16]{};
|
||||
GetClassNameW(control,name,static_cast<int>(std::size(name)));
|
||||
return lstrcmpiW(name,L"Edit")==0;
|
||||
}
|
||||
|
||||
void prepareDialogEdit(HWND field)
|
||||
{
|
||||
SendMessageW(field,WM_SETFONT,reinterpret_cast<WPARAM>(mono),FALSE);
|
||||
const auto margin=MulDiv(2,GetDpiForWindow(field),96);
|
||||
SendMessageW(field,EM_SETMARGINS,EC_LEFTMARGIN|EC_RIGHTMARGIN,MAKELPARAM(margin,margin));
|
||||
if(!(GetWindowLongPtrW(field,GWL_STYLE)&ES_MULTILINE)) return;
|
||||
|
||||
// A single-line Edit centers its own text. Copy uses a multiline Edit for
|
||||
// its formatting rectangle and keeps its existing dialog key handling.
|
||||
RECT edit{};
|
||||
GetClientRect(field,&edit);
|
||||
if(const auto dc=GetDC(field))
|
||||
{
|
||||
const auto oldFont=SelectObject(dc,mono);
|
||||
TEXTMETRICW metrics{};
|
||||
if(GetTextMetricsW(dc,&metrics))
|
||||
edit.top=std::max(0L,(edit.bottom-metrics.tmHeight)/2);
|
||||
SelectObject(dc,oldFont);
|
||||
ReleaseDC(field,dc);
|
||||
}
|
||||
SendMessageW(field,EM_SETRECTNP,0,reinterpret_cast<LPARAM>(&edit));
|
||||
}
|
||||
|
||||
void prepareCopyDialog(HWND dialog)
|
||||
{
|
||||
installDialogButtonKeys(dialog);
|
||||
EnumChildWindows(dialog,[](HWND control,LPARAM)->BOOL
|
||||
{
|
||||
if(isDialogEdit(control)) prepareDialogEdit(control);
|
||||
return TRUE;
|
||||
},0);
|
||||
const auto style=GetWindowLongPtrW(dialog,GWL_STYLE);
|
||||
SetWindowLongPtrW(dialog,GWL_STYLE,style&~(WS_CAPTION|WS_SYSMENU|WS_THICKFRAME));
|
||||
const auto extended=GetWindowLongPtrW(dialog,GWL_EXSTYLE);
|
||||
@@ -1396,8 +1512,6 @@ void prepareCopyDialog(HWND dialog)
|
||||
SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_FRAMECHANGED);
|
||||
}
|
||||
|
||||
constexpr LONG dialogFrameInset=10;
|
||||
|
||||
RECT dialogControlBounds(HWND dialog,int id)
|
||||
{
|
||||
RECT bounds{};
|
||||
@@ -1406,6 +1520,34 @@ RECT dialogControlBounds(HWND dialog,int id)
|
||||
return bounds;
|
||||
}
|
||||
|
||||
void layoutDialogContent(HWND dialog,int firstControl)
|
||||
{
|
||||
const auto dpi=GetDpiForWindow(dialog);
|
||||
const auto frame=dialogFrameMetrics(dpi);
|
||||
const auto first=dialogControlBounds(dialog,firstControl);
|
||||
const int dx=frame.content()-first.left;
|
||||
const int dy=dialogListInset(frame,textHeight)+MulDiv(1,dpi,96)-first.top;
|
||||
for(auto control=GetWindow(dialog,GW_CHILD);control;control=GetWindow(control,GW_HWNDNEXT))
|
||||
{
|
||||
RECT bounds{};
|
||||
GetWindowRect(control,&bounds);
|
||||
MapWindowPoints(nullptr,dialog,reinterpret_cast<POINT*>(&bounds),2);
|
||||
SetWindowPos(control,nullptr,bounds.left+dx,bounds.top+dy,0,0,
|
||||
SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
|
||||
}
|
||||
RECT outer{};
|
||||
GetWindowRect(dialog,&outer);
|
||||
SetWindowPos(dialog,nullptr,0,0,outer.right-outer.left+2*dx,outer.bottom-outer.top+dy,
|
||||
SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
int dialogFooterInset(HWND dialog)
|
||||
{
|
||||
const auto dpi=GetDpiForWindow(dialog);
|
||||
const auto metrics=dialogFrameMetrics(dpi);
|
||||
return metrics.innerLine();
|
||||
}
|
||||
|
||||
LONG dialogDividerY(HWND dialog,int id)
|
||||
{
|
||||
const auto bounds=dialogControlBounds(dialog,id);
|
||||
@@ -1424,7 +1566,7 @@ void centerDialogRow(HWND dialog,int upperDivider,int lowerDivider,std::initiali
|
||||
RECT client{};
|
||||
GetClientRect(dialog,&client);
|
||||
const auto top=dialogDividerY(dialog,upperDivider)+1;
|
||||
const auto bottom=lowerDivider?dialogDividerY(dialog,lowerDivider):client.bottom-dialogFrameInset;
|
||||
const auto bottom=lowerDivider?dialogDividerY(dialog,lowerDivider):client.bottom-dialogFooterInset(dialog);
|
||||
for(const auto id:controls)
|
||||
{
|
||||
const auto bounds=dialogControlBounds(dialog,id);
|
||||
@@ -1438,8 +1580,17 @@ void layoutDialogButtons(HWND dialog,int divider,std::initializer_list<int> cont
|
||||
RECT client{},window{};
|
||||
GetClientRect(dialog,&client);
|
||||
GetWindowRect(dialog,&window);
|
||||
const auto footerHeight=2*static_cast<LONG>(textHeight);
|
||||
const auto height=dialogDividerY(dialog,divider)+1+footerHeight+dialogFrameInset;
|
||||
const auto dpi=GetDpiForWindow(dialog);
|
||||
const auto frame=dialogFrameMetrics(dpi);
|
||||
const auto extra=frame.inner-dialogFrameMetrics(dpi,defaultDialogFramePadding).inner;
|
||||
LONG tallest{};
|
||||
for(const auto id:controls)
|
||||
{
|
||||
const auto bounds=dialogControlBounds(dialog,id);
|
||||
tallest=std::max(tallest,bounds.bottom-bounds.top);
|
||||
}
|
||||
const auto footerHeight=std::max(tallest+2,2*static_cast<LONG>(textHeight)+2*extra);
|
||||
const auto height=dialogDividerY(dialog,divider)+1+footerHeight+dialogFooterInset(dialog);
|
||||
const auto width=window.right-window.left;
|
||||
const auto outerHeight=window.bottom-window.top+height-client.bottom;
|
||||
RECT owner{};
|
||||
@@ -1477,7 +1628,8 @@ INT_PTR paintCopyDialog(HWND dialog, UINT message, WPARAM wp, LPARAM lp,
|
||||
if(message==WM_CTLCOLORSTATIC || message==WM_CTLCOLOREDIT || message==WM_CTLCOLORBTN)
|
||||
{
|
||||
const auto dc=reinterpret_cast<HDC>(wp);
|
||||
const bool edit=reinterpret_cast<HWND>(lp)==GetDlgItem(dialog,211);
|
||||
const bool edit=message==WM_CTLCOLOREDIT ||
|
||||
(message==WM_CTLCOLORSTATIC && isDialogEdit(reinterpret_cast<HWND>(lp)));
|
||||
SetTextColor(dc,edit?colors[Text]:foreground);
|
||||
SetBkColor(dc,edit?colors[Dialog]:surface);
|
||||
return reinterpret_cast<INT_PTR>(edit?teal:brush);
|
||||
@@ -1488,27 +1640,16 @@ INT_PTR paintCopyDialog(HWND dialog, UINT message, WPARAM wp, LPARAM lp,
|
||||
const auto dc=BeginPaint(dialog,&paint);
|
||||
RECT bounds{};
|
||||
GetClientRect(dialog,&bounds);
|
||||
FillRect(dc,&bounds,brush);
|
||||
InflateRect(&bounds,-8,-8);
|
||||
const auto client=bounds;
|
||||
const auto black=static_cast<HBRUSH>(GetStockObject(warning?WHITE_BRUSH:BLACK_BRUSH));
|
||||
FrameRect(dc,&bounds,black);
|
||||
InflateRect(&bounds,-2,-2);
|
||||
FrameRect(dc,&bounds,black);
|
||||
bounds=paintDialogFrame(dc,client,dialogFrameMetrics(GetDpiForWindow(dialog)),brush,black);
|
||||
for(const auto control:dividers)
|
||||
{
|
||||
const auto y=dialogDividerY(dialog,control);
|
||||
RECT divider{bounds.left+1,y,bounds.right-1,y+1};
|
||||
FillRect(dc,÷r,black);
|
||||
}
|
||||
SelectObject(dc,mono);
|
||||
RECT title{};
|
||||
DrawTextW(dc,caption,-1,&title,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX);
|
||||
const auto titleHeight=title.bottom;
|
||||
const auto top=std::max(0L,bounds.top-titleHeight/2);
|
||||
title={bounds.left,top,bounds.right,top+titleHeight};
|
||||
SetTextColor(dc,foreground);
|
||||
SetBkColor(dc,surface);
|
||||
DrawTextW(dc,caption,-1,&title,DT_CENTER|DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX);
|
||||
paintDialogFrameCaptions(dc,client,bounds,caption,brush,foreground,mono);
|
||||
EndPaint(dialog,&paint);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -1586,6 +1727,7 @@ INT_PTR CALLBACK copyDialog(HWND dialog, UINT message, WPARAM wp, LPARAM lp)
|
||||
{
|
||||
auto &request=*reinterpret_cast<CopyRequest*>(lp);
|
||||
prepareCopyDialog(dialog);
|
||||
layoutDialogContent(dialog,summary);
|
||||
SetWindowLongPtrW(dialog,DWLP_USER,lp);
|
||||
for(const auto label:{L"Ask",L"Overwrite",L"Skip",L"Rename",L"Append",L"Only newer file(s)"})
|
||||
if(!request.move || std::wstring_view(label)!=L"Append")
|
||||
@@ -1601,22 +1743,6 @@ INT_PTR CALLBACK copyDialog(HWND dialog, UINT message, WPARAM wp, LPARAM lp)
|
||||
SetDlgItemTextW(dialog,pathField,fieldText.c_str());
|
||||
SendDlgItemMessageW(dialog,pathField,EM_SETREADONLY,!single && !request.move,0);
|
||||
if(request.move) SetDlgItemTextW(dialog,IDOK,L"&Rename");
|
||||
SendDlgItemMessageW(dialog,pathField,WM_SETFONT,reinterpret_cast<WPARAM>(mono),FALSE);
|
||||
RECT edit{};
|
||||
GetClientRect(GetDlgItem(dialog,pathField),&edit);
|
||||
const auto field=GetDlgItem(dialog,pathField);
|
||||
if(const auto dc=GetDC(field))
|
||||
{
|
||||
const auto oldFont=SelectObject(dc,mono);
|
||||
TEXTMETRICW metrics{};
|
||||
if(GetTextMetricsW(dc,&metrics))
|
||||
edit.top=std::max(0L,(edit.bottom-metrics.tmHeight)/2);
|
||||
SelectObject(dc,oldFont);
|
||||
ReleaseDC(field,dc);
|
||||
}
|
||||
edit.left+=2;
|
||||
edit.right-=2;
|
||||
SendDlgItemMessageW(dialog,pathField,EM_SETRECT,0,reinterpret_cast<LPARAM>(&edit));
|
||||
layoutDialogButtons(dialog,214,{IDOK,IDCANCEL});
|
||||
SendDlgItemMessageW(dialog,summary,WM_SETFONT,reinterpret_cast<WPARAM>(mono),FALSE);
|
||||
SendDlgItemMessageW(dialog,conflicts,WM_SETFONT,reinterpret_cast<WPARAM>(mono),FALSE);
|
||||
@@ -1873,6 +1999,7 @@ INT_PTR CALLBACK cancelOperationDialog(HWND window,UINT message,WPARAM wp,LPARAM
|
||||
if(message==WM_INITDIALOG)
|
||||
{
|
||||
prepareCopyDialog(window);
|
||||
layoutDialogContent(window,235);
|
||||
SetDlgItemTextW(window,235,reinterpret_cast<const wchar_t*>(lp));
|
||||
SendDlgItemMessageW(window,235,WM_SETFONT,reinterpret_cast<WPARAM>(mono),FALSE);
|
||||
layoutDialogButtons(window,236,{IDYES,IDNO});
|
||||
@@ -1942,6 +2069,7 @@ INT_PTR CALLBACK copyProgressDialog(HWND window,UINT message,WPARAM wp,LPARAM lp
|
||||
if(message==WM_INITDIALOG)
|
||||
{
|
||||
prepareCopyDialog(window);
|
||||
layoutDialogContent(window,220);
|
||||
SetWindowLongPtrW(window,DWLP_USER,lp);
|
||||
}
|
||||
for(const auto control:{220,221,224,225})
|
||||
@@ -1965,7 +2093,8 @@ INT_PTR CALLBACK copyProgressDialog(HWND window,UINT message,WPARAM wp,LPARAM lp
|
||||
SetWindowPos(GetDlgItem(window,id),nullptr,bounds.left,top,0,0,
|
||||
SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
|
||||
};
|
||||
const auto padding=dialogControlBounds(window,220).top-dialogFrameInset-1;
|
||||
const auto frame=dialogFrameMetrics(GetDpiForWindow(window));
|
||||
const auto padding=std::max(0,frame.inner);
|
||||
moveRow(221,std::max(dialogControlBounds(window,221).top,dialogControlBounds(window,220).bottom));
|
||||
moveRow(222,std::max(dialogControlBounds(window,222).top,dialogControlBounds(window,221).bottom+4));
|
||||
const auto firstBottom=dialogControlBounds(window,222).bottom;
|
||||
@@ -2049,6 +2178,7 @@ INT_PTR CALLBACK warningDialog(HWND window,UINT message,WPARAM wp,LPARAM lp)
|
||||
if(message==WM_INITDIALOG)
|
||||
{
|
||||
prepareCopyDialog(window);
|
||||
layoutDialogContent(window,230);
|
||||
SetWindowLongPtrW(window,DWLP_USER,lp);
|
||||
const auto &warning=*reinterpret_cast<WarningPrompt*>(lp);
|
||||
SetDlgItemTextW(window,230,warning.message);
|
||||
@@ -2497,6 +2627,7 @@ INT_PTR CALLBACK deleteConfirmationDialog(HWND window,UINT message,WPARAM wp,LPA
|
||||
if(message==WM_INITDIALOG)
|
||||
{
|
||||
prepareCopyDialog(window);
|
||||
layoutDialogContent(window,240);
|
||||
SetDlgItemTextW(window,240,data->message);
|
||||
SetDlgItemTextW(window,241,data->entries.c_str());
|
||||
if(data->permanent) SetDlgItemTextW(window,IDYES,L"&Delete");
|
||||
@@ -2591,6 +2722,7 @@ INT_PTR CALLBACK deleteProgressDialog(HWND window,UINT message,WPARAM wp,LPARAM
|
||||
if(message==WM_INITDIALOG)
|
||||
{
|
||||
prepareCopyDialog(window);
|
||||
layoutDialogContent(window,245);
|
||||
SetWindowLongPtrW(window,DWLP_USER,lp);
|
||||
for(const auto id:{245,246})
|
||||
SendDlgItemMessageW(window,id,WM_SETFONT,reinterpret_cast<WPARAM>(mono),FALSE);
|
||||
@@ -2883,11 +3015,6 @@ LRESULT CALLBACK panelKeys(HWND window, UINT message, WPARAM wp, LPARAM lp, UINT
|
||||
}
|
||||
if (message == WM_KEYDOWN)
|
||||
{
|
||||
if ((GetKeyState(VK_CONTROL)&0x8000) && (wp==L'1' || wp==L'4'))
|
||||
{
|
||||
setMode(static_cast<int>(index),wp==L'4');
|
||||
return 0;
|
||||
}
|
||||
if (wp == VK_TAB) { SetFocus(panels[1 - index].list); return 0; }
|
||||
if (wp == VK_RETURN) { enter(panels[index]); return 0; }
|
||||
if (wp == VK_F2) { open(panels[index], panels[index].directory); return 0; }
|
||||
@@ -2940,6 +3067,7 @@ LRESULT CALLBACK panelKeys(HWND window, UINT message, WPARAM wp, LPARAM lp, UINT
|
||||
return result;
|
||||
}
|
||||
|
||||
#include "files_folder_shortcuts.inl"
|
||||
#include "files_options.inl"
|
||||
|
||||
void openMenu(HWND window, int index)
|
||||
@@ -2958,12 +3086,12 @@ void openMenu(HWND window, int index)
|
||||
if(index==0 || index==4)
|
||||
{
|
||||
const bool wide=panels[index==4].wide;
|
||||
rows={wide?L" Brief\tCtrl+1":L"✓ Brief\tCtrl+1",
|
||||
wide?L"✓ Wide\tCtrl+4":L" Wide\tCtrl+4",
|
||||
rows={wide?L" Brief":L"✓ Brief",
|
||||
wide?L"✓ Wide":L" Wide",
|
||||
index==0?L" Change drive\tAlt+F1":L" Change drive\tAlt+F2"};
|
||||
}
|
||||
else if(index==1) rows={L"Copy\tF5",L"Move\tF6",L"Exit\tF10"};
|
||||
else if(index==2) rows={L"Refresh\tF2",L"View\tF3",L"Edit\tF4"};
|
||||
else if(index==2) rows={L"Refresh\tF2",L"View\tF3",L"Edit\tF4",L"Folder shortcuts"};
|
||||
std::set<wchar_t> used;
|
||||
commandAccessPositions.assign(rows.size(),std::wstring::npos);
|
||||
const auto reserve=[&](size_t row,wchar_t key)
|
||||
@@ -2996,11 +3124,13 @@ void openMenu(HWND window, int index)
|
||||
GetWindowRect(menuItems[index],&anchor);
|
||||
MapWindowPoints(nullptr,window,reinterpret_cast<POINT*>(&anchor),2);
|
||||
GetClientRect(window,&client);
|
||||
const int width=std::min(static_cast<int>(client.right)-16,26*cellWidth+16);
|
||||
const int x=std::clamp(static_cast<int>(anchor.left)+4,4,std::max(4,static_cast<int>(client.right)-width-12));
|
||||
const int inset=dialogFrameMetrics(GetDpiForWindow(window)).content();
|
||||
const int width=std::max(1,std::min(static_cast<int>(client.right)-2*inset,26*cellWidth+16));
|
||||
const int x=std::clamp(static_cast<int>(anchor.left)+inset,inset,
|
||||
std::max(inset,static_cast<int>(client.right)-width-inset));
|
||||
const int y=anchor.bottom+5;
|
||||
const int height=static_cast<int>(rows.size())*static_cast<int>(SendMessageW(picker,LB_GETITEMHEIGHT,0,0));
|
||||
SetWindowPos(pickerFrame,HWND_TOP,x-4,y-4,width+13,height+8,SWP_SHOWWINDOW);
|
||||
SetWindowPos(pickerFrame,HWND_TOP,x-inset,y-inset,width+2*inset,height+2*inset,SWP_SHOWWINDOW);
|
||||
SetWindowPos(picker,HWND_TOP,x,y,width,height,SWP_SHOWWINDOW);
|
||||
SendMessageW(picker,LB_SETCURSEL,0,0);
|
||||
SetFocus(picker);
|
||||
@@ -3101,7 +3231,12 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp)
|
||||
font.lfFaceName[LF_FACESIZE-1]=0;
|
||||
if(!applyFont(window,font,static_cast<int>(std::clamp(preference(L"FontPoints",defaultFontPoints),80UL,240UL)))) return -1;
|
||||
loadColors();
|
||||
dialogFramePadding={
|
||||
std::min(preference(dialogFrameOuterKey,defaultDialogFramePadding.outer),maxDialogFramePadding),
|
||||
std::min(preference(dialogFrameInnerKey,defaultDialogFramePadding.inner),maxDialogFramePadding),
|
||||
std::min(preference(dialogRowTextKey,defaultDialogFramePadding.rowText),maxDialogRowTextPadding)};
|
||||
loadFileStyles();
|
||||
loadFolderShortcuts();
|
||||
panels[0].wide=preference(L"LeftWide",defaultWide)!=0;
|
||||
panels[1].wide=preference(L"RightWide",defaultWide)!=0;
|
||||
sizeWidth=static_cast<int>(std::clamp(preference(L"SizeWidth",defaultSizeWidth),6UL,16UL));
|
||||
@@ -3355,15 +3490,11 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp)
|
||||
}
|
||||
if(item.hwndItem==pickerFrame)
|
||||
{
|
||||
auto box=item.rcItem;
|
||||
FillRect(item.hDC,&box,teal);
|
||||
auto border=CreateSolidBrush(commandPicker?colors[Frame]:RGB(255,255,255));
|
||||
const auto box=paintDialogFrame(item.hDC,item.rcItem,
|
||||
dialogFrameMetrics(GetDpiForWindow(window)),teal,border);
|
||||
if(!commandPicker)
|
||||
{
|
||||
InflateRect(&box,-6,-textHeight/2);
|
||||
FrameRect(item.hDC,&box,border);
|
||||
InflateRect(&box,-3,-3);
|
||||
FrameRect(item.hDC,&box,border);
|
||||
if(!drivePaths.empty())
|
||||
{
|
||||
RECT list{};
|
||||
@@ -3379,16 +3510,8 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp)
|
||||
}
|
||||
}
|
||||
}
|
||||
RECT title{box.left,box.top-textHeight/2-2,
|
||||
box.right,box.top-textHeight/2-2+textHeight};
|
||||
paintDriveCaption(item.hDC,title,L"Change drive");
|
||||
DeleteObject(border);
|
||||
return TRUE;
|
||||
paintDialogFrameCaptions(item.hDC,item.rcItem,box,L"Change drive",teal,colors[Text],mono);
|
||||
}
|
||||
InflateRect(&box,-2,-2);
|
||||
FrameRect(item.hDC,&box,border);
|
||||
InflateRect(&box,-2,-2);
|
||||
FrameRect(item.hDC,&box,border);
|
||||
DeleteObject(border);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -3418,7 +3541,7 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp)
|
||||
SetDCBrushColor(item.hDC,RGB(255,255,255));
|
||||
FillRect(item.hDC,&line,static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
|
||||
++row.top;
|
||||
paintDriveCaption(item.hDC,row,L"KSwitcher LAN");
|
||||
paintDialogCaption(item.hDC,row,L"KSwitcher LAN",teal,colors[Text],mono);
|
||||
const RECT sectionLine{row.left,row.bottom-1,row.right,row.bottom};
|
||||
FillRect(item.hDC,§ionLine,static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
|
||||
return TRUE;
|
||||
@@ -3698,6 +3821,23 @@ 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) &&
|
||||
(GetKeyState(VK_CONTROL)&0x8000) && !(GetKeyState(VK_MENU)&0x8000) &&
|
||||
!(GetKeyState(VK_LWIN)&0x8000) && !(GetKeyState(VK_RWIN)&0x8000) &&
|
||||
message.wParam>='0' && message.wParam<='9' && !copyInProgress)
|
||||
{
|
||||
if(!(message.lParam & (1LL<<30)))
|
||||
{
|
||||
hidePicker();
|
||||
showMenu(false);
|
||||
auto &panel=panels[message.hwnd==panels[1].list];
|
||||
const auto digit=static_cast<int>(message.wParam-'0');
|
||||
if(GetKeyState(VK_SHIFT)&0x8000) addFolderShortcut(panel,digit);
|
||||
else showFolderShortcuts(panel,digit);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const bool filesMessage=message.hwnd==window || IsChild(window,message.hwnd);
|
||||
const bool modified=(GetKeyState(VK_CONTROL)&0x8000) || (GetKeyState(VK_SHIFT)&0x8000) ||
|
||||
(GetKeyState(VK_LWIN)&0x8000) || (GetKeyState(VK_RWIN)&0x8000);
|
||||
|
||||
+56
-6
@@ -11,6 +11,18 @@ constexpr int wideColumnControls[]{275,276,201,202,204};
|
||||
constexpr int statusColumnControls[]{277,203,205,206,207,208};
|
||||
constexpr int colorOptionControls[]{279,280};
|
||||
constexpr int appearanceControls[]{281,282};
|
||||
enum AppearanceSection { Font, DialogFrame };
|
||||
constexpr int outerFramePadding=303,innerFramePadding=305,rowTextPadding=307;
|
||||
constexpr int dialogFrameControls[]{302,outerFramePadding,304,innerFramePadding,306,rowTextPadding};
|
||||
constexpr struct
|
||||
{
|
||||
int id;
|
||||
DWORD DialogFramePadding::*value;
|
||||
DWORD maximum;
|
||||
} frameOptions[]{
|
||||
{outerFramePadding,&DialogFramePadding::outer,maxDialogFramePadding},
|
||||
{innerFramePadding,&DialogFramePadding::inner,maxDialogFramePadding},
|
||||
{rowTextPadding,&DialogFramePadding::rowText,maxDialogRowTextPadding}};
|
||||
|
||||
enum class OptionsAnchor { StretchWidth, StretchHeight, BottomRight };
|
||||
struct OptionsControl
|
||||
@@ -44,6 +56,7 @@ struct OptionsDraft
|
||||
{
|
||||
LOGFONTW font=displayFont;
|
||||
int points=fontPoints;
|
||||
DialogFramePadding framePadding=dialogFramePadding;
|
||||
std::array<COLORREF,ColorCount> palette{};
|
||||
std::array<COLORREF,16> custom{};
|
||||
std::vector<FileStyle> styles=fileStyles;
|
||||
@@ -87,7 +100,8 @@ void updateOptionsApply(HWND dialog,const OptionsDraft &draft)
|
||||
const bool dirty=!valid || width!=static_cast<UINT>(sizeWidth) ||
|
||||
checked(273)!=panels[0].wide || checked(274)!=panels[1].wide ||
|
||||
checked(202)!=alignExtensions || checked(204)!=alignFolders || checked(203)!=showDetails ||
|
||||
fields!=statusFields || draft.palette!=colors || draft.styles!=fileStyles || optionsFontChanged(draft);
|
||||
fields!=statusFields || draft.palette!=colors || draft.styles!=fileStyles ||
|
||||
draft.framePadding!=dialogFramePadding || optionsFontChanged(draft);
|
||||
EnableWindow(GetDlgItem(dialog,optionsApply),dirty);
|
||||
InvalidateRect(GetDlgItem(dialog,fileStylePreview),nullptr,FALSE);
|
||||
}
|
||||
@@ -165,7 +179,9 @@ void showOptionsPage(HWND dialog,OptionsDraft &draft,int page,int section)
|
||||
{
|
||||
title=optionsTitles[page];
|
||||
if(section>=0)
|
||||
title=page==PanelModes?panelSectionNames[section]:page==PanelColors?(section==fileStyleSection?L"File type styles":colorNames[section]):L"Font";
|
||||
title=page==PanelModes?panelSectionNames[section]:page==PanelColors?
|
||||
(section==fileStyleSection?L"File type styles":colorNames[section]):
|
||||
section==DialogFrame?L"Dialog frame":L"Font";
|
||||
}
|
||||
SetDlgItemTextW(dialog,optionsTitle,title);
|
||||
const auto show=[&](auto &controls,bool visible)
|
||||
@@ -192,6 +208,13 @@ void showOptionsPage(HWND dialog,OptionsDraft &draft,int page,int section)
|
||||
show(panelLayoutControls,page==PanelModes && section==PanelLayout);
|
||||
show(wideColumnControls,page==PanelModes && section==WideColumns);
|
||||
show(statusColumnControls,page==PanelModes && section==StatusColumns);
|
||||
show(appearanceControls,page==Appearance && section==Font);
|
||||
show(dialogFrameControls,page==Appearance && section==DialogFrame);
|
||||
if(page==Appearance && section==DialogFrame)
|
||||
{
|
||||
for(const auto &option:frameOptions)
|
||||
SendDlgItemMessageW(dialog,option.id,CB_SETCURSEL,draft.framePadding.*option.value,0);
|
||||
}
|
||||
const bool fileTypes=page==PanelColors && section==fileStyleSection;
|
||||
constexpr int fileStyleControls[]{287,288,289,290,291,292,293,294,295,296,297,298,300,fileStyleState};
|
||||
show(fileStyleControls,fileTypes);
|
||||
@@ -209,7 +232,6 @@ void showOptionsPage(HWND dialog,OptionsDraft &draft,int page,int section)
|
||||
}
|
||||
updateFileStyleEditor(dialog,draft,true);
|
||||
}
|
||||
show(appearanceControls,page==Appearance && section>=0);
|
||||
if(page==PanelColors && section>=0 && !fileTypes)
|
||||
{
|
||||
const auto color=draft.palette[section];
|
||||
@@ -283,7 +305,12 @@ void filterOptions(HWND dialog,OptionsDraft &draft)
|
||||
addSection(L"File type styles",fileStyleSection,extensionMatches || matches(L"System and hidden") ||
|
||||
matches(L"Underline") || matches(L"Strikethrough") || matches(L"Foreground"));
|
||||
}
|
||||
else addSection(L"Font",0,matches(L"Display font"));
|
||||
else
|
||||
{
|
||||
addSection(L"Font",Font,matches(L"Display font"));
|
||||
addSection(L"Dialog frame",DialogFrame,
|
||||
matches(L"Outer padding") || matches(L"Inner padding") || matches(L"Row text padding"));
|
||||
}
|
||||
if(category) TreeView_Expand(tree,category,TVE_EXPAND);
|
||||
}
|
||||
TreeView_Expand(tree,root,TVE_EXPAND);
|
||||
@@ -312,6 +339,10 @@ INT_PTR CALLBACK optionsDialog(HWND dialog,UINT message,WPARAM wp,LPARAM lp)
|
||||
SetDlgItemInt(dialog,201,sizeWidth,FALSE);
|
||||
SendDlgItemMessageW(dialog,201,EM_SETLIMITTEXT,2,0);
|
||||
SendDlgItemMessageW(dialog,optionsSearch,EM_SETLIMITTEXT,255,0);
|
||||
for(const auto &option:frameOptions)
|
||||
for(DWORD value=0;value<=option.maximum;++value)
|
||||
SendDlgItemMessageW(dialog,option.id,CB_ADDSTRING,0,
|
||||
reinterpret_cast<LPARAM>(std::to_wstring(value).c_str()));
|
||||
CheckDlgButton(dialog,202,alignExtensions);
|
||||
CheckDlgButton(dialog,203,showDetails);
|
||||
CheckDlgButton(dialog,204,alignFolders);
|
||||
@@ -376,9 +407,19 @@ INT_PTR CALLBACK optionsDialog(HWND dialog,UINT message,WPARAM wp,LPARAM lp)
|
||||
return TRUE;
|
||||
case 282:
|
||||
chooseDisplayFont(dialog,draft->font,draft->points);
|
||||
showOptionsPage(dialog,*draft,Appearance,0);
|
||||
showOptionsPage(dialog,*draft,Appearance,Font);
|
||||
updateOptionsApply(dialog,*draft);
|
||||
return TRUE;
|
||||
case outerFramePadding: case innerFramePadding: case rowTextPadding:
|
||||
if(HIWORD(wp)==CBN_SELCHANGE)
|
||||
{
|
||||
const auto value=SendMessageW(reinterpret_cast<HWND>(lp),CB_GETCURSEL,0,0);
|
||||
if(value>=0)
|
||||
for(const auto &option:frameOptions)
|
||||
if(option.id==LOWORD(wp)) draft->framePadding.*option.value=static_cast<DWORD>(value);
|
||||
updateOptionsApply(dialog,*draft);
|
||||
}
|
||||
return TRUE;
|
||||
case fileStyleState:
|
||||
if(HIWORD(wp)==CBN_SELCHANGE) updateFileStyleEditor(dialog,*draft,false);
|
||||
return TRUE;
|
||||
@@ -430,11 +471,13 @@ INT_PTR CALLBACK optionsDialog(HWND dialog,UINT message,WPARAM wp,LPARAM lp)
|
||||
if(draft->page==allOptions || (draft->page==PanelColors &&
|
||||
(draft->section<0 || draft->section==fileStyleSection)))
|
||||
draft->styles=defaultFileStyles;
|
||||
if(draft->page==allOptions || draft->page==Appearance)
|
||||
if(draft->page==allOptions || (draft->page==Appearance && draft->section!=DialogFrame))
|
||||
{
|
||||
draft->font=defaultFilesFont();
|
||||
draft->points=defaultFontPoints;
|
||||
}
|
||||
if(draft->page==allOptions || (draft->page==Appearance && draft->section!=Font))
|
||||
draft->framePadding=defaultDialogFramePadding;
|
||||
showOptionsPage(dialog,*draft,draft->page,draft->section);
|
||||
updateOptionsApply(dialog,*draft);
|
||||
return TRUE;
|
||||
@@ -503,6 +546,13 @@ INT_PTR CALLBACK optionsDialog(HWND dialog,UINT message,WPARAM wp,LPARAM lp)
|
||||
savePreference(std::to_wstring(i).c_str(),colors[i]);
|
||||
}
|
||||
refreshColors();
|
||||
if(draft->framePadding!=dialogFramePadding)
|
||||
{
|
||||
dialogFramePadding=draft->framePadding;
|
||||
savePreference(dialogFrameOuterKey,dialogFramePadding.outer);
|
||||
savePreference(dialogFrameInnerKey,dialogFramePadding.inner);
|
||||
savePreference(dialogRowTextKey,dialogFramePadding.rowText);
|
||||
}
|
||||
if(fontChanged)
|
||||
{
|
||||
savePreference(L"FontPoints",fontPoints);
|
||||
|
||||
@@ -80,7 +80,25 @@ void paintOptionsPreview(HWND dialog,const DRAWITEMSTRUCT &item,const OptionsDra
|
||||
entry(L"examples",L"old",true,false);
|
||||
};
|
||||
|
||||
if(draft.page==PanelColors && (draft.section==Dialog || draft.section==Selection))
|
||||
if(draft.page==Appearance && draft.section==DialogFrame)
|
||||
{
|
||||
InflateRect(&bounds,-px(8),-px(8));
|
||||
const auto framePadding=dialogFrameMetrics(dpi,draft.framePadding);
|
||||
const auto surface=CreateSolidBrush(palette[Dialog]);
|
||||
const auto ink=CreateSolidBrush(palette[Text]);
|
||||
const auto frame=paintDialogFrame(dc,bounds,framePadding,surface,ink);
|
||||
const auto titleFont=previewFont?previewFont:mono;
|
||||
paintDialogFrameCaptions(dc,bounds,frame,L"Folder shortcuts",surface,palette[Text],titleFont);
|
||||
DeleteObject(ink);
|
||||
DeleteObject(surface);
|
||||
auto content=bounds;
|
||||
InflateRect(&content,-framePadding.content(),-dialogListInset(framePadding,metrics.tmHeight));
|
||||
content.bottom=std::min(content.bottom,content.top+rowHeight);
|
||||
if(content.bottom>content.top)
|
||||
paintDialogListRow(dc,content,LR"(C:\Projects\Example\Documents\Archive)",titleFont,
|
||||
palette[Text],palette[Selection],dialogRowTextInset(metrics.tmAveCharWidth,draft.framePadding));
|
||||
}
|
||||
else if(draft.page==PanelColors && (draft.section==Dialog || draft.section==Selection))
|
||||
{
|
||||
InflateRect(&bounds,-px(12),-px(10));
|
||||
const auto maxWidth=px(340);
|
||||
@@ -134,6 +152,18 @@ void paintOptionsPreview(HWND dialog,const DRAWITEMSTRUCT &item,const OptionsDra
|
||||
else panel(bounds,draft.page==PanelModes || checked(273),
|
||||
draft.page==PanelColors && draft.section==PanelCursor,L"C:\\Example");
|
||||
|
||||
if(draft.page==PanelColors && (draft.section==Text || draft.section==Dialog))
|
||||
{
|
||||
auto field=item.rcItem;
|
||||
InflateRect(&field,-px(12),-px(10));
|
||||
field.right=std::min(field.right,field.left+px(340));
|
||||
field.top+=px(5)+4*rowHeight;
|
||||
field.bottom=field.top+rowHeight+px(6);
|
||||
fill(field,RGB(192,192,192));
|
||||
InflateRect(&field,-px(4),-px(3));
|
||||
row(field,L"C:\\Example",palette[Text],palette[Dialog]);
|
||||
}
|
||||
|
||||
RestoreDC(dc,saved);
|
||||
if(previewFont) DeleteObject(previewFont);
|
||||
}
|
||||
Reference in new issue
Block a user