Compare commits

..
7 changed files with 714 additions and 40 deletions

No files matched your search

+8
View File
@@ -15,6 +15,14 @@ Far Manager є зразком для оформлення файлового м
- Reset selected to defaults скидає вибраний рівень дерева. Корінь All settings скидає всі параметри, навіть коли пошук приховує частину дерева. У редакторах із кількома параметрами Reset property скидає лише вибрану властивість. Скидання змінює чернетку до Apply або OK. Стандартний Panel background — `#000080`.
- Apply доступна, коли чернетка відрізняється від застосованих налаштувань. Після Apply або повернення всіх значень до застосованих вона вимикається. Apply одразу зберігає зміни й перемальовує панелі за відкритим діалогом. OK робить те саме й закриває діалог. Cancel і Escape відкидають зміни після останнього Apply та повертають фокус до панелі.
- Colors → File type styles налаштовує текст, фон, підкреслення й закреслення для правил за розширенням і групи System and hidden. Розширення можна вибрати зі списку Windows або додати вручну з крапкою чи без неї. Вони зберігаються в KSwitcher, незалежно від асоціацій Windows. Курсор, позначки й результат передачі зберігають свої сигнальні кольори. Кольори правила можна перевизначити окремо для звичайного рядка, позначки Insert, курсора у фокусі та курсора без фокуса. Позначка має пріоритет над курсором, а підсвічування передачі має пріоритет над кольорами курсора. Без перевизначення стан зберігає звичайний вигляд панелі. Мініпанель унизу редактора одразу показує всі чотири стани на синтетичних рядках до Apply.
- Нижня область показує живе прев’ю для кольорів, шрифту, режимів панелей, широких колонок і рядка стану. Для Popup background, Menu selection і Command bar зразок показує відповідний елемент. Прев’ю читає чернетку, зокрема після Reset, і не потребує реальних файлів. Воно заповнює простір від останнього видимого елемента редактора до нижніх кнопок зі сталими відступами. Зі зміною розміру росте область зразка, а рядки зберігають масштаб шрифту.
- Підписи File type, Windows extension or custom extension і Row state стоять над відповідними полями зі спільним лівим краєм. Ширина списків враховує найдовші значення, шрифт, DPI й доступне місце. Add і Remove стоять одразу після своїх полів.
- Початковий набір правил охоплює EXE, Markdown, системні й приховані елементи, архіви, зображення, аудіо й відео, офісні документи й PDF, код і скрипти. Збережений набір правил завантажується як є, включно з видаленими правилами. Reset у File type styles відновлює весь початковий набір у чернетці.
## Панелі
- Меню Change drive показує літеру, тип, загальний обсяг і вільне місце. Відкриття меню запускає нове LAN-виявлення. Секція KSwitcher LAN показує збережені пари, зокрема офлайн. Після кожного відкриття меню асинхронно запитує диски й показує їх під назвою відповідного ПК. Поки триває запит, видно стан завантаження. Якщо запит не вдався, видно помилку. Повторне відкриття запускає новий запит. Кожна панель має власний диск і шлях, а в заголовку віддаленої панелі перед шляхом показано назву ПК.
+21 -6
View File
@@ -4,14 +4,14 @@
1 ICON "kswitcher-files.ico"
1 RT_MANIFEST "app.manifest"
101 DIALOGEX 0, 0, 397, 228
101 DIALOGEX 0, 0, 397, 310
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_CLIPCHILDREN
CAPTION "KSwitcher Files Options"
FONT 9, "Segoe UI"
BEGIN
LTEXT "&Search settings", -1, 8, 8, 113, 10
EDITTEXT 270, 8, 21, 113, 14, ES_AUTOHSCROLL
CONTROL "", 271, "SysTreeView32", WS_TABSTOP | WS_BORDER | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_SHOWSELALWAYS, 8, 40, 113, 158
CONTROL "", 271, "SysTreeView32", WS_TABSTOP | WS_BORDER | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_SHOWSELALWAYS, 8, 40, 113, 240
LTEXT "", 272, 131, 8, 253, 12
AUTOCHECKBOX "Left panel: &Wide", 273, 132, 27, 115, 12
AUTOCHECKBOX "Right panel: W&ide", 274, 251, 27, 125, 12
@@ -30,12 +30,27 @@ BEGIN
LTEXT "", 280, 222, 50, 155, 12
LTEXT "", 281, 131, 30, 254, 14
PUSHBUTTON "Change &font…", 282, 131, 52, 82, 15
PUSHBUTTON "Reset selected to defaults", 284, 131, 163, 125, 15
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
DEFPUSHBUTTON "OK", IDOK, 222, 207, 50, 14
PUSHBUTTON "Cancel", IDCANCEL, 280, 207, 50, 14
PUSHBUTTON "&Apply", 283, 338, 207, 50, 14
LTEXT "File type", 287, 131, 26, 75, 10
COMBOBOX 288, 131, 38, 151, 100, CBS_DROPDOWNLIST | WS_TABSTOP | WS_VSCROLL
PUSHBUTTON "&Remove", 289, 290, 38, 77, 14
LTEXT "Windows extension or custom extension", 290, 131, 63, 246, 10
COMBOBOX 291, 131, 75, 151, 108, CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_TABSTOP | WS_VSCROLL
PUSHBUTTON "A&dd", 292, 290, 75, 77, 14
LTEXT "Row state", 300, 131, 100, 72, 10
COMBOBOX 301, 131, 112, 174, 80, CBS_DROPDOWNLIST | WS_TABSTOP
PUSHBUTTON "&Foreground…", 293, 131, 135, 78, 14
PUSHBUTTON "&Background…", 294, 216, 135, 78, 14
PUSHBUTTON "&Inherit colors", 295, 301, 135, 83, 14
LTEXT "", 296, 131, 152, 253, 13
AUTOCHECKBOX "&Underline", 297, 131, 166, 80, 12
AUTOCHECKBOX "Stri&kethrough", 298, 218, 166, 100, 12
CONTROL "Settings preview", 299, "Static", SS_OWNERDRAW | WS_BORDER, 131, 202, 253, 81
DEFPUSHBUTTON "OK", IDOK, 222, 289, 50, 14
PUSHBUTTON "Cancel", IDCANCEL, 280, 289, 50, 14
PUSHBUTTON "&Apply", 283, 338, 289, 50, 14
END
102 DIALOGEX 0, 0, 350, 114
+40 -28
View File
@@ -367,6 +367,8 @@ void savePreference(const wchar_t *name, DWORD value)
RegSetKeyValueW(HKEY_CURRENT_USER,schemeKey,name,REG_DWORD,&value,sizeof(value));
}
#include "files_styles.inl"
constexpr const wchar_t *panelPathKeys[]{L"LeftPath",L"RightPath"};
constexpr const wchar_t *panelCursorKeys[]{L"LeftCursor",L"RightCursor"};
@@ -416,6 +418,8 @@ LOGFONTW defaultFilesFont()
return font;
}
std::array<HFONT,4> fileStyleFonts{};
bool applyFont(HWND owner, LOGFONTW font, int points)
{
const auto dpi=GetDpiForWindow(owner);
@@ -434,6 +438,14 @@ bool applyFont(HWND owner, LOGFONTW font, int points)
cellWidth=std::max(1L,metrics.tmAveCharWidth);
auto old=mono;
mono=next;
for(unsigned i=1;i<fileStyleFonts.size();++i)
{
if(fileStyleFonts[i]) DeleteObject(fileStyleFonts[i]);
auto decorated=font;
decorated.lfUnderline=(i&1)!=0;
decorated.lfStrikeOut=(i&2)!=0;
fileStyleFonts[i]=CreateFontIndirectW(&decorated);
}
displayFont=font;
fontPoints=points;
terminalView.setFont(displayFont,fontPoints);
@@ -494,7 +506,7 @@ void loadColors()
refreshColors();
}
void choosePanelColor(HWND owner,COLORREF &color,COLORREF *custom)
bool choosePanelColor(HWND owner,COLORREF &color,COLORREF *custom)
{
CHOOSECOLORW dialog{};
dialog.lStructSize=sizeof(dialog);
@@ -502,14 +514,16 @@ void choosePanelColor(HWND owner,COLORREF &color,COLORREF *custom)
dialog.rgbResult=color;
dialog.lpCustColors=custom;
dialog.Flags=CC_RGBINIT | CC_FULLOPEN;
if(ChooseColorW(&dialog)) color=dialog.rgbResult;
if(!ChooseColorW(&dialog)) return false;
color=dialog.rgbResult;
return true;
}
void paintText(HDC dc, RECT rect, const wchar_t *text, COLORREF foreground, COLORREF background, bool right=false)
void paintText(HDC dc, RECT rect, const wchar_t *text, COLORREF foreground, COLORREF background, bool right=false,HFONT font=nullptr)
{
SetTextColor(dc, foreground);
SetBkColor(dc, background);
SelectObject(dc, mono);
SelectObject(dc, font?font:mono);
auto alignment=SetTextAlign(dc,right?TA_RIGHT:TA_LEFT);
ExtTextOutW(dc,right?rect.right-5:rect.left+5,rect.top+3,ETO_OPAQUE|ETO_CLIPPED,&rect,text,lstrlenW(text),nullptr);
SetTextAlign(dc,alignment);
@@ -2848,6 +2862,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_KILLFOCUS || message==WM_SETFOCUS) InvalidateRect(window,nullptr,FALSE);
if (message == WM_SETFOCUS)
{
const auto previous=active;
@@ -3086,6 +3101,7 @@ 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();
loadFileStyles();
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));
@@ -3412,34 +3428,29 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp)
if(item.CtlID==102 || item.CtlID==103)
{
const auto cursor=SendMessageW(item.hwndItem,LB_GETCURSEL,0,0);
if(item.hwndItem==panels[active].list && cursor!=LB_ERR &&
if(GetFocus()==item.hwndItem && cursor!=LB_ERR &&
static_cast<UINT>(cursor)==item.itemID) background=PanelCursor;
}
else if(item.itemState&(ODS_SELECTED|ODS_FOCUS))
background=Selection;
COLORREF rowBackground=colors[background];
HFONT rowFont{};
if(item.CtlID==102 || item.CtlID==103)
{
const auto &panel=panels[item.CtlID-102];
if(item.itemID<panel.rows.size() && panel.sizes[item.itemID]!=L"Folder" &&
panel.sizes[item.itemID]!=L"Up")
if(item.itemID<panel.rows.size() && panel.sizes[item.itemID]!=L"Up")
{
const auto extension=panel.rows[item.itemID].extension();
if(CompareStringOrdinal(extension.c_str(),-1,L".exe",-1,TRUE)==CSTR_EQUAL)
foreground=RGB(85,255,85);
else if(CompareStringOrdinal(extension.c_str(),-1,L".md",-1,TRUE)==CSTR_EQUAL)
foreground=RGB(231,192,247);
}
if(item.itemID<panel.marked.size() && panel.marked[item.itemID])
foreground=RGB(255,255,0);
else if(transferHighlightPhase%2==0 && item.itemID<panel.rows.size() &&
panel.sizes[item.itemID]!=L"Up" && panel.transferResults.contains(panel.rows[item.itemID]))
foreground=RGB(255,223,115);
else if(item.itemID<panel.dimmed.size() && panel.dimmed[item.itemID])
{
auto bg=colors[background];
foreground=RGB((3*GetRValue(foreground)+2*GetRValue(bg))/5,
(3*GetGValue(foreground)+2*GetGValue(bg))/5,
(3*GetBValue(foreground)+2*GetBValue(bg))/5);
const bool marked=panel.marked[item.itemID];
const bool transferred=transferHighlightPhase%2==0 && panel.transferResults.contains(panel.rows[item.itemID]);
const bool cursor=SendMessageW(panel.list,LB_GETCURSEL,0,0)==item.itemID;
const auto state=marked?FileRowState::Marked:!cursor?FileRowState::Normal:
GetFocus()==panel.list?FileRowState::Focused:FileRowState::Unfocused;
const auto resolved=resolveFileStyle(panel.rows[item.itemID].extension().native(),
panel.sizes[item.itemID]!=L"Folder",panel.dimmed[item.itemID],
background==PanelCursor || marked || transferred,foreground,rowBackground,fileStyles,state,transferred);
foreground=resolved.foreground;
rowBackground=resolved.background;
rowFont=fileStyleFonts[resolved.decorations];
}
}
auto shortcut=text.find(L'\t');
@@ -3512,7 +3523,7 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp)
}
else if(item.CtlID>=130 && item.CtlID<=134)
paintMenuLabel(item.hDC,item.rcItem,text,0,foreground,colors[background]);
else paintText(item.hDC,item.rcItem,text.c_str(),foreground,colors[background]);
else paintText(item.hDC,item.rcItem,text.c_str(),foreground,rowBackground,false,rowFont);
if(item.CtlID==102 || item.CtlID==103)
{
const auto &panel=panels[item.CtlID-102];
@@ -3535,16 +3546,16 @@ LRESULT CALLBACK filesWindow(HWND window, UINT message, WPARAM wp, LPARAM lp)
end.left=std::max(end.left+static_cast<LONG>(cellWidth),end.right-width);
end.left=std::min(end.left,end.right);
name.right=end.left;
paintText(item.hDC,end,extension.c_str()+1,foreground,colors[background],true);
paintText(item.hDC,end,extension.c_str()+1,foreground,rowBackground,true,rowFont);
text=panel.rows[item.itemID].stem().wstring();
}
}
paintText(item.hDC,name,text.c_str(),foreground,colors[background]);
paintText(item.hDC,name,text.c_str(),foreground,rowBackground,false,rowFont);
const auto &amount=panel.sizes[item.itemID];
auto visible=amount;
if(amount!=L"Folder" && amount!=L"Up")
visible=formatSize(std::stoull(amount),static_cast<size_t>(sizeWidth));
paintText(item.hDC,size,visible.c_str(),foreground,colors[background],true);
paintText(item.hDC,size,visible.c_str(),foreground,rowBackground,true);
RECT line{size.left,size.top,size.left+1,size.bottom};
SetDCBrushColor(item.hDC,colors[Frame]);
FillRect(item.hDC,&line,static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
@@ -3765,6 +3776,7 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int show)
TranslateMessage(&message);
DispatchMessageW(&message);
}
for(auto font:fileStyleFonts) if(font) DeleteObject(font);
DeleteObject(mono);
DeleteObject(blue);
DeleteObject(teal);
+117 -6
View File
@@ -46,6 +46,9 @@ struct OptionsDraft
int points=fontPoints;
std::array<COLORREF,ColorCount> palette{};
std::array<COLORREF,16> custom{};
std::vector<FileStyle> styles=fileStyles;
int styleIndex=0;
bool styleSuggestionsLoaded{};
int page=PanelModes, section=PanelLayout;
HTREEITEM wideColumnsNode{};
RECT initialClient{};
@@ -60,6 +63,9 @@ struct OptionsDraft
};
#include "files_style_options.inl"
#include "files_options_preview.inl"
bool optionsFontChanged(const OptionsDraft &draft)
{
auto pending=draft.font, applied=displayFont;
@@ -81,8 +87,9 @@ 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 || optionsFontChanged(draft);
fields!=statusFields || draft.palette!=colors || draft.styles!=fileStyles || optionsFontChanged(draft);
EnableWindow(GetDlgItem(dialog,optionsApply),dirty);
InvalidateRect(GetDlgItem(dialog,fileStylePreview),nullptr,FALSE);
}
LRESULT CALLBACK optionsGroupBackground(HWND group,UINT message,WPARAM wp,LPARAM lp,UINT_PTR,DWORD_PTR)
@@ -98,6 +105,28 @@ LRESULT CALLBACK optionsGroupBackground(HWND group,UINT message,WPARAM wp,LPARAM
return DefSubclassProc(group,message,wp,lp);
}
void layoutOptionsPreview(HWND dialog)
{
const auto preview=GetDlgItem(dialog,fileStylePreview);
if(!(GetWindowLongPtrW(preview,GWL_STYLE)&WS_VISIBLE)) return;
RECT gap{0,0,6,5};
MapDialogRect(dialog,&gap);
const auto title=dialogControlBounds(dialog,optionsTitle);
const auto buttons=dialogControlBounds(dialog,IDOK);
LONG top=title.bottom;
for(auto child=GetWindow(dialog,GW_CHILD);child;child=GetWindow(child,GW_HWNDNEXT))
{
const auto id=GetDlgCtrlID(child);
if(child==preview || id==IDOK || id==IDCANCEL || id==optionsApply ||
!(GetWindowLongPtrW(child,GWL_STYLE)&WS_VISIBLE)) continue;
const auto bounds=dialogControlBounds(dialog,id);
if(id!=-1 && bounds.left>=title.left-4) top=std::max(top,bounds.bottom);
}
top+=gap.bottom;
SetWindowPos(preview,nullptr,title.left,top,title.right-title.left,
std::max(1L,buttons.top-gap.bottom-top),SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOREDRAW);
}
void layoutOptions(HWND dialog,const OptionsDraft &draft)
{
const auto dpi=GetDpiForWindow(dialog);
@@ -120,6 +149,8 @@ void layoutOptions(HWND dialog,const OptionsDraft &draft)
SetWindowPos(GetDlgItem(dialog,control.id),nullptr,x,y,width,height,
SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOREDRAW);
}
if(draft.page==PanelColors && draft.section==fileStyleSection) layoutFileStyleFields(dialog,false);
layoutOptionsPreview(dialog);
// Paint the final layout together, including the areas vacated by moved controls.
RedrawWindow(dialog,nullptr,nullptr,RDW_INVALIDATE|RDW_ERASE|RDW_ALLCHILDREN|RDW_UPDATENOW);
}
@@ -134,7 +165,7 @@ void showOptionsPage(HWND dialog,OptionsDraft &draft,int page,int section)
{
title=optionsTitles[page];
if(section>=0)
title=page==PanelModes?panelSectionNames[section]:page==PanelColors?colorNames[section]:L"Font";
title=page==PanelModes?panelSectionNames[section]:page==PanelColors?(section==fileStyleSection?L"File type styles":colorNames[section]):L"Font";
}
SetDlgItemTextW(dialog,optionsTitle,title);
const auto show=[&](auto &controls,bool visible)
@@ -161,9 +192,25 @@ 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(colorOptionControls,page==PanelColors && section>=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);
const auto preview=GetDlgItem(dialog,fileStylePreview);
ShowWindow(preview,page>=0 && page<allOptions && section>=0?SW_SHOW:SW_HIDE);
layoutOptionsPreview(dialog);
RedrawWindow(dialog,nullptr,nullptr,RDW_INVALIDATE|RDW_ERASE|RDW_ALLCHILDREN);
show(colorOptionControls,page==PanelColors && section>=0 && !fileTypes);
if(fileTypes)
{
if(!draft.styleSuggestionsLoaded)
{
fillFileStyleSuggestions(dialog);
draft.styleSuggestionsLoaded=true;
}
updateFileStyleEditor(dialog,draft,true);
}
show(appearanceControls,page==Appearance && section>=0);
if(page==PanelColors && section>=0)
if(page==PanelColors && section>=0 && !fileTypes)
{
const auto color=draft.palette[section];
wchar_t hex[8]{};
@@ -229,7 +276,13 @@ void filterOptions(HWND dialog,OptionsDraft &draft)
}
}
else if(page==PanelColors)
{
for(int color=0;color<ColorCount;++color) addSection(colorNames[color],color);
const bool extensionMatches=std::any_of(draft.styles.begin(),draft.styles.end(),
[&](const auto &style){return matches(style.extension.c_str());});
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"));
if(category) TreeView_Expand(tree,category,TVE_EXPAND);
}
@@ -271,6 +324,13 @@ INT_PTR CALLBACK optionsDialog(HWND dialog,UINT message,WPARAM wp,LPARAM lp)
}
auto *draft=reinterpret_cast<OptionsDraft*>(GetWindowLongPtrW(dialog,DWLP_USER));
if(!draft) return FALSE;
if(message==WM_DRAWITEM && wp==fileStylePreview)
{
const auto &item=*reinterpret_cast<const DRAWITEMSTRUCT*>(lp);
if(draft->page==PanelColors && draft->section==fileStyleSection) paintFileStylePreview(item,*draft);
else paintOptionsPreview(dialog,item,*draft);
return TRUE;
}
if(message==WM_GETMINMAXINFO)
{
const auto dpi=GetDpiForWindow(dialog);
@@ -307,7 +367,7 @@ INT_PTR CALLBACK optionsDialog(HWND dialog,UINT message,WPARAM wp,LPARAM lp)
if(HIWORD(wp)==EN_CHANGE) filterOptions(dialog,*draft);
return TRUE;
case 279:
if(draft->page==PanelColors && draft->section>=0)
if(draft->page==PanelColors && draft->section>=0 && draft->section<ColorCount)
{
choosePanelColor(dialog,draft->palette[draft->section],draft->custom.data());
showOptionsPage(dialog,*draft,PanelColors,draft->section);
@@ -319,14 +379,57 @@ INT_PTR CALLBACK optionsDialog(HWND dialog,UINT message,WPARAM wp,LPARAM lp)
showOptionsPage(dialog,*draft,Appearance,0);
updateOptionsApply(dialog,*draft);
return TRUE;
case fileStyleState:
if(HIWORD(wp)==CBN_SELCHANGE) updateFileStyleEditor(dialog,*draft,false);
return TRUE;
case fileStyleRules:
if(HIWORD(wp)==CBN_SELCHANGE)
{
draft->styleIndex=static_cast<int>(SendDlgItemMessageW(dialog,fileStyleRules,CB_GETCURSEL,0,0));
updateFileStyleEditor(dialog,*draft,false);
}
return TRUE;
case 292:
if(addFileStyle(dialog,*draft)) updateOptionsApply(dialog,*draft);
return TRUE;
case 289: case 293: case 294: case 295: case 297: case 298:
if(draft->styleIndex>=0)
{
auto &style=draft->styles[draft->styleIndex];
const auto state=SendDlgItemMessageW(dialog,fileStyleState,CB_GETCURSEL,0,0);
auto &foreground=state>0?style.states[state-1].foreground:style.foreground;
auto &background=state>0?style.states[state-1].background:style.background;
switch(LOWORD(wp))
{
case 289:
draft->styles.erase(draft->styles.begin()+draft->styleIndex);
break;
case 293: case 294:
{
auto &field=LOWORD(wp)==293?foreground:background;
auto color=field.value_or(draft->palette[LOWORD(wp)==293?Text:PanelBlue]);
if(choosePanelColor(dialog,color,draft->custom.data())) field=color;
break;
}
case 295: foreground.reset();background.reset();break;
case 297: style.underline=IsDlgButtonChecked(dialog,297)==BST_CHECKED;break;
case 298: style.strikeout=IsDlgButtonChecked(dialog,298)==BST_CHECKED;break;
}
updateFileStyleEditor(dialog,*draft,LOWORD(wp)==289);
updateOptionsApply(dialog,*draft);
}
return TRUE;
case optionsReset:
if(draft->page==allOptions || draft->page==PanelModes)
for(const auto &property:panelProperties)
if(draft->section<0 || property.section==draft->section) resetPanelProperty(dialog,property);
if(draft->page==allOptions || (draft->page==PanelColors && draft->section<0))
draft->palette=defaultColors;
else if(draft->page==PanelColors)
else if(draft->page==PanelColors && draft->section< ColorCount)
draft->palette[draft->section]=defaultColors[draft->section];
if(draft->page==allOptions || (draft->page==PanelColors &&
(draft->section<0 || draft->section==fileStyleSection)))
draft->styles=defaultFileStyles;
if(draft->page==allOptions || draft->page==Appearance)
{
draft->font=defaultFilesFont();
@@ -367,12 +470,20 @@ INT_PTR CALLBACK optionsDialog(HWND dialog,UINT message,WPARAM wp,LPARAM lp)
return TRUE;
}
const auto owner=GetWindow(dialog,GW_OWNER);
const bool stylesChanged=draft->styles!=fileStyles;
if(stylesChanged && !saveFileStyles(draft->styles))
{
MessageBoxW(dialog,L"Could not save the file type styles.",L"KSwitcher Files Options",MB_OK|MB_ICONWARNING);
return TRUE;
}
const bool fontChanged=optionsFontChanged(*draft);
if(fontChanged && !applyFont(owner,draft->font,draft->points))
{
if(stylesChanged) saveFileStyles(fileStyles);
MessageBoxW(dialog,L"Could not apply the selected font.",optionsTitles[Appearance],MB_OK|MB_ICONWARNING);
return TRUE;
}
if(stylesChanged) fileStyles=draft->styles;
sizeWidth=static_cast<int>(width);
alignExtensions=IsDlgButtonChecked(dialog,202)==BST_CHECKED;
showDetails=IsDlgButtonChecked(dialog,203)==BST_CHECKED;
+139
View File
@@ -0,0 +1,139 @@
void paintOptionsPreview(HWND dialog,const DRAWITEMSTRUCT &item,const OptionsDraft &draft)
{
const auto dc=item.hDC;
const auto saved=SaveDC(dc);
if(!saved) return;
IntersectClipRect(dc,item.rcItem.left,item.rcItem.top,item.rcItem.right,item.rcItem.bottom);
const auto dpi=GetDpiForWindow(item.hwndItem);
const auto px=[&](int value){return MulDiv(value,dpi,96);};
auto font=draft.font;
font.lfHeight=-MulDiv(draft.points,dpi,720);
font.lfWidth=0;
const auto previewFont=CreateFontIndirectW(&font);
SelectObject(dc,previewFont?previewFont:mono);
TEXTMETRICW metrics{};
GetTextMetricsW(dc,&metrics);
const auto rowHeight=std::max<LONG>(px(18),metrics.tmHeight+px(4));
const auto &palette=draft.palette;
const auto fill=[&](RECT bounds,COLORREF color)
{
SetBkColor(dc,color);
ExtTextOutW(dc,0,0,ETO_OPAQUE|ETO_CLIPPED,&bounds,nullptr,0,nullptr);
};
const auto row=[&](RECT bounds,std::wstring_view label,COLORREF fg,COLORREF bg,bool right=false)
{
fill(bounds,bg);
bounds.left+=px(5);
bounds.right-=px(5);
SetBkMode(dc,TRANSPARENT);
SetTextColor(dc,fg);
DrawTextW(dc,label.data(),static_cast<int>(label.size()),&bounds,
DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX|DT_END_ELLIPSIS|(right?DT_RIGHT:DT_LEFT));
};
const auto checked=[&](int id){return IsDlgButtonChecked(dialog,id)==BST_CHECKED;};
auto bounds=item.rcItem;
fill(bounds,palette[PanelBlue]);
const auto frame=[&](RECT rect)
{
SetDCBrushColor(dc,palette[Frame]);
FrameRect(dc,&rect,static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
InflateRect(&rect,-px(2),-px(2));
FrameRect(dc,&rect,static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
};
const auto panel=[&](RECT panelBounds,bool wide,bool cursor,std::wstring_view path)
{
frame(panelBounds);
InflateRect(&panelBounds,-px(4),-px(4));
auto line=panelBounds;
line.bottom=line.top+rowHeight;
row(line,path,palette[Frame],palette[PanelBlue]);
OffsetRect(&line,0,rowHeight);
BOOL valid{};
const auto input=GetDlgItemInt(dialog,201,&valid,FALSE);
const auto columns=valid?std::clamp(input,6u,16u):static_cast<UINT>(sizeWidth);
const auto entry=[&](std::wstring_view stem,std::wstring_view ext,bool folder,bool highlight)
{
const auto bg=highlight?palette[PanelCursor]:palette[PanelBlue];
auto name=line;
if(wide)
{
name.right=std::max(name.left+px(20),name.right-static_cast<LONG>(columns+2)*metrics.tmAveCharWidth);
name.right=std::min(name.right,line.right);
auto amount=line;
amount.left=name.right;
row(amount,folder?L"Folder":formatSize(48382,columns),palette[Text],bg,true);
fill({amount.left,amount.top,amount.left+px(1),amount.bottom},palette[Frame]);
}
if(wide && checked(folder?204:202) && name.right-name.left>px(70))
{
auto extension=name;
extension.left=extension.right-std::min<LONG>(px(60),(extension.right-extension.left)/2);
name.right=extension.left;
row(extension,ext,palette[Text],bg,true);
row(name,stem,palette[Text],bg);
}
else row(name,std::wstring(stem)+L"."+std::wstring(ext),palette[Text],bg);
OffsetRect(&line,0,rowHeight);
};
entry(L"report",L"txt",false,cursor);
entry(L"examples",L"old",true,false);
};
if(draft.page==PanelColors && (draft.section==Dialog || draft.section==Selection))
{
InflateRect(&bounds,-px(12),-px(10));
const auto maxWidth=px(340);
bounds.right=std::min(bounds.right,bounds.left+maxWidth);
fill(bounds,palette[Dialog]);
frame(bounds);
InflateRect(&bounds,-px(5),-px(5));
auto line=bounds;
line.bottom=line.top+rowHeight;
row(line,L"Change drive",palette[Text],palette[Dialog]);
OffsetRect(&line,0,rowHeight);
row(line,L"C: fixed",palette[Text],palette[Selection]);
OffsetRect(&line,0,rowHeight);
row(line,L"D: fixed",palette[Text],palette[Dialog]);
}
else if(draft.page==PanelColors && draft.section==Footer)
{
auto bar=bounds;
bar.top=bar.bottom-rowHeight;
row(bar,L"F2 Refresh F3 View F5 Copy F6 Move",palette[Text],palette[Footer]);
bounds.bottom=bar.top;
panel(bounds,false,false,L"C:\\Example");
}
else if(draft.page==PanelModes && draft.section==PanelLayout)
{
auto left=bounds,right=bounds;
left.right=(bounds.left+bounds.right)/2;
right.left=left.right+px(4);
panel(left,checked(273),true,L"C:\\Example");
panel(right,checked(274),false,L"D:\\Example");
}
else if(draft.page==PanelModes && draft.section==StatusColumns)
{
auto statusRow=bounds;
statusRow.top=statusRow.bottom-rowHeight;
bounds.bottom=statusRow.top;
panel(bounds,true,true,L"C:\\Example");
std::wstring label;
if(checked(203))
{
constexpr const wchar_t *values[]{L"report.txt",L"47 KiB",L"2026-11-14",L"10:30"};
for(int i=0;i<4;++i)
if(checked(205+i))
{
if(!label.empty()) label+=L" ";
label+=values[i];
}
}
row(statusRow,label,palette[Text],palette[PanelBlue]);
}
else panel(bounds,draft.page==PanelModes || checked(273),
draft.page==PanelColors && draft.section==PanelCursor,L"C:\\Example");
RestoreDC(dc,saved);
if(previewFont) DeleteObject(previewFont);
}
+209
View File
@@ -0,0 +1,209 @@
constexpr int fileStyleSection=ColorCount;
constexpr int fileStyleRules=288,fileStyleSuggestion=291,fileStylePreview=299,fileStyleState=301;
constexpr const wchar_t *fileStyleStateNames[]{L"Normal",L"Marked (Insert)",L"Focused cursor",L"Unfocused cursor"};
void layoutFileStyleFields(HWND dialog,bool redraw)
{
RECT client{};
GetClientRect(dialog,&client);
RECT spacing{0,0,8,0};
MapDialogRect(dialog,&spacing);
const auto gap=spacing.right;
const auto dpi=GetDpiForWindow(dialog);
const auto arrow=GetSystemMetricsForDpi(SM_CXVSCROLL,dpi);
const auto border=GetSystemMetricsForDpi(SM_CXEDGE,dpi);
const auto flags=SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOREDRAW;
const auto fit=[&](int id,int buttonId)
{
const auto combo=GetDlgItem(dialog,id);
const auto bounds=dialogControlBounds(dialog,id);
const auto dc=GetDC(combo);
if(!dc) return;
const auto previous=SelectObject(dc,reinterpret_cast<HFONT>(SendMessageW(combo,WM_GETFONT,0,0)));
SIZE extent{};
const std::wstring_view example=L".example";
GetTextExtentPoint32W(dc,example.data(),static_cast<int>(example.size()),&extent);
LONG longest=extent.cx;
const auto count=SendMessageW(combo,CB_GETCOUNT,0,0);
for(LRESULT row=0;row<count;++row)
{
const auto length=SendMessageW(combo,CB_GETLBTEXTLEN,row,0);
if(length<=0) continue;
std::wstring item(static_cast<size_t>(length)+1,L'\0');
if(SendMessageW(combo,CB_GETLBTEXT,row,reinterpret_cast<LPARAM>(item.data()))!=CB_ERR &&
GetTextExtentPoint32W(dc,item.data(),static_cast<int>(length),&extent))
longest=std::max(longest,extent.cx);
}
if(previous) SelectObject(dc,previous);
ReleaseDC(combo,dc);
const auto button=buttonId?dialogControlBounds(dialog,buttonId):RECT{};
const auto buttonWidth=button.right-button.left;
const auto available=client.right-bounds.left-gap-(buttonId?gap+buttonWidth:0);
const auto width=std::max(1L,std::min(available,longest+arrow+2*border+gap));
RECT dropped{};
SendMessageW(combo,CB_GETDROPPEDCONTROLRECT,0,reinterpret_cast<LPARAM>(&dropped));
SetWindowPos(combo,nullptr,bounds.left,bounds.top,width,dropped.bottom-dropped.top,flags);
if(buttonId)
SetWindowPos(GetDlgItem(dialog,buttonId),nullptr,bounds.left+width+gap,button.top,
buttonWidth,button.bottom-button.top,flags);
};
fit(fileStyleRules,289);
fit(fileStyleSuggestion,292);
fit(fileStyleState,0);
if(redraw)
RedrawWindow(dialog,nullptr,nullptr,RDW_INVALIDATE|RDW_ERASE|RDW_ALLCHILDREN);
}
void fillFileStyleSuggestions(HWND dialog)
{
std::set<std::wstring> extensions;
for(DWORD i=0;;++i)
{
wchar_t name[256]{};
DWORD length=std::size(name);
const auto result=RegEnumKeyExW(HKEY_CLASSES_ROOT,i,name,&length,nullptr,nullptr,nullptr,nullptr);
if(result==ERROR_MORE_DATA) continue;
if(result!=ERROR_SUCCESS) break;
if(name[0]!=L'.') continue;
auto extension=fileStyleExtension(name);
if(!extension.empty()) extensions.insert(std::move(extension));
}
for(const auto &extension:extensions)
SendDlgItemMessageW(dialog,fileStyleSuggestion,CB_ADDSTRING,0,reinterpret_cast<LPARAM>(extension.c_str()));
SendDlgItemMessageW(dialog,fileStyleSuggestion,CB_LIMITTEXT,255,0);
for(const auto label:fileStyleStateNames)
SendDlgItemMessageW(dialog,fileStyleState,CB_ADDSTRING,0,reinterpret_cast<LPARAM>(label));
SendDlgItemMessageW(dialog,fileStyleState,CB_SETCURSEL,0,0);
}
void updateFileStyleEditor(HWND dialog,OptionsDraft &draft,bool rebuild)
{
const auto combo=GetDlgItem(dialog,fileStyleRules);
if(rebuild)
{
SendMessageW(combo,CB_RESETCONTENT,0,0);
for(const auto &style:draft.styles)
{
const auto label=style.extension==L"@system-hidden"?L"System and hidden":style.extension.c_str();
SendMessageW(combo,CB_ADDSTRING,0,reinterpret_cast<LPARAM>(label));
}
draft.styleIndex=draft.styles.empty()?-1:std::clamp(draft.styleIndex,0,static_cast<int>(draft.styles.size())-1);
SendMessageW(combo,CB_SETCURSEL,draft.styleIndex,0);
layoutFileStyleFields(dialog,true);
}
InvalidateRect(GetDlgItem(dialog,fileStylePreview),nullptr,FALSE);
const bool selected=draft.styleIndex>=0;
for(const int id:{289,293,294,295,297,298})
EnableWindow(GetDlgItem(dialog,id),selected);
if(!selected)
{
SetDlgItemTextW(dialog,296,L"");
CheckDlgButton(dialog,297,BST_UNCHECKED);
CheckDlgButton(dialog,298,BST_UNCHECKED);
return;
}
const auto &style=draft.styles[draft.styleIndex];
const auto colorLabel=[](std::optional<COLORREF> color)->std::wstring
{
if(!color) return L"Default";
wchar_t hex[8]{};
swprintf_s(hex,L"#%02X%02X%02X",unsigned(GetRValue(*color)),unsigned(GetGValue(*color)),unsigned(GetBValue(*color)));
return hex;
};
const auto state=SendDlgItemMessageW(dialog,fileStyleState,CB_GETCURSEL,0,0);
const auto fg=state>0?style.states[state-1].foreground:style.foreground;
const auto bg=state>0?style.states[state-1].background:style.background;
const auto label=L"Foreground: "+colorLabel(fg)+L" Background: "+colorLabel(bg);
SetDlgItemTextW(dialog,296,label.c_str());
CheckDlgButton(dialog,297,style.underline);
CheckDlgButton(dialog,298,style.strikeout);
}
bool addFileStyle(HWND dialog,OptionsDraft &draft)
{
wchar_t input[256]{};
GetDlgItemTextW(dialog,fileStyleSuggestion,input,std::size(input));
const auto extension=fileStyleExtension(input);
if(extension.empty())
{
MessageBoxW(dialog,L"Enter a file extension such as .txt.",L"File type styles",MB_OK|MB_ICONWARNING);
SetFocus(GetDlgItem(dialog,fileStyleSuggestion));
return false;
}
const auto existing=std::find_if(draft.styles.begin(),draft.styles.end(),
[&](const auto &style){return sameFileExtension(style.extension,extension);});
if(existing!=draft.styles.end()) draft.styleIndex=static_cast<int>(existing-draft.styles.begin());
else
{
if(draft.styles.size()>=64)
{
MessageBoxW(dialog,L"The profile can contain up to 64 file type rules.",L"File type styles",MB_OK|MB_ICONWARNING);
return false;
}
draft.styles.push_back({extension});
draft.styleIndex=static_cast<int>(draft.styles.size())-1;
}
SetDlgItemTextW(dialog,fileStyleSuggestion,L"");
updateFileStyleEditor(dialog,draft,true);
return true;
}
void paintFileStylePreview(const DRAWITEMSTRUCT &item,const OptionsDraft &draft)
{
const auto saved=SaveDC(item.hDC);
const bool selected=draft.styleIndex>=0 && static_cast<size_t>(draft.styleIndex)<draft.styles.size();
const auto extension=selected?std::wstring_view(draft.styles[draft.styleIndex].extension):std::wstring_view{};
const bool systemHidden=extension==L"@system-hidden";
SetBkColor(item.hDC,draft.palette[PanelBlue]);
ExtTextOutW(item.hDC,0,0,ETO_OPAQUE,&item.rcItem,nullptr,0,nullptr);
if(selected)
{
auto font=draft.font;
font.lfHeight=-MulDiv(draft.points,GetDpiForWindow(item.hwndItem),720);
font.lfWidth=0;
const auto fontHandle=CreateFontIndirectW(&font);
if(fontHandle) SelectObject(item.hDC,fontHandle);
TEXTMETRICW metrics{};
GetTextMetricsW(item.hDC,&metrics);
const int height=metrics.tmHeight+MulDiv(4,GetDpiForWindow(item.hwndItem),96);
if(fontHandle)
{
SelectObject(item.hDC,mono?mono:GetStockObject(SYSTEM_FONT));
DeleteObject(fontHandle);
}
IntersectClipRect(item.hDC,item.rcItem.left,item.rcItem.top,item.rcItem.right,item.rcItem.bottom);
for(int i=0;i<4;++i)
{
const auto state=static_cast<FileRowState>(i);
const auto background=draft.palette[state==FileRowState::Focused?PanelCursor:PanelBlue];
const auto style=resolveFileStyle(extension,!systemHidden,systemHidden,
state==FileRowState::Focused || state==FileRowState::Marked,
draft.palette[Text],background,draft.styles,state);
auto decorated=font;
if(style.decorations)
{
decorated.lfUnderline=(style.decorations&1)!=0;
decorated.lfStrikeOut=(style.decorations&2)!=0;
}
const auto previewFont=CreateFontIndirectW(&decorated);
const auto previous=SelectObject(item.hDC,previewFont?previewFont:mono);
auto row=item.rcItem;
row.top+=i*height;
row.bottom=row.top+height;
SetBkColor(item.hDC,style.background);
ExtTextOutW(item.hDC,0,0,ETO_OPAQUE,&row,nullptr,0,nullptr);
constexpr const wchar_t *samples[]{L"normal-sample",L"marked-sample",L"focused-cursor",L"unfocused-cursor"};
const auto label=std::wstring(samples[i])+(systemHidden?L" (hidden)":std::wstring(extension));
row.left+=5;
row.right-=5;
SetBkMode(item.hDC,TRANSPARENT);
SetTextColor(item.hDC,style.foreground);
DrawTextW(item.hDC,label.c_str(),static_cast<int>(label.size()),&row,
DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX|DT_END_ELLIPSIS);
SelectObject(item.hDC,previous);
if(previewFont) DeleteObject(previewFont);
}
}
RestoreDC(item.hDC,saved);
}
+180
View File
@@ -0,0 +1,180 @@
enum class FileRowState { Normal, Marked, Focused, Unfocused };
struct FileStyleColors
{
std::optional<COLORREF> foreground,background;
bool operator==(const FileStyleColors &) const = default;
};
struct FileStyle
{
std::wstring extension;
std::optional<COLORREF> foreground,background;
bool underline{},strikeout{};
std::array<FileStyleColors,3> states{};
bool operator==(const FileStyle &) const = default;
};
const std::vector<FileStyle> defaultFileStyles{
{L"@system-hidden"},{L".exe",RGB(85,255,85)},{L".md",RGB(231,192,247)},
{L".7z",RGB(255,210,128)},{L".zip",RGB(255,210,128)},{L".rar",RGB(255,210,128)},
{L".tar",RGB(255,210,128)},{L".gz",RGB(255,210,128)},
{L".png",RGB(165,230,255)},{L".jpg",RGB(165,230,255)},{L".jpeg",RGB(165,230,255)},
{L".gif",RGB(165,230,255)},{L".webp",RGB(165,230,255)},{L".svg",RGB(165,230,255)},
{L".mp3",RGB(255,185,216)},{L".wav",RGB(255,185,216)},{L".flac",RGB(255,185,216)},
{L".mp4",RGB(255,185,216)},{L".mkv",RGB(255,185,216)},{L".mov",RGB(255,185,216)},
{L".pdf",RGB(255,229,168)},{L".docx",RGB(255,229,168)},{L".xlsx",RGB(255,229,168)},
{L".pptx",RGB(255,229,168)},{L".odt",RGB(255,229,168)},
{L".cpp",RGB(168,247,190)},{L".h",RGB(168,247,190)},{L".cs",RGB(168,247,190)},
{L".py",RGB(168,247,190)},{L".js",RGB(168,247,190)},{L".ts",RGB(168,247,190)},
{L".ps1",RGB(168,247,190)},{L".bat",RGB(168,247,190)},{L".cmd",RGB(168,247,190)}};
std::vector<FileStyle> fileStyles=defaultFileStyles;
constexpr wchar_t fileStyleKey[]=L"FileStyles";
std::wstring fileStyleExtension(std::wstring_view text)
{
const auto first=text.find_first_not_of(L" \t");
if(first==text.npos) return {};
auto extension=std::wstring(text.substr(first,text.find_last_not_of(L" \t")-first+1));
if(extension.front()!=L'.') extension.insert(0,1,L'.');
if(extension.size()<2 || extension.size()>255 ||
extension.find_first_of(L". *?\\/:<>|\";\r\n\t",1)!=extension.npos) return {};
CharLowerBuffW(extension.data(),static_cast<DWORD>(extension.size()));
return extension;
}
bool sameFileExtension(std::wstring_view left,std::wstring_view right)
{
return !left.empty() && left.size()==right.size() &&
CompareStringOrdinal(left.data(),static_cast<int>(left.size()),
right.data(),static_cast<int>(right.size()),TRUE)==CSTR_EQUAL;
}
void loadFileStyles()
{
DWORD bytes{};
if(RegGetValueW(HKEY_CURRENT_USER,schemeKey,fileStyleKey,RRF_RT_REG_BINARY,nullptr,nullptr,&bytes)!=ERROR_SUCCESS ||
!bytes || bytes>256*1024) return;
std::string data(bytes,'\0');
if(RegGetValueW(HKEY_CURRENT_USER,schemeKey,fileStyleKey,RRF_RT_REG_BINARY,nullptr,data.data(),&bytes)!=ERROR_SUCCESS)
return;
try
{
const auto rows=Json::parse(data.begin(),data.begin()+bytes);
if(!rows.is_array() || rows.size()>64) return;
std::vector<FileStyle> parsed;
for(const auto &row:rows)
{
FileStyle style{ks::wide(row.at("extension").get<std::string>())};
if(style.extension!=L"@system-hidden" && fileStyleExtension(style.extension)!=style.extension) return;
if(std::any_of(parsed.begin(),parsed.end(),[&](const auto &existing){return existing.extension==style.extension;}))
return;
const auto color=[&](const char *key)->std::optional<COLORREF>
{
if(!row.contains(key)) return {};
const auto value=row.at(key).get<DWORD>();
if(value>0xffffff) throw std::runtime_error("Invalid file style color");
return value;
};
style.foreground=color("foreground");
style.background=color("background");
style.underline=row.value("underline",false);
style.strikeout=row.value("strikeout",false);
if(row.contains("states"))
{
const auto &states=row.at("states");
if(!states.is_array() || states.size()!=style.states.size()) return;
for(size_t i=0;i<style.states.size();++i)
{
const auto read=[&](const char *key)->std::optional<COLORREF>
{
if(!states[i].contains(key)) return {};
const auto value=states[i].at(key).get<DWORD>();
if(value>0xffffff) throw std::runtime_error("Invalid state color");
return value;
};
style.states[i]={read("foreground"),read("background")};
}
}
parsed.push_back(std::move(style));
}
fileStyles=std::move(parsed);
}
catch(const std::exception &) {}
}
bool saveFileStyles(const std::vector<FileStyle> &styles)
{
auto rows=Json::array();
for(const auto &style:styles)
{
Json row{{"extension",ks::utf8(style.extension)},{"underline",style.underline},{"strikeout",style.strikeout}};
if(style.foreground) row["foreground"]=*style.foreground;
if(style.background) row["background"]=*style.background;
row["states"]=Json::array();
for(const auto &state:style.states)
{
auto entry=Json::object();
if(state.foreground) entry["foreground"]=*state.foreground;
if(state.background) entry["background"]=*state.background;
row["states"].push_back(std::move(entry));
}
rows.push_back(std::move(row));
}
const auto data=rows.dump();
return RegSetKeyValueW(HKEY_CURRENT_USER,schemeKey,fileStyleKey,REG_BINARY,data.data(),
static_cast<DWORD>(data.size()))==ERROR_SUCCESS;
}
struct FileRowStyle
{
COLORREF foreground,background;
unsigned decorations{};
};
FileRowStyle resolveFileStyle(std::wstring_view extension,bool file,bool dimmed,bool protectedRow,
COLORREF foreground,COLORREF background,const std::vector<FileStyle> &styles=fileStyles,
FileRowState state=FileRowState::Normal,bool transferred=false)
{
FileRowStyle result{foreground,background};
const FileStyle *extensionRule=nullptr,*attributeRule=nullptr;
for(const auto &style:styles)
{
if(!file || !sameFileExtension(style.extension,extension)) continue;
extensionRule=&style;
result.foreground=style.foreground.value_or(result.foreground);
if(!protectedRow) result.background=style.background.value_or(result.background);
result.decorations=(style.underline?1u:0u)|(style.strikeout?2u:0u);
break;
}
if(dimmed)
for(const auto &style:styles)
{
if(style.extension!=L"@system-hidden") continue;
attributeRule=&style;
if(style.foreground) result.foreground=*style.foreground;
else
{
const auto fg=result.foreground,bg=result.background;
result.foreground=RGB((3*GetRValue(fg)+2*GetRValue(bg))/5,
(3*GetGValue(fg)+2*GetGValue(bg))/5,(3*GetBValue(fg)+2*GetBValue(bg))/5);
}
if(!protectedRow) result.background=style.background.value_or(result.background);
result.decorations|=(style.underline?1u:0u)|(style.strikeout?2u:0u);
break;
}
if(transferred && state!=FileRowState::Marked)
return {RGB(255,223,115),background,result.decorations};
if(state==FileRowState::Marked) result.foreground=RGB(255,255,0);
if(state!=FileRowState::Normal)
{
const auto index=static_cast<size_t>(state)-1;
for(const auto *rule:{extensionRule,attributeRule})
if(rule)
{
result.foreground=rule->states[index].foreground.value_or(result.foreground);
result.background=rule->states[index].background.value_or(result.background);
}
}
if(result.foreground==result.background && state!=FileRowState::Normal)
result.foreground=GetRValue(result.background)+GetGValue(result.background)+GetBValue(result.background)>382?RGB(0,0,0):RGB(255,255,255);
return result;
}