From bb9a22212f83e55ebaf601a65251ccb3caf86fab Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 23 Mar 2021 21:40:41 +0200 Subject: UI language preference; switching at runtime IssueID #192 --- src/app.c | 28 ++++++++++++++++++++---- src/lang.c | 2 +- src/prefs.c | 2 ++ src/prefs.h | 1 + src/ui/command.c | 2 +- src/ui/inputwidget.c | 14 ++++++++++-- src/ui/labelwidget.c | 9 ++++++++ src/ui/labelwidget.h | 1 + src/ui/util.c | 61 ++++++++++++++++++++++++++++++++++++++++++---------- src/ui/window.c | 4 ++++ 10 files changed, 105 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/app.c b/src/app.c index 95345bda..d477abb5 100644 --- a/src/app.c +++ b/src/app.c @@ -208,6 +208,7 @@ static iString *serializePrefs_App_(const iApp *d) { } appendFormat_String(str, "sidebar2.mode arg:%d\n", mode_SidebarWidget(sidebar2)); } + appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.uiLanguage)); appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window)); appendFormat_String(str, "prefs.dialogtab arg:%d\n", d->prefs.dialogTab); appendFormat_String(str, "font.set arg:%d\n", d->prefs.font); @@ -308,6 +309,11 @@ static void loadPrefs_App_(iApp *d) { if (equal_Command(cmd, "uiscale")) { setUiScale_Window(get_Window(), argf_Command(cmd)); } + else if (equal_Command(cmd, "uilang")) { + const char *id = cstr_Rangecc(range_Command(cmd, "id")); + setCStr_String(&d->prefs.uiLanguage, id); + setCurrent_Lang(id); + } else if (equal_Command(cmd, "ca.file") || equal_Command(cmd, "ca.path")) { /* Background requests may be started before these commands would get handled via the event loop. */ @@ -1286,13 +1292,14 @@ static void updatePrefsThemeButtons_(iWidget *d) { } static void updateDropdownSelection_(iLabelWidget *dropButton, const char *selectedCommand) { - iForEach(ObjectList, i, children_Widget(findChild_Widget(as_Widget(dropButton), "menu"))) { + iWidget *menu = findChild_Widget(as_Widget(dropButton), "menu"); + iForEach(ObjectList, i, children_Widget(menu)) { if (isInstance_Object(i.object, &Class_LabelWidget)) { iLabelWidget *item = i.object; const iBool isSelected = endsWith_String(command_LabelWidget(item), selectedCommand); setFlags_Widget(as_Widget(item), selected_WidgetFlag, isSelected); if (isSelected) { - updateText_LabelWidget(dropButton, text_LabelWidget(item)); + updateText_LabelWidget(dropButton, sourceText_LabelWidget(item)); } } } @@ -1300,8 +1307,6 @@ static void updateDropdownSelection_(iLabelWidget *dropButton, const char *selec static void updateColorThemeButton_(iLabelWidget *button, int theme) { if (!button) return; -// const char *mode = strstr(cstr_String(id_Widget(as_Widget(button))), ".dark") -// ? "dark" : "light"; updateDropdownSelection_(button, format_CStr(".set arg:%d", theme)); } @@ -1355,6 +1360,11 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { postCommand_App("prefs.changed"); return iTrue; } + else if (equal_Command(cmd, "uilang")) { + updateDropdownSelection_(findChild_Widget(d, "prefs.uilang"), + cstr_String(string_Command(cmd, "id"))); + return iFalse; + } else if (equal_Command(cmd, "quoteicon.set")) { const int arg = arg_Command(cmd); setFlags_Widget(findChild_Widget(d, "prefs.quoteicon.0"), selected_WidgetFlag, arg == 0); @@ -1532,6 +1542,15 @@ iBool handleCommand_App(const char *cmd) { d->prefs.dialogTab = arg_Command(cmd); return iTrue; } + else if (equal_Command(cmd, "uilang")) { + const iString *lang = string_Command(cmd, "id"); + if (!equal_String(lang, &d->prefs.uiLanguage)) { + set_String(&d->prefs.uiLanguage, lang); + setCurrent_Lang(cstr_String(&d->prefs.uiLanguage)); + postCommand_App("lang.changed"); + } + return iTrue; + } else if (equal_Command(cmd, "translation.languages")) { d->prefs.langFrom = argLabel_Command(cmd, "from"); d->prefs.langTo = argLabel_Command(cmd, "to"); @@ -1891,6 +1910,7 @@ iBool handleCommand_App(const char *cmd) { setToggle_Widget(findChild_Widget(dlg, "prefs.hidetoolbarscroll"), d->prefs.hideToolbarOnScroll); setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); + updateDropdownSelection_(findChild_Widget(dlg, "prefs.uilang"), cstr_String(&d->prefs.uiLanguage)); setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), collectNewFormat_String("%g", uiScale_Window(d->window))); diff --git a/src/lang.c b/src/lang.c index 57efcaa2..db28e318 100644 --- a/src/lang.c +++ b/src/lang.c @@ -72,7 +72,7 @@ iRangecc range_Lang(iRangecc msgId) { return ((const iMsgStr *) at_SortedArray(d->messages, pos))->str; } fprintf(stderr, "[Lang] missing: %s\n", cstr_Rangecc(msgId)); fflush(stderr); - iAssert(iFalse); +// iAssert(iFalse); return msgId; } diff --git a/src/prefs.c b/src/prefs.c index a4f12e20..119dfd56 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -54,6 +54,7 @@ void init_Prefs(iPrefs *d) { d->docThemeDark = colorfulDark_GmDocumentTheme; d->docThemeLight = white_GmDocumentTheme; d->saturation = 1.0f; + initCStr_String(&d->uiLanguage, "en"); init_String(&d->caFile); init_String(&d->caPath); init_String(&d->geminiProxy); @@ -81,4 +82,5 @@ void deinit_Prefs(iPrefs *d) { deinit_String(&d->downloadDir); deinit_String(&d->caPath); deinit_String(&d->caFile); + deinit_String(&d->uiLanguage); } diff --git a/src/prefs.h b/src/prefs.h index 130f11e2..1bd434d0 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -38,6 +38,7 @@ struct Impl_Prefs { int langFrom; int langTo; /* Window */ + iString uiLanguage; iBool useSystemTheme; enum iColorTheme theme; enum iColorAccent accent; diff --git a/src/ui/command.c b/src/ui/command.c index 44e66121..c5ca164e 100644 --- a/src/ui/command.c +++ b/src/ui/command.c @@ -96,7 +96,7 @@ iString *suffix_Command(const char *cmd, const char *label) { } const iString *string_Command(const char *cmd, const char *label) { - return collect_String(newRange_String(range_Command(cmd, label))); + return collectNewRange_String(range_Command(cmd, label)); } iRangecc range_Command(const char *cmd, const char *label) { diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 05b83b3d..52359732 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c @@ -83,6 +83,7 @@ struct Impl_InputWidget { iArray text; /* iChar[] */ iArray oldText; /* iChar[] */ iString hint; + iString srcHint; int leftPadding; int rightPadding; size_t cursor; @@ -136,6 +137,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { init_Array(&d->text, sizeof(iChar)); init_Array(&d->oldText, sizeof(iChar)); init_String(&d->hint); + init_String(&d->srcHint); init_Array(&d->undoStack, sizeof(iInputUndo)); d->font = uiInput_FontId | alwaysVariableFlag_FontId; d->leftPadding = 0; @@ -164,6 +166,7 @@ void deinit_InputWidget(iInputWidget *d) { if (d->timer) { SDL_RemoveTimer(d->timer); } + deinit_String(&d->srcHint); deinit_String(&d->hint); deinit_Array(&d->oldText); deinit_Array(&d->text); @@ -251,8 +254,10 @@ void setMaxLen_InputWidget(iInputWidget *d, size_t maxLen) { } void setHint_InputWidget(iInputWidget *d, const char *hintText) { - setCStr_String(&d->hint, hintText); - translate_Lang(&d->hint); /* TODO: Keep original for retranslations. */ + /* Keep original for retranslations. */ + setCStr_String(&d->srcHint, hintText); + set_String(&d->hint, &d->srcHint); + translate_Lang(&d->hint); } void setContentPadding_InputWidget(iInputWidget *d, int left, int right) { @@ -665,6 +670,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { begin_InputWidget(d); return iFalse; } + else if (isCommand_UserEvent(ev, "lang.changed")) { + set_String(&d->hint, &d->srcHint); + translate_Lang(&d->hint); + return iFalse; + } else if (isCommand_Widget(w, ev, "focus.lost")) { end_InputWidget(d, iTrue); return iFalse; diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index 2a1eb06a..8089445b 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c @@ -85,6 +85,10 @@ static iBool processEvent_LabelWidget_(iLabelWidget *d, const SDL_Event *ev) { if (isMetricsChange_UserEvent(ev)) { updateSize_LabelWidget(d); } + else if (isCommand_UserEvent(ev, "lang.changed")) { + setText_LabelWidget(d, &d->srcLabel); + return iFalse; + } else if (isCommand_UserEvent(ev, "bindings.changed")) { /* Update the key used to trigger this label. */ updateKey_LabelWidget_(d); @@ -474,6 +478,11 @@ const iString *text_LabelWidget(const iLabelWidget *d) { return &d->label; } +const iString *sourceText_LabelWidget(const iLabelWidget *d) { + if (!d) return collectNew_String(); + return &d->srcLabel; +} + const iString *command_LabelWidget(const iLabelWidget *d) { return &d->command; } diff --git a/src/ui/labelwidget.h b/src/ui/labelwidget.h index 3e3c76fb..f4c4658c 100644 --- a/src/ui/labelwidget.h +++ b/src/ui/labelwidget.h @@ -45,6 +45,7 @@ void updateTextCStr_LabelWidget (iLabelWidget *, const char *text); /* not r iInt2 defaultSize_LabelWidget (const iLabelWidget *); int font_LabelWidget (const iLabelWidget *); const iString * text_LabelWidget (const iLabelWidget *); +const iString * sourceText_LabelWidget (const iLabelWidget *); /* untranslated */ const iString * command_LabelWidget (const iLabelWidget *); iChar icon_LabelWidget (const iLabelWidget *); diff --git a/src/ui/util.c b/src/ui/util.c index 94690bc6..8c0b0138 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -1679,14 +1679,13 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg, void setToggle_Widget(iWidget *d, iBool active) { if (d) { - const char *YES = cstr_Lang("toggle.yes"); - const char *NO = cstr_Lang("toggle.no"); setFlags_Widget(d, selected_WidgetFlag, active); iLabelWidget *label = (iLabelWidget *) d; - if (!cmp_String(text_LabelWidget(label), YES) || - !cmp_String(text_LabelWidget(label), NO)) { - updateText_LabelWidget((iLabelWidget *) d, - collectNewCStr_String(isSelected_Widget(d) ? YES : NO)); + if (!cmp_String(text_LabelWidget(label), cstr_Lang("toggle.yes")) || + !cmp_String(text_LabelWidget(label), cstr_Lang("toggle.no"))) { + updateText_LabelWidget( + (iLabelWidget *) d, + collectNewCStr_String(isSelected_Widget(d) ? "${toggle.yes}" : "${toggle.no}")); } else { refresh_Widget(d); @@ -1707,9 +1706,10 @@ static iBool toggleHandler_(iWidget *d, const char *cmd) { } iWidget *makeToggle_Widget(const char *id) { - iWidget *toggle = as_Widget(new_LabelWidget("YES", "toggle")); /* "YES" for sizing */ + iWidget *toggle = as_Widget(new_LabelWidget("${toggle.yes}", "toggle")); /* "YES" for sizing */ setId_Widget(toggle, id); - updateTextCStr_LabelWidget((iLabelWidget *) toggle, "NO"); /* actual initial value */ + updateTextCStr_LabelWidget((iLabelWidget *) toggle, "${toggle.no}"); /* actual initial value */ + setFlags_Widget(toggle, fixedWidth_WidgetFlag, iTrue); setCommandHandler_Widget(toggle, toggleHandler_); return toggle; } @@ -1787,6 +1787,11 @@ static void addFontButtons_(iWidget *parent, const char *id) { delete_Array(items); } +static int cmp_MenuItem_(const void *e1, const void *e2) { + const iMenuItem *a = e1, *b = e2; + return iCmpStr(a->label, b->label); +} + iWidget *makePreferences_Widget(void) { iWidget *dlg = makeSheet_Widget("prefs"); addChildFlags_Widget(dlg, @@ -1820,6 +1825,36 @@ iWidget *makePreferences_Widget(void) { } /* Window. */ { appendTwoColumnPage_(tabs, "${heading.prefs.interface}", '2', &headings, &values); + /* UI languages. */ { + iArray *uiLangs = collectNew_Array(sizeof(iMenuItem)); + const iMenuItem langItems[] = { + { "${lang.en}", 0, 0, "uilang id:en" }, + { "${lang.fi}", 0, 0, "uilang id:fi" }, + }; + pushBackN_Array(uiLangs, langItems, iElemCount(langItems)); + sort_Array(uiLangs, cmp_MenuItem_); + /* TODO: Add an arrange flag for resizing parent to widest child. */ + int widest = 0; + size_t widestPos = iInvalidPos; + iConstForEach(Array, i, uiLangs) { + const int width = + advance_Text(uiLabel_FontId, + translateCStr_Lang(((const iMenuItem *) i.value)->label)) + .x; + if (widestPos == iInvalidPos || width > widest) { + widest = width; + widestPos = index_ArrayConstIterator(&i); + } + } + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.uilang}"))); + setId_Widget(addChildFlags_Widget(values, + iClob(makeMenuButton_LabelWidget( + value_Array(uiLangs, widestPos, iMenuItem).label, + data_Array(uiLangs), + size_Array(uiLangs))), + 0), + "prefs.uilang"); + } #if defined (iPlatformApple) || defined (iPlatformMSys) addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.ostheme}"))); addChild_Widget(values, iClob(makeToggle_Widget("prefs.ostheme"))); @@ -1898,11 +1933,15 @@ iWidget *makePreferences_Widget(void) { addFontButtons_(values, "font"); addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}"))); iWidget *mono = new_Widget(); - /* TODO: Needs labels! */ + iWidget *tog; setTextCStr_LabelWidget( - addChild_Widget(mono, iClob(makeToggle_Widget("prefs.mono.gemini"))), "${prefs.mono.gemini}"); + addChild_Widget(mono, tog = iClob(makeToggle_Widget("prefs.mono.gemini"))), "${prefs.mono.gemini}"); + setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); + updateSize_LabelWidget((iLabelWidget *) tog); setTextCStr_LabelWidget( - addChild_Widget(mono, iClob(makeToggle_Widget("prefs.mono.gopher"))), "${prefs.mono.gopher}"); + addChild_Widget(mono, tog = iClob(makeToggle_Widget("prefs.mono.gopher"))), "${prefs.mono.gopher}"); + setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); + updateSize_LabelWidget((iLabelWidget *) tog); addChildFlags_Widget(values, iClob(mono), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); } makeTwoColumnHeading_("${heading.prefs.paragraph}", headings, values); diff --git a/src/ui/window.c b/src/ui/window.c index 6949e245..97500b22 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -1820,6 +1820,10 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { if (isMetricsChange_UserEvent(&event)) { updateMetrics_Window_(d); } + if (isCommand_UserEvent(&event, "lang.changed")) { + invalidate_Window_(d); + arrange_Widget(d->root); + } if (oldHover != hover_Widget()) { postRefresh_App(); } -- cgit v1.2.3