Add configurable offline layout correction and latest Windows release
This commit is contained in:
+170
-5
@@ -8,6 +8,7 @@
|
||||
#include <map>
|
||||
#include <richedit.h>
|
||||
#include <set>
|
||||
#include <shellapi.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace ks
|
||||
@@ -648,6 +649,7 @@ void fillSettings(HWND w, SettingsView *v)
|
||||
check(w, 204, s.autostart);
|
||||
check(w, 206, s.elevatedAutostart);
|
||||
check(w, 205, s.awake);
|
||||
check(w, 207, s.autoCorrect);
|
||||
value(w, 210, s.toggle);
|
||||
value(w, 211, s.casing);
|
||||
value(w, 212, s.translit);
|
||||
@@ -695,6 +697,7 @@ Settings collectSettings(HWND w, SettingsView *v)
|
||||
s.autostart = checked(w, 204);
|
||||
s.elevatedAutostart = checked(w, 206);
|
||||
s.awake = checked(w, 205);
|
||||
s.autoCorrect = checked(w, 207);
|
||||
s.toggle = value(w, 210);
|
||||
s.casing = value(w, 211);
|
||||
s.translit = value(w, 212);
|
||||
@@ -723,6 +726,158 @@ Settings collectSettings(HWND w, SettingsView *v)
|
||||
validateSettings(s);
|
||||
return s;
|
||||
}
|
||||
struct AutoOptionsView
|
||||
{
|
||||
HWND parent;
|
||||
SettingsView *settings;
|
||||
};
|
||||
std::wstring commaList(const std::vector<std::wstring> &items)
|
||||
{
|
||||
std::wstring text;
|
||||
for (auto &item : items)
|
||||
{
|
||||
if (!text.empty())
|
||||
text += L", ";
|
||||
text += item;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
std::vector<std::wstring> parseCommaList(const std::wstring &text)
|
||||
{
|
||||
std::vector<std::wstring> items;
|
||||
std::wistringstream input(text);
|
||||
std::wstring item;
|
||||
while (std::getline(input, item, L','))
|
||||
{
|
||||
auto first = item.find_first_not_of(L" \t\r\n"), last = item.find_last_not_of(L" \t\r\n");
|
||||
if (first != item.npos)
|
||||
items.push_back(item.substr(first, last - first + 1));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
LRESULT CALLBACK autoOptionsProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
||||
{
|
||||
auto *view = (AutoOptionsView *)GetWindowLongPtrW(w, GWLP_USERDATA);
|
||||
if (!view)
|
||||
return DefWindowProcW(w, msg, wp, lp);
|
||||
if (msg == WM_COMMAND && LOWORD(wp) == 407)
|
||||
{
|
||||
auto folder = std::filesystem::path(executable()).parent_path() / L"dictionaries";
|
||||
if ((INT_PTR)ShellExecuteW(w, L"open", folder.c_str(), nullptr, nullptr, SW_SHOWNORMAL) <= 32)
|
||||
MessageBoxW(w, L"Could not open the dictionary folder.", L"KSwitcher", MB_OK | MB_ICONERROR);
|
||||
return 0;
|
||||
}
|
||||
if (msg == WM_COMMAND && LOWORD(wp) == IDOK)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto draft = view->settings->draft;
|
||||
auto lengthText = value(w, 410);
|
||||
if (lengthText.empty() || lengthText.size() > 2 ||
|
||||
lengthText.find_first_not_of(L"0123456789") != lengthText.npos)
|
||||
throw std::runtime_error("Minimum word length must be between 1 and 32 letters");
|
||||
draft.autoMinLength = std::stoi(lengthText);
|
||||
auto rankText = value(w, 406);
|
||||
if (rankText.empty() || rankText.find_first_not_of(L"0123456789") != rankText.npos)
|
||||
throw std::runtime_error("Enter a non-negative whole number for the frequency rank limit");
|
||||
try
|
||||
{
|
||||
draft.autoMaxRank = std::stoi(rankText);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
throw std::runtime_error("Frequency rank limit is too large");
|
||||
}
|
||||
draft.autoLanguages.clear();
|
||||
if (checked(w, 401))
|
||||
draft.autoLanguages.push_back(L"en");
|
||||
if (checked(w, 402))
|
||||
draft.autoLanguages.push_back(L"uk");
|
||||
if (checked(w, 403))
|
||||
draft.autoLanguages.push_back(L"ru");
|
||||
draft.autoNever = parseCommaList(value(w, 405));
|
||||
draft.autoExcluded = parseCommaList(value(w, 404));
|
||||
draft.autoPrefixChars = value(w, 408);
|
||||
draft.autoSuffixChars = value(w, 409);
|
||||
if (draft.autoLanguages.size() < 2)
|
||||
throw std::runtime_error("Select at least two languages");
|
||||
validateSettings(draft);
|
||||
view->settings->draft.autoLanguages = draft.autoLanguages;
|
||||
view->settings->draft.autoNever = draft.autoNever;
|
||||
view->settings->draft.autoExcluded = draft.autoExcluded;
|
||||
view->settings->draft.autoMaxRank = draft.autoMaxRank;
|
||||
view->settings->draft.autoMinLength = draft.autoMinLength;
|
||||
view->settings->draft.autoPrefixChars = draft.autoPrefixChars;
|
||||
view->settings->draft.autoSuffixChars = draft.autoSuffixChars;
|
||||
DestroyWindow(w);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
error(w, e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if ((msg == WM_COMMAND && LOWORD(wp) == IDCANCEL) || msg == WM_CLOSE)
|
||||
{
|
||||
DestroyWindow(w);
|
||||
return 0;
|
||||
}
|
||||
if (msg == WM_DPICHANGED)
|
||||
{
|
||||
resizeForDpi(w, wp, lp);
|
||||
return 0;
|
||||
}
|
||||
if (msg == WM_NCDESTROY)
|
||||
{
|
||||
EnableWindow(view->parent, TRUE);
|
||||
SetForegroundWindow(view->parent);
|
||||
disposeFont(w);
|
||||
delete view;
|
||||
SetWindowLongPtrW(w, GWLP_USERDATA, 0);
|
||||
}
|
||||
return DefWindowProcW(w, msg, wp, lp);
|
||||
}
|
||||
void showAutoOptions(HWND parent, SettingsView *v)
|
||||
{
|
||||
HWND w = makeWindow(*v->app, L"KSwitcher.AutoOptions", L"Automatic layout correction", autoOptionsProc,
|
||||
560, 680);
|
||||
SetWindowLongPtrW(w, GWLP_HWNDPARENT, (LONG_PTR)parent);
|
||||
SetWindowLongPtrW(w, GWLP_USERDATA, (LONG_PTR) new AutoOptionsView{parent, v});
|
||||
label(w, L"Offline dictionaries (only layouts in your cycle are used)", 18, 16, 525);
|
||||
checkbox(w, L"English", 401, 18, 48, 150);
|
||||
checkbox(w, L"Ukrainian", 402, 188, 48, 150);
|
||||
checkbox(w, L"Russian", 403, 358, 48, 150);
|
||||
for (auto &lang : v->draft.autoLanguages)
|
||||
check(w, lang == L"en" ? 401 : lang == L"uk" ? 402 : 403, true);
|
||||
label(w, L"Excluded applications (process names, comma-separated)", 18, 94, 525);
|
||||
edit(w, 404, 18, 122, 524, 84, ES_MULTILINE | WS_VSCROLL | ES_AUTOVSCROLL);
|
||||
value(w, 404, commaList(v->draft.autoExcluded));
|
||||
label(w, L"Personal dictionary: never correct these words (comma-separated)", 18, 220, 525);
|
||||
edit(w, 405, 18, 248, 524, 84, ES_MULTILINE | WS_VSCROLL | ES_AUTOVSCROLL);
|
||||
value(w, 405, commaList(v->draft.autoNever));
|
||||
label(w, L"Corrects after Space and a short pause.", 18, 344, 525);
|
||||
label(w, L"Undo skips this word until KSwitcher restarts.", 18, 366, 525);
|
||||
label(w, L"Frequency rank limit (0 = entire dictionary)", 18, 400, 390);
|
||||
edit(w, 406, 420, 396, 122, 26, ES_NUMBER);
|
||||
value(w, 406, std::to_wstring(v->draft.autoMaxRank));
|
||||
label(w, L"Minimum word length (1–32 letters)", 18, 434, 390);
|
||||
edit(w, 410, 420, 430, 122, 26, ES_NUMBER);
|
||||
value(w, 410, std::to_wstring(v->draft.autoMinLength));
|
||||
label(w, L"Characters before a word", 18, 474, 250);
|
||||
label(w, L"Characters after a word", 290, 474, 250);
|
||||
edit(w, 408, 18, 500, 252);
|
||||
edit(w, 409, 290, 500, 252);
|
||||
value(w, 408, v->draft.autoPrefixChars);
|
||||
value(w, 409, v->draft.autoSuffixChars);
|
||||
label(w, L"Enter punctuation without spaces or separators. These characters are kept.", 18, 530, 525);
|
||||
button(w, L"Open dictionary folder", 407, 18, 562, 220);
|
||||
label(w, L"After editing files, click OK here, then Save in Settings to reload.", 18, 600, 525);
|
||||
button(w, L"OK", IDOK, 326, 632, 100);
|
||||
button(w, L"Cancel", IDCANCEL, 440, 632, 100);
|
||||
EnableWindow(parent, FALSE);
|
||||
showRequestedWindow(w);
|
||||
SetForegroundWindow(w);
|
||||
}
|
||||
LRESULT CALLBACK settingsProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
||||
{
|
||||
if (msg == WM_DPICHANGED)
|
||||
@@ -802,6 +957,11 @@ LRESULT CALLBACK settingsProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
||||
saveSettings(path, s);
|
||||
return 0;
|
||||
}
|
||||
if (id == 255)
|
||||
{
|
||||
showAutoOptions(w, v);
|
||||
return 0;
|
||||
}
|
||||
if (id == 252)
|
||||
{
|
||||
showSnippets(*v->app);
|
||||
@@ -1040,7 +1200,10 @@ void showSettings(App &app)
|
||||
if (app.settingsWindow)
|
||||
{
|
||||
ShowWindow(app.settingsWindow, SW_RESTORE);
|
||||
SetForegroundWindow(app.settingsWindow);
|
||||
HWND target = app.settingsWindow;
|
||||
if (!IsWindowEnabled(target))
|
||||
target = GetLastActivePopup(target);
|
||||
activateWindow(target);
|
||||
return;
|
||||
}
|
||||
HWND w = makeWindow(app, L"KSwitcher.Settings", L"KSwitcher settings", settingsProc, 780, 660);
|
||||
@@ -1060,6 +1223,7 @@ void showSettings(App &app)
|
||||
label(w, names[i], 420, 48 + i * 30, 115);
|
||||
hotkeyEdit(app, w, 210 + i, 540, 44 + i * 30, 220);
|
||||
}
|
||||
checkbox(w, L"Automatically correct layout (experimental)", 207, 420, 195, 350);
|
||||
label(w, L"Layout cycle — top to bottom", 18, 210, 400);
|
||||
control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER, 220,
|
||||
18, 232, 470, 190);
|
||||
@@ -1080,14 +1244,15 @@ void showSettings(App &app)
|
||||
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"Import settings…", 250, 18, 606, 145);
|
||||
button(w, L"Export settings…", 251, 173, 606, 145);
|
||||
button(w, L"Snippets…", 252, 328, 606, 110);
|
||||
button(w, L"Save", IDOK, 548, 606, 100);
|
||||
button(w, L"Cancel", IDCANCEL, 660, 606, 100);
|
||||
fillSettings(w, (SettingsView *)GetWindowLongPtrW(w, GWLP_USERDATA));
|
||||
ShowWindow(w, SW_SHOW);
|
||||
SetForegroundWindow(w);
|
||||
showRequestedWindow(w);
|
||||
activateWindow(w);
|
||||
}
|
||||
void showSnippets(App &app)
|
||||
{
|
||||
@@ -1112,7 +1277,7 @@ void showSnippets(App &app)
|
||||
button(w, L"Save", IDOK, 510, 478, 100);
|
||||
button(w, L"Cancel", IDCANCEL, 624, 478, 100);
|
||||
fillSnippets(w, (SnippetView *)GetWindowLongPtrW(w, GWLP_USERDATA));
|
||||
ShowWindow(w, SW_SHOW);
|
||||
showRequestedWindow(w);
|
||||
SetForegroundWindow(w);
|
||||
}
|
||||
void showShortcuts(App &app)
|
||||
@@ -1145,7 +1310,7 @@ void showEntry(App &app, const std::wstring &id)
|
||||
WS_THICKFRAME | WS_MAXIMIZEBOX);
|
||||
auto field = edit(w, 1, 10, 10, 700, 480, ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL | WS_VSCROLL);
|
||||
SetWindowTextW(field, text.c_str());
|
||||
ShowWindow(w, SW_SHOW);
|
||||
showRequestedWindow(w);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user