Release v0.6.48: chat language profiles and tabbed settings

This commit is contained in:
agrynco committed 2026-11-05 23:25:33 +02:00
1 parent e33387c2d5
commit 4e7a683c47
20 files changed
+729 -70

No files matched your search

+212 -46
View File
@@ -967,7 +967,127 @@ struct SettingsView
std::vector<int> order;
int selection = -1;
bool filling{};
std::vector<std::vector<HWND>> pages;
};
void selectSettingsPage(HWND w, SettingsView *v)
{
int selected = TabCtrl_GetCurSel(GetDlgItem(w, 260));
for (size_t page = 0; page < v->pages.size(); ++page)
for (HWND child : v->pages[page]) ShowWindow(child, (int)page == selected ? SW_SHOWNA : SW_HIDE);
InvalidateRect(w, nullptr, TRUE);
}
struct ChatLanguagesView
{
HWND parent{};
SettingsView *settings{};
std::vector<std::wstring> keys;
};
void fillChatLanguages(HWND w, ChatLanguagesView *v)
{
int index = (int)SendDlgItemMessageW(w, 710, LB_GETCURSEL, 0, 0);
if (index < 0 || index >= (int)v->keys.size()) return;
const auto &draft = v->settings->draft;
auto saved = draft.chatCycles.find(v->keys[index]);
const auto &cycle = saved == draft.chatCycles.end() ? draft.cycle : saved->second;
for (size_t i = 0; i < v->settings->order.size(); ++i)
{
auto &layout = v->settings->app->layouts[v->settings->order[i]];
SendDlgItemMessageW(w, 711, LB_SETSEL,
std::find(cycle.begin(), cycle.end(), layout.id) != cycle.end(), i);
}
value(w, 712, saved == draft.chatCycles.end() ? L"No profile yet. Select the languages for this chat." :
L"Saved profile. Select languages, or remove it to use the defaults.");
EnableWindow(GetDlgItem(w, 713), saved != draft.chatCycles.end());
}
LRESULT CALLBACK chatLanguagesProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
auto *v = (ChatLanguagesView *)GetWindowLongPtrW(w, GWLP_USERDATA);
if (!v) return DefWindowProcW(w, msg, wp, lp);
try
{
if (msg == WM_COMMAND)
{
int id = LOWORD(wp);
if (id == 710 && HIWORD(wp) == LBN_SELCHANGE) { fillChatLanguages(w, v); return 0; }
if (id == IDOK || id == 713)
{
int selected = (int)SendDlgItemMessageW(w, 710, LB_GETCURSEL, 0, 0);
if (selected < 0 || selected >= (int)v->keys.size())
throw std::runtime_error("Open a Telegram or Teams chat, then reopen this dialog.");
auto next = v->settings->draft;
if (id == 713) next.chatCycles.erase(v->keys[selected]);
else
{
std::vector<std::wstring> cycle;
for (size_t i = 0; i < v->settings->order.size(); ++i)
if (SendDlgItemMessageW(w, 711, LB_GETSEL, i, 0) > 0)
cycle.push_back(v->settings->app->layouts[v->settings->order[i]].id);
if (cycle.empty()) throw std::runtime_error("Select at least one language.");
next.chatCycles[v->keys[selected]] = std::move(cycle);
}
validateSettings(next);
v->settings->draft.chatCycles = std::move(next.chatCycles);
DestroyWindow(w);
return 0;
}
if (id == IDCANCEL) { DestroyWindow(w); return 0; }
}
if (msg == WM_CLOSE) { DestroyWindow(w); return 0; }
if (msg == WM_DPICHANGED) { resizeForDpi(w, wp, lp); return 0; }
if (msg == WM_NCDESTROY)
{
EnableWindow(v->parent, TRUE);
SetForegroundWindow(v->parent);
disposeFont(w);
delete v;
SetWindowLongPtrW(w, GWLP_USERDATA, 0);
}
}
catch (const std::exception &e) { error(w, e); }
return DefWindowProcW(w, msg, wp, lp);
}
void showChatLanguages(HWND parent, SettingsView *settings)
{
std::set<std::wstring> keys;
for (const auto &[key, cycle] : settings->draft.chatCycles) keys.insert(key);
EnumWindows([](HWND candidate, LPARAM context) -> BOOL
{
if (!IsWindowVisible(candidate)) return TRUE;
auto key = windowChatKey(candidate);
if (!key.empty()) ((std::set<std::wstring> *)context)->insert(std::move(key));
return TRUE;
}, (LPARAM)&keys);
HWND w = makeWindow(*settings->app, L"KSwitcher.ChatLanguages", L"Languages for a chat", chatLanguagesProc, 640, 524);
auto *v = new ChatLanguagesView{parent, settings, {keys.begin(), keys.end()}};
SetWindowLongPtrW(w, GWLP_HWNDPARENT, (LONG_PTR)parent);
SetWindowLongPtrW(w, GWLP_USERDATA, (LONG_PTR)v);
label(w, L"Saved profiles and currently open Telegram / Teams chats", 18, 16, 604);
control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER,
710, 18, 44, 604, 140);
for (const auto &key : v->keys)
{
auto split = key.find(L'\n');
auto name = std::wstring(key.starts_with(L"telegram\n") ? L"Telegram: " : L"Teams: ") + key.substr(split + 1);
SendDlgItemMessageW(w, 710, LB_ADDSTRING, 0, (LPARAM)name.c_str());
}
control(w, L"STATIC", L"Open a chat, then reopen this dialog to add it.", 0, 712, 18, 194, 604, 26);
label(w, L"Click languages to include (saved in the order shown)", 18, 228, 604);
control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_MULTIPLESEL | LBS_NOINTEGRALHEIGHT | WS_BORDER,
711, 18, 256, 604, 132);
for (int i : settings->order)
SendDlgItemMessageW(w, 711, LB_ADDSTRING, 0, (LPARAM)settings->app->layouts[i].name.c_str());
label(w, L"Applies to Alt+Shift, Win+Space and KSwitcher Next layout.", 18, 402, 604);
label(w, L"Matched by chat name. Identical names share a profile. Then Save settings.", 18, 430, 604);
button(w, L"&Remove profile", 713, 18, 476, 170);
button(w, L"&Use languages", IDOK, 300, 476, 170);
button(w, L"&Cancel", IDCANCEL, 486, 476, 136);
EnableWindow(GetDlgItem(w, IDOK), !keys.empty());
EnableWindow(GetDlgItem(w, 713), FALSE);
if (!keys.empty()) { SendDlgItemMessageW(w, 710, LB_SETCURSEL, 0, 0); fillChatLanguages(w, v); }
EnableWindow(parent, FALSE);
showRequestedWindow(w);
SetFocus(GetDlgItem(w, 710));
}
void collectLayout(HWND w, SettingsView *v)
{
if (v->selection < 0 || v->selection >= (int)v->order.size())
@@ -1461,6 +1581,13 @@ LRESULT CALLBACK settingsProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_NOTIFY:
if (((NMHDR *)lp)->idFrom == 260 && ((NMHDR *)lp)->code == TCN_SELCHANGE)
{
selectSettingsPage(w, v);
return 0;
}
break;
case WM_COMMAND:
{
int id = LOWORD(wp);
@@ -1477,6 +1604,7 @@ LRESULT CALLBACK settingsProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
}
if (id == 257) { showOcrOptions(w, v); return 0; }
if (id == 258) { showSyncOptions(w, *v->app); return 0; }
if (id == 259) { collectLayout(w, v); showChatLanguages(w, v); return 0; }
if (id == 230)
{
collectLayout(w, v);
@@ -1815,56 +1943,94 @@ void showSettings(App &app, bool visible)
activateWindow(target);
return;
}
HWND w = makeWindow(app, L"KSwitcher.Settings", L"KSwitcher settings", settingsProc, 780, 660, 0, false);
HWND w = makeWindow(app, L"KSwitcher.Settings", L"KSwitcher settings", settingsProc, 720, 510, 0, false);
app.settingsWindow = w;
label(w, L"Text conversion", 18, 16);
checkbox(w, L"Con&vert recently typed text when nothing is selected", 201, 18, 43, 390);
checkbox(w, L"Switch Windows input &layout after conversion", 202, 18, 70, 390);
checkbox(w, L"Record clip&board history", 203, 18, 106);
checkbox(w, L"Start with &Windows", 204, 18, 133);
checkbox(w, L"Start with Windows as ad&ministrator", 206, 18, 160, 395);
checkbox(w, L"&Keep screen awake", 205, 18, 184, 210);
checkbox(w, L"Write lo&g", 208, 240, 184, 160);
label(w, L"Click a shortcut and press the desired keys", 420, 16, 350);
const wchar_t *names[] = {L"Next layout", L"Change case", L"Transliterate", L"Undo",
L"Clipboard history", L"Screen capture"};
auto *v = (SettingsView *)GetWindowLongPtrW(w, GWLP_USERDATA);
INITCOMMONCONTROLSEX tabsInit{sizeof(tabsInit), ICC_TAB_CLASSES};
InitCommonControlsEx(&tabsInit);
HWND tabs = control(w, WC_TABCONTROLW, L"", WS_TABSTOP, 260, 18, 16, 684, 32);
const wchar_t *tabNames[] = {L"General", L"Languages", L"Shortcuts", L"Clipboard", L"Tools"};
for (int i = 0; i < 5; ++i)
{
TCITEMW item{}; item.mask = TCIF_TEXT; item.pszText = const_cast<wchar_t *>(tabNames[i]);
TabCtrl_InsertItem(tabs, i, &item);
}
size_t start = windowLayoutState(w)->controls.size();
auto page = [&]
{
std::vector<HWND> children;
const auto &controls = windowLayoutState(w)->controls;
for (size_t i = start; i < controls.size(); ++i) children.push_back(controls[i].first);
v->pages.push_back(std::move(children));
start = controls.size();
};
label(w, L"Text conversion", 24, 76, 650);
checkbox(w, L"Con&vert recently typed text when nothing is selected", 201, 24, 110, 660);
checkbox(w, L"Switch Windows input &layout after conversion", 202, 24, 146, 660);
checkbox(w, L"Automatic correction (e&xperimental)", 207, 24, 182, 390);
button(w, L"&Correction options...", 255, 454, 180, 230);
label(w, L"Windows integration", 24, 254, 650);
checkbox(w, L"Start with &Windows", 204, 24, 288, 660);
checkbox(w, L"Start with Windows as ad&ministrator", 206, 24, 324, 660);
checkbox(w, L"&Keep screen awake", 205, 24, 360, 660);
page();
label(w, L"Default language cycle — top to bottom", 24, 76, 650);
control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER,
220, 24, 108, 410, 240);
button(w, L"Move u&p", 232, 454, 108, 110);
button(w, L"Move &down", 233, 574, 108, 110);
checkbox(w, L"&Include in cycle", 230, 454, 154, 230);
label(w, L"Direct layout shortcut", 454, 206, 230);
hotkeyEdit(app, w, 231, 454, 234, 230);
label(w, L"Backspace / Delete clears it", 454, 270, 230);
button(w, L"Re&fresh layouts", 253, 454, 322, 230);
button(w, L"Languages for a &chat...", 259, 24, 378, 230);
label(w, L"Choose a separate set for each Telegram or Teams chat.", 272, 382, 412);
page();
label(w, L"Click a shortcut field and press the desired keys", 24, 76, 660);
const wchar_t *names[] = {L"Next layout", L"Change case", L"Transliterate", L"Undo", L"Clipboard history", L"Screen capture"};
for (int i = 0; i < 6; ++i)
{
label(w, names[i], 420, 48 + i * 30, 115);
hotkeyEdit(app, w, 210 + i, 540, 44 + i * 30, 220);
label(w, names[i], 24, 116 + i * 40, 180);
hotkeyEdit(app, w, 210 + i, 230, 112 + i * 40, 454);
}
checkbox(w, L"Open editor after screen capt&ure", 209, 420, 224, 340);
checkbox(w, L"Automatic correction (e&xperimental)", 207, 18, 210, 395);
label(w, L"Layout cycle — top to bottom", 18, 240, 400);
control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER, 220,
18, 262, 470, 172);
button(w, L"Move u&p", 232, 504, 262, 120);
button(w, L"Move &down", 233, 632, 262, 128);
checkbox(w, L"&Include in cycle", 230, 504, 300, 240);
label(w, L"Direct layout shortcut", 504, 332, 240);
hotkeyEdit(app, w, 231, 504, 356, 256);
label(w, L"Backspace / Delete clears a shortcut", 504, 382, 256);
button(w, L"Re&fresh layouts", 253, 504, 408, 150);
label(w, L"History limits", 18, 444);
label(w, L"Items", 18, 478, 50);
edit(w, 240, 70, 474, 90);
label(w, L"Total MiB", 180, 478, 75);
edit(w, 241, 260, 474, 90);
label(w, L"Per item MiB", 370, 478, 100);
edit(w, 242, 475, 474, 80);
button(w, L"Clear &history…", 254, 580, 474, 180);
label(w, L"Exclude applications (process names, comma-separated)", 18, 516, 700);
edit(w, 243, 18, 544, 742);
button(w, L"&Auto-correction options…", 255, 18, 575, 240);
button(w, L"View l&og…", 256, 278, 575, 220);
button(w, L"OCR op&tions…", 257, 518, 575, 242);
button(w, L"Impo&rt settings…", 250, 18, 606, 145);
button(w, L"&Export settings…", 251, 173, 606, 145);
button(w, L"S&nippets…", 252, 328, 606, 110);
button(w, L"S&ync...", 258, 448, 606, 90);
button(w, L"&Save", IDOK, 548, 606, 100);
button(w, L"&Cancel", IDCANCEL, 660, 606, 100);
fillSettings(w, (SettingsView *)GetWindowLongPtrW(w, GWLP_USERDATA));
checkbox(w, L"Open editor after screen capt&ure", 209, 24, 378, 660);
page();
checkbox(w, L"Record clip&board history", 203, 24, 80, 660);
label(w, L"History limits", 24, 140, 660);
label(w, L"Items", 24, 176, 180);
label(w, L"Total MiB", 254, 176, 180);
label(w, L"Per item MiB", 484, 176, 180);
edit(w, 240, 24, 204, 200);
edit(w, 241, 254, 204, 200);
edit(w, 242, 484, 204, 200);
button(w, L"Clear &history...", 254, 24, 258, 200);
label(w, L"Exclude applications (process names, comma-separated)", 24, 330, 660);
edit(w, 243, 24, 362, 660);
page();
label(w, L"Additional tools and preferences", 24, 76, 660);
button(w, L"S&nippets...", 252, 24, 116, 210);
label(w, L"Manage reusable text and expansion triggers.", 254, 120, 430);
button(w, L"OCR op&tions...", 257, 24, 180, 210);
label(w, L"Configure text recognition from images.", 254, 184, 430);
button(w, L"S&ync...", 258, 24, 244, 210);
label(w, L"Synchronize preferences between computers.", 254, 248, 430);
button(w, L"View l&og...", 256, 24, 308, 210);
label(w, L"Inspect recent operations and diagnostics.", 254, 312, 430);
checkbox(w, L"Write lo&g", 208, 24, 378, 210);
page();
button(w, L"Impo&rt...", 250, 18, 466, 125);
button(w, L"&Export...", 251, 155, 466, 125);
button(w, L"&Save", IDOK, 490, 466, 100);
button(w, L"&Cancel", IDCANCEL, 602, 466, 100);
fillSettings(w, v);
TabCtrl_SetCurSel(tabs, 0);
selectSettingsPage(w, v);
if (visible)
{
showRequestedWindow(w);