ui/ncurses: Add language selector & support for language changes
authorJeremy Kerr <jk@ozlabs.org>
Mon, 28 Jul 2014 02:42:25 +0000 (10:42 +0800)
committerJeremy Kerr <jk@ozlabs.org>
Mon, 28 Jul 2014 06:23:05 +0000 (14:23 +0800)
This change adds a language selector UI, and allows language changes
from incoming configuration messages.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
po/POTFILES.in
po/en.po
ui/ncurses/Makefile.am
ui/ncurses/nc-cui.c
ui/ncurses/nc-cui.h
ui/ncurses/nc-lang.c [new file with mode: 0644]
ui/ncurses/nc-lang.h [new file with mode: 0644]
ui/ncurses/nc-menu.c
ui/ncurses/nc-scr.h

index 7754f8b9c06d305fb2cbcc62af5225565c8c2db5..ee5d41fbe5ff352f3085c2ed17c9a3394c607039 100644 (file)
@@ -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
index d55b155594c4c8cf86dd63f41d24cdd3406a6dac..94545bfff06067c037a55c0386d446c25a17523a 100644 (file)
--- 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"
index 39cf597eee2b36284af28ae48a7a713e036be092..06f272f5624faed7bf3587089217974a1662dd03 100644 (file)
@@ -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 \
index 3c14f21bea32f55fc98d4cd433eba27fd6d0a66f..4f03409fac2ed81723ca7e3dcdd607f6f30a0a78 100644 (file)
 #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);
 
index 417126f8d57b38d99b4b3772ac7fc7bc9f13c977..ff735ae444be2ee37724c83ae31ec67998a58c64 100644 (file)
@@ -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 (file)
index 0000000..35f54e6
--- /dev/null
@@ -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 <stdlib.h>
+#include <string.h>
+
+#include <talloc/talloc.h>
+#include <types/types.h>
+#include <log/log.h>
+#include <i18n/i18n.h>
+#include <pb-config/pb-config.h>
+
+#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 (file)
index 0000000..f25dcc8
--- /dev/null
@@ -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 */
index a77ea98ec87af303b99de9ae5959309e47cea049..666b78bbfee23d5bd7688958adc828eeda62b3d1 100644 (file)
@@ -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)
index 50cce33106838428e1be7e2d867e397eee4f5eff..3d7c4ebcd919207c12b8607d3fd0e478fe2e636c 100644 (file)
@@ -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)