From 9330aba60a05a8d9889dab42bc92b9ebbc55941b Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Mon, 28 Jul 2014 10:42:25 +0800 Subject: [PATCH 1/1] ui/ncurses: Add language selector & support for language changes This change adds a language selector UI, and allows language changes from incoming configuration messages. Signed-off-by: Jeremy Kerr --- po/POTFILES.in | 1 + po/en.po | 14 +- ui/ncurses/Makefile.am | 2 + ui/ncurses/nc-cui.c | 65 ++++++- ui/ncurses/nc-cui.h | 2 + ui/ncurses/nc-lang.c | 374 +++++++++++++++++++++++++++++++++++++++++ ui/ncurses/nc-lang.h | 34 ++++ ui/ncurses/nc-menu.c | 3 + ui/ncurses/nc-scr.h | 3 +- 9 files changed, 491 insertions(+), 7 deletions(-) create mode 100644 ui/ncurses/nc-lang.c create mode 100644 ui/ncurses/nc-lang.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 7754f8b..ee5d41f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -4,6 +4,7 @@ ui/ncurses/nc-boot-editor.c ui/ncurses/nc-config.c ui/ncurses/nc-cui.c ui/ncurses/nc-helpscreen.c +ui/ncurses/nc-lang.c ui/ncurses/nc-menu.c ui/ncurses/nc-sysinfo.c ui/ncurses/nc-textscreen.c diff --git a/po/en.po b/po/en.po index d55b155..94545bf 100644 --- a/po/en.po +++ b/po/en.po @@ -166,8 +166,8 @@ msgstr "SAFE MODE: select '%s' to continue" msgid "Rescan devices" msgstr "Rescan devices" -msgid "Enter=accept, e=edit, n=new, x=exit, h=help" -msgstr "Enter=accept, e=edit, n=new, x=exit, h=help" +msgid "Enter=accept, e=edit, n=new, x=exit, l=language, h=help" +msgstr "Enter=accept, e=edit, n=new, x=exit, l=language, h=help" msgid "Welcome to Petitboot" msgstr "Welcome to Petitboot" @@ -207,6 +207,16 @@ msgstr "Petitboot help" msgid "Petitboot help: %s" msgstr "Petitboot help: %s" +msgid "Language" +msgstr "Language" + +#, c-format +msgid "Unknown language '%s'" +msgstr "Unknown language '%s'" + +msgid "Petitboot Language Selection" +msgstr "Petitboot Language Selection" + #, c-format msgid "!Invalid option %d" msgstr "!Invalid option %d" diff --git a/ui/ncurses/Makefile.am b/ui/ncurses/Makefile.am index 39cf597..06f272f 100644 --- a/ui/ncurses/Makefile.am +++ b/ui/ncurses/Makefile.am @@ -41,6 +41,8 @@ libpbnc_la_SOURCES = \ nc-boot-editor-help.c \ nc-helpscreen.c \ nc-helpscreen.h \ + nc-lang.c \ + nc-lang.h \ nc-menu.c \ nc-menu.h \ nc-scr.c \ diff --git a/ui/ncurses/nc-cui.c b/ui/ncurses/nc-cui.c index 3c14f21..4f03409 100644 --- a/ui/ncurses/nc-cui.c +++ b/ui/ncurses/nc-cui.c @@ -38,10 +38,13 @@ #include "nc-boot-editor.h" #include "nc-config.h" #include "nc-sysinfo.h" +#include "nc-lang.h" #include "nc-helpscreen.h" extern const struct help_text main_menu_help_text; +static struct pmenu *main_menu_init(struct cui *cui); + static void cui_start(void) { initscr(); /* Initialize ncurses. */ @@ -265,6 +268,19 @@ void cui_show_config(struct cui *cui) cui_set_current(cui, config_screen_scr(cui->config_screen)); } +static void cui_lang_exit(struct cui *cui) +{ + cui_set_current(cui, &cui->main->scr); + talloc_free(cui->lang_screen); + cui->lang_screen = NULL; +} + +void cui_show_lang(struct cui *cui) +{ + cui->lang_screen = lang_screen_init(cui, cui->config, cui_lang_exit); + cui_set_current(cui, lang_screen_scr(cui->lang_screen)); +} + static void cui_help_exit(struct cui *cui) { cui_set_current(cui, help_screen_return_scr(cui->help_screen)); @@ -619,11 +635,41 @@ static void cui_update_sysinfo(struct system_info *sysinfo, void *arg) cui_update_mm_title(cui); } +static void cui_update_language(struct cui *cui, char *lang) +{ + bool repost_menu; + char *cur_lang; + + cur_lang = setlocale(LC_ALL, NULL); + if (cur_lang && !strcmp(cur_lang, lang)) + return; + + setlocale(LC_ALL, lang); + + /* we'll need to update the menu: drop all items and repopulate */ + repost_menu = cui->current == &cui->main->scr; + if (repost_menu) + nc_scr_unpost(cui->current); + + talloc_free(cui->main); + cui->main = main_menu_init(cui); + + if (repost_menu) { + cui->current = &cui->main->scr; + nc_scr_post(cui->current); + } + + discover_client_enumerate(cui->client); +} + static void cui_update_config(struct config *config, void *arg) { struct cui *cui = cui_from_arg(arg); cui->config = talloc_steal(cui, config); + if (config->lang) + cui_update_language(cui, config->lang); + if (cui->config_screen) config_screen_update(cui->config_screen, config, cui->sysinfo); @@ -655,6 +701,12 @@ static int menu_config_execute(struct pmenu_item *item) return 0; } +static int menu_lang_execute(struct pmenu_item *item) +{ + cui_show_lang(cui_from_item(item)); + return 0; +} + static int menu_reinit_execute(struct pmenu_item *item) { cui_send_reinit(cui_from_item(item)); @@ -670,7 +722,7 @@ static struct pmenu *main_menu_init(struct cui *cui) struct pmenu *m; int result; - m = pmenu_init(cui, 5, cui_on_exit); + m = pmenu_init(cui, 6, cui_on_exit); if (!m) { pb_log("%s: failed\n", __func__); return NULL; @@ -682,7 +734,7 @@ static struct pmenu *main_menu_init(struct cui *cui) "Petitboot (" PACKAGE_VERSION ")"); m->scr.frame.rtitle = NULL; m->scr.frame.help = talloc_strdup(m, - _("Enter=accept, e=edit, n=new, x=exit, h=help")); + _("Enter=accept, e=edit, n=new, x=exit, l=language, h=help")); m->scr.frame.status = talloc_strdup(m, _("Welcome to Petitboot")); /* add a separator */ @@ -699,13 +751,18 @@ static struct pmenu *main_menu_init(struct cui *cui) i->on_execute = menu_config_execute; pmenu_item_insert(m, i, 2); + /* this label isn't translated, so we don't want a gettext() here */ + i = pmenu_item_create(m, "Language"); + i->on_execute = menu_lang_execute; + pmenu_item_insert(m, i, 3); + i = pmenu_item_create(m, _("Rescan devices")); i->on_execute = menu_reinit_execute; - pmenu_item_insert(m, i, 3); + pmenu_item_insert(m, i, 4); i = pmenu_item_create(m, _("Exit to shell")); i->on_execute = pmenu_exit_cb; - pmenu_item_insert(m, i, 4); + pmenu_item_insert(m, i, 5); result = pmenu_setup(m); diff --git a/ui/ncurses/nc-cui.h b/ui/ncurses/nc-cui.h index 417126f..ff735ae 100644 --- a/ui/ncurses/nc-cui.h +++ b/ui/ncurses/nc-cui.h @@ -60,6 +60,7 @@ struct cui { struct config *config; struct config_screen *config_screen; struct boot_editor *boot_editor; + struct lang_screen *lang_screen; struct help_screen *help_screen; struct pjs *pjs; void *platform_info; @@ -76,6 +77,7 @@ void cui_item_edit(struct pmenu_item *item); void cui_item_new(struct pmenu *menu); void cui_show_sysinfo(struct cui *cui); void cui_show_config(struct cui *cui); +void cui_show_lang(struct cui *cui); void cui_show_help(struct cui *cui, const char *title, const struct help_text *text); int cui_send_config(struct cui *cui, struct config *config); diff --git a/ui/ncurses/nc-lang.c b/ui/ncurses/nc-lang.c new file mode 100644 index 0000000..35f54e6 --- /dev/null +++ b/ui/ncurses/nc-lang.c @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2013 IBM Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include "nc-cui.h" +#include "nc-lang.h" +#include "nc-widgets.h" + +#define N_FIELDS 4 + +static struct lang { + const char *name; + const wchar_t *label; +} languages[] = { + { "en_US.utf8", L"English"}, +}; + +struct lang_screen { + struct nc_scr scr; + struct cui *cui; + struct nc_widgetset *widgetset; + WINDOW *pad; + + bool exit; + void (*on_exit)(struct cui *); + + int scroll_y; + + int label_x; + int field_x; + + struct { + struct nc_widget_select *lang_f; + struct nc_widget_label *lang_l; + + struct nc_widget_button *ok_b; + struct nc_widget_button *cancel_b; + } widgets; +}; + +static struct lang_screen *lang_screen_from_scr(struct nc_scr *scr) +{ + struct lang_screen *lang_screen; + + assert(scr->sig == pb_lang_screen_sig); + lang_screen = (struct lang_screen *) + ((char *)scr - (size_t)&((struct lang_screen *)0)->scr); + assert(lang_screen->scr.sig == pb_lang_screen_sig); + return lang_screen; +} + +static void pad_refresh(struct lang_screen *screen) +{ + int y, x, rows, cols; + + getmaxyx(screen->scr.sub_ncw, rows, cols); + getbegyx(screen->scr.sub_ncw, y, x); + + prefresh(screen->pad, screen->scroll_y, 0, y, x, rows, cols); +} + +static void lang_screen_process_key(struct nc_scr *scr, int key) +{ + struct lang_screen *screen = lang_screen_from_scr(scr); + bool handled; + + handled = widgetset_process_key(screen->widgetset, key); + + if (!handled) { + switch (key) { + case 'x': + case 27: /* esc */ + screen->exit = true; + break; + } + } + + if (screen->exit) { + screen->on_exit(screen->cui); + + } else if (handled) { + pad_refresh(screen); + } +} + +static void lang_screen_resize(struct nc_scr *scr) +{ + struct lang_screen *screen = lang_screen_from_scr(scr); + (void)screen; +} + +static int lang_screen_post(struct nc_scr *scr) +{ + struct lang_screen *screen = lang_screen_from_scr(scr); + widgetset_post(screen->widgetset); + nc_scr_frame_draw(scr); + redrawwin(scr->main_ncw); + wrefresh(screen->scr.main_ncw); + pad_refresh(screen); + return 0; +} + +static int lang_screen_unpost(struct nc_scr *scr) +{ + struct lang_screen *screen = lang_screen_from_scr(scr); + widgetset_unpost(screen->widgetset); + return 0; +} + +struct nc_scr *lang_screen_scr(struct lang_screen *screen) +{ + return &screen->scr; +} + +static int lang_process_form(struct lang_screen *screen) +{ + struct config *config; + struct lang *lang; + int idx, rc; + + config = config_copy(screen, screen->cui->config); + + idx = widget_select_get_value(screen->widgets.lang_f); + + /* Option -1 ("Unknown") can only be populated from the current + * language, so there's no change here */ + if (idx == -1) + return 0; + + lang = &languages[idx]; + + if (config->lang && !strcmp(lang->name, config->lang)) + return 0; + + config->lang = talloc_strdup(screen, lang->name); + + rc = cui_send_config(screen->cui, config); + talloc_free(config); + + if (rc) + pb_log("cui_send_config failed!\n"); + else + pb_debug("config sent!\n"); + + return 0; +} + +static void ok_click(void *arg) +{ + struct lang_screen *screen = arg; + if (lang_process_form(screen)) + /* errors are written to the status line, so we'll need + * to refresh */ + wrefresh(screen->scr.main_ncw); + else + screen->exit = true; +} + +static void cancel_click(void *arg) +{ + struct lang_screen *screen = arg; + screen->exit = true; +} + +static int layout_pair(struct lang_screen *screen, int y, + struct nc_widget_label *label, + struct nc_widget *field) +{ + struct nc_widget *label_w = widget_label_base(label); + widget_move(label_w, y, screen->label_x); + widget_move(field, y, screen->field_x); + return max(widget_height(label_w), widget_height(field)); +} + +static void lang_screen_layout_widgets(struct lang_screen *screen) +{ + int y; + + y = 1; + + y += layout_pair(screen, y, screen->widgets.lang_l, + widget_select_base(screen->widgets.lang_f)); + + y += 1; + + widget_move(widget_button_base(screen->widgets.ok_b), + y, screen->field_x); + widget_move(widget_button_base(screen->widgets.cancel_b), + y, screen->field_x + 10); +} + +static void lang_screen_setup_empty(struct lang_screen *screen) +{ + widget_new_label(screen->widgetset, 2, screen->field_x, + _("Waiting for configuration data...")); + screen->widgets.cancel_b = widget_new_button(screen->widgetset, + 4, screen->field_x, 6, _("Cancel"), + cancel_click, screen); +} + + +static void lang_screen_setup_widgets(struct lang_screen *screen, + const struct config *config) +{ + struct nc_widgetset *set = screen->widgetset; + unsigned int i; + bool found; + + build_assert(sizeof(screen->widgets) / sizeof(struct widget *) + == N_FIELDS); + + screen->widgets.lang_l = widget_new_label(set, 0, 0, _("Language")); + screen->widgets.lang_f = widget_new_select(set, 0, 0, 50); + + found = false; + + for (i = 0; i < ARRAY_SIZE(languages); i++) { + struct lang *lang = &languages[i]; + bool selected; + char *label; + int len; + + len = wcstombs(NULL, lang->label, 0); + assert(len >= 0); + label = talloc_array(screen, char, len + 1); + wcstombs(label, lang->label, len + 1); + + selected = config->lang && !strcmp(lang->name, config->lang); + found |= selected; + + widget_select_add_option(screen->widgets.lang_f, i, + label, selected); + } + + if (!found && config->lang) { + char *label = talloc_asprintf(screen, + _("Unknown language '%s'"), config->lang); + widget_select_add_option(screen->widgets.lang_f, -1, + label, true); + } + + screen->widgets.ok_b = widget_new_button(set, 0, 0, 6, _("OK"), + ok_click, screen); + screen->widgets.cancel_b = widget_new_button(set, 0, 0, 6, _("Cancel"), + cancel_click, screen); +} + +static void lang_screen_widget_focus(struct nc_widget *widget, void *arg) +{ + struct lang_screen *screen = arg; + int w_y, s_max; + + w_y = widget_y(widget) + widget_focus_y(widget); + s_max = getmaxy(screen->scr.sub_ncw) - 1; + + if (w_y < screen->scroll_y) + screen->scroll_y = w_y; + + else if (w_y + screen->scroll_y + 1 > s_max) + screen->scroll_y = 1 + w_y - s_max; + + else + return; + + pad_refresh(screen); +} + +static void lang_screen_draw(struct lang_screen *screen, + const struct config *config) +{ + bool repost = false; + int height; + + height = ARRAY_SIZE(languages) + 4; + if (!screen->pad || getmaxy(screen->pad) < height) { + if (screen->pad) + delwin(screen->pad); + screen->pad = newpad(height, COLS); + } + + if (screen->widgetset) { + widgetset_unpost(screen->widgetset); + talloc_free(screen->widgetset); + repost = true; + } + + screen->widgetset = widgetset_create(screen, screen->scr.main_ncw, + screen->pad); + widgetset_set_widget_focus(screen->widgetset, + lang_screen_widget_focus, screen); + + if (!config) { + lang_screen_setup_empty(screen); + } else { + lang_screen_setup_widgets(screen, config); + lang_screen_layout_widgets(screen); + } + + if (repost) + widgetset_post(screen->widgetset); +} + +void lang_screen_update(struct lang_screen *screen, + const struct config *config) +{ + lang_screen_draw(screen, config); + pad_refresh(screen); +} + +static int lang_screen_destroy(void *arg) +{ + struct lang_screen *screen = arg; + if (screen->pad) + delwin(screen->pad); + return 0; +} + +struct lang_screen *lang_screen_init(struct cui *cui, + const struct config *config, + void (*on_exit)(struct cui *)) +{ + struct lang_screen *screen; + + screen = talloc_zero(cui, struct lang_screen); + talloc_set_destructor(screen, lang_screen_destroy); + nc_scr_init(&screen->scr, pb_lang_screen_sig, 0, + cui, lang_screen_process_key, + lang_screen_post, lang_screen_unpost, + lang_screen_resize); + + screen->cui = cui; + screen->on_exit = on_exit; + screen->label_x = 2; + screen->field_x = 17; + + screen->scr.frame.ltitle = talloc_strdup(screen, + _("Petitboot Language Selection")); + screen->scr.frame.rtitle = NULL; + screen->scr.frame.help = talloc_strdup(screen, + _("tab=next, shift+tab=previous, x=exit, h=help")); + nc_scr_frame_draw(&screen->scr); + + scrollok(screen->scr.sub_ncw, true); + + lang_screen_draw(screen, config); + + return screen; +} diff --git a/ui/ncurses/nc-lang.h b/ui/ncurses/nc-lang.h new file mode 100644 index 0000000..f25dcc8 --- /dev/null +++ b/ui/ncurses/nc-lang.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 IBM Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NC_LANG_H +#define _NC_LANG_H + +#include "types/types.h" +#include "nc-cui.h" + +struct lang_screen; + +struct lang_screen *lang_screen_init(struct cui *cui, + const struct config *config, + void (*on_exit)(struct cui *)); + +struct nc_scr *lang_screen_scr(struct lang_screen *screen); +void lang_screen_update(struct lang_screen *screen, + const struct config *config); + +#endif /* defined _NC_LANG_H */ diff --git a/ui/ncurses/nc-menu.c b/ui/ncurses/nc-menu.c index a77ea98..666b78b 100644 --- a/ui/ncurses/nc-menu.c +++ b/ui/ncurses/nc-menu.c @@ -409,6 +409,9 @@ static void pmenu_process_key(struct nc_scr *scr, int key) case 'c': cui_show_config(cui_from_arg(scr->ui_ctx)); break; + case 'l': + cui_show_lang(cui_from_arg(scr->ui_ctx)); + break; case KEY_F(1): case 'h': if (menu->help_text) diff --git a/ui/ncurses/nc-scr.h b/ui/ncurses/nc-scr.h index 50cce33..3d7c4eb 100644 --- a/ui/ncurses/nc-scr.h +++ b/ui/ncurses/nc-scr.h @@ -46,7 +46,8 @@ enum pb_nc_sig { pb_boot_editor_sig = 444, pb_text_screen_sig = 555, pb_config_screen_sig = 666, - pb_removed_sig = -777, + pb_lang_screen_sig = 777, + pb_removed_sig = -888, }; static inline void nc_flush_keys(void) -- 2.39.2