ui/ncurses: Add language selector & support for language changes
[petitboot] / ui / ncurses / nc-lang.c
1 /*
2  *  Copyright (C) 2013 IBM Corporation
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; version 2 of the License.
7  *
8  *  This program is distributed in the hope that it will be useful,
9  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  *  GNU General Public License for more details.
12  *
13  *  You should have received a copy of the GNU General Public License
14  *  along with this program; if not, write to the Free Software
15  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16  */
17
18 #if defined(HAVE_CONFIG_H)
19 #include "config.h"
20 #endif
21
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include <talloc/talloc.h>
26 #include <types/types.h>
27 #include <log/log.h>
28 #include <i18n/i18n.h>
29 #include <pb-config/pb-config.h>
30
31 #include "nc-cui.h"
32 #include "nc-lang.h"
33 #include "nc-widgets.h"
34
35 #define N_FIELDS        4
36
37 static struct lang {
38         const char      *name;
39         const wchar_t   *label;
40 } languages[] = {
41         { "en_US.utf8", L"English"},
42 };
43
44 struct lang_screen {
45         struct nc_scr           scr;
46         struct cui              *cui;
47         struct nc_widgetset     *widgetset;
48         WINDOW                  *pad;
49
50         bool                    exit;
51         void                    (*on_exit)(struct cui *);
52
53         int                     scroll_y;
54
55         int                     label_x;
56         int                     field_x;
57
58         struct {
59                 struct nc_widget_select         *lang_f;
60                 struct nc_widget_label          *lang_l;
61
62                 struct nc_widget_button         *ok_b;
63                 struct nc_widget_button         *cancel_b;
64         } widgets;
65 };
66
67 static struct lang_screen *lang_screen_from_scr(struct nc_scr *scr)
68 {
69         struct lang_screen *lang_screen;
70
71         assert(scr->sig == pb_lang_screen_sig);
72         lang_screen = (struct lang_screen *)
73                 ((char *)scr - (size_t)&((struct lang_screen *)0)->scr);
74         assert(lang_screen->scr.sig == pb_lang_screen_sig);
75         return lang_screen;
76 }
77
78 static void pad_refresh(struct lang_screen *screen)
79 {
80         int y, x, rows, cols;
81
82         getmaxyx(screen->scr.sub_ncw, rows, cols);
83         getbegyx(screen->scr.sub_ncw, y, x);
84
85         prefresh(screen->pad, screen->scroll_y, 0, y, x, rows, cols);
86 }
87
88 static void lang_screen_process_key(struct nc_scr *scr, int key)
89 {
90         struct lang_screen *screen = lang_screen_from_scr(scr);
91         bool handled;
92
93         handled = widgetset_process_key(screen->widgetset, key);
94
95         if (!handled) {
96                 switch (key) {
97                 case 'x':
98                 case 27: /* esc */
99                         screen->exit = true;
100                         break;
101                 }
102         }
103
104         if (screen->exit) {
105                 screen->on_exit(screen->cui);
106
107         } else if (handled) {
108                 pad_refresh(screen);
109         }
110 }
111
112 static void lang_screen_resize(struct nc_scr *scr)
113 {
114         struct lang_screen *screen = lang_screen_from_scr(scr);
115         (void)screen;
116 }
117
118 static int lang_screen_post(struct nc_scr *scr)
119 {
120         struct lang_screen *screen = lang_screen_from_scr(scr);
121         widgetset_post(screen->widgetset);
122         nc_scr_frame_draw(scr);
123         redrawwin(scr->main_ncw);
124         wrefresh(screen->scr.main_ncw);
125         pad_refresh(screen);
126         return 0;
127 }
128
129 static int lang_screen_unpost(struct nc_scr *scr)
130 {
131         struct lang_screen *screen = lang_screen_from_scr(scr);
132         widgetset_unpost(screen->widgetset);
133         return 0;
134 }
135
136 struct nc_scr *lang_screen_scr(struct lang_screen *screen)
137 {
138         return &screen->scr;
139 }
140
141 static int lang_process_form(struct lang_screen *screen)
142 {
143         struct config *config;
144         struct lang *lang;
145         int idx, rc;
146
147         config = config_copy(screen, screen->cui->config);
148
149         idx = widget_select_get_value(screen->widgets.lang_f);
150
151         /* Option -1 ("Unknown") can only be populated from the current
152          * language, so there's no change here */
153         if (idx == -1)
154                 return 0;
155
156         lang = &languages[idx];
157
158         if (config->lang && !strcmp(lang->name, config->lang))
159                 return 0;
160
161         config->lang = talloc_strdup(screen, lang->name);
162
163         rc = cui_send_config(screen->cui, config);
164         talloc_free(config);
165
166         if (rc)
167                 pb_log("cui_send_config failed!\n");
168         else
169                 pb_debug("config sent!\n");
170
171         return 0;
172 }
173
174 static void ok_click(void *arg)
175 {
176         struct lang_screen *screen = arg;
177         if (lang_process_form(screen))
178                 /* errors are written to the status line, so we'll need
179                  * to refresh */
180                 wrefresh(screen->scr.main_ncw);
181         else
182                 screen->exit = true;
183 }
184
185 static void cancel_click(void *arg)
186 {
187         struct lang_screen *screen = arg;
188         screen->exit = true;
189 }
190
191 static int layout_pair(struct lang_screen *screen, int y,
192                 struct nc_widget_label *label,
193                 struct nc_widget *field)
194 {
195         struct nc_widget *label_w = widget_label_base(label);
196         widget_move(label_w, y, screen->label_x);
197         widget_move(field, y, screen->field_x);
198         return max(widget_height(label_w), widget_height(field));
199 }
200
201 static void lang_screen_layout_widgets(struct lang_screen *screen)
202 {
203         int y;
204
205         y = 1;
206
207         y += layout_pair(screen, y, screen->widgets.lang_l,
208                         widget_select_base(screen->widgets.lang_f));
209
210         y += 1;
211
212         widget_move(widget_button_base(screen->widgets.ok_b),
213                         y, screen->field_x);
214         widget_move(widget_button_base(screen->widgets.cancel_b),
215                         y, screen->field_x + 10);
216 }
217
218 static void lang_screen_setup_empty(struct lang_screen *screen)
219 {
220         widget_new_label(screen->widgetset, 2, screen->field_x,
221                         _("Waiting for configuration data..."));
222         screen->widgets.cancel_b = widget_new_button(screen->widgetset,
223                         4, screen->field_x, 6, _("Cancel"),
224                         cancel_click, screen);
225 }
226
227
228 static void lang_screen_setup_widgets(struct lang_screen *screen,
229                 const struct config *config)
230 {
231         struct nc_widgetset *set = screen->widgetset;
232         unsigned int i;
233         bool found;
234
235         build_assert(sizeof(screen->widgets) / sizeof(struct widget *)
236                         == N_FIELDS);
237
238         screen->widgets.lang_l = widget_new_label(set, 0, 0, _("Language"));
239         screen->widgets.lang_f = widget_new_select(set, 0, 0, 50);
240
241         found = false;
242
243         for (i = 0; i < ARRAY_SIZE(languages); i++) {
244                 struct lang *lang = &languages[i];
245                 bool selected;
246                 char *label;
247                 int len;
248
249                 len = wcstombs(NULL, lang->label, 0);
250                 assert(len >= 0);
251                 label = talloc_array(screen, char, len + 1);
252                 wcstombs(label, lang->label, len + 1);
253
254                 selected = config->lang && !strcmp(lang->name, config->lang);
255                 found |= selected;
256
257                 widget_select_add_option(screen->widgets.lang_f, i,
258                                         label, selected);
259         }
260
261         if (!found && config->lang) {
262                 char *label = talloc_asprintf(screen,
263                                 _("Unknown language '%s'"), config->lang);
264                 widget_select_add_option(screen->widgets.lang_f, -1,
265                                         label, true);
266         }
267
268         screen->widgets.ok_b = widget_new_button(set, 0, 0, 6, _("OK"),
269                         ok_click, screen);
270         screen->widgets.cancel_b = widget_new_button(set, 0, 0, 6, _("Cancel"),
271                         cancel_click, screen);
272 }
273
274 static void lang_screen_widget_focus(struct nc_widget *widget, void *arg)
275 {
276         struct lang_screen *screen = arg;
277         int w_y, s_max;
278
279         w_y = widget_y(widget) + widget_focus_y(widget);
280         s_max = getmaxy(screen->scr.sub_ncw) - 1;
281
282         if (w_y < screen->scroll_y)
283                 screen->scroll_y = w_y;
284
285         else if (w_y + screen->scroll_y + 1 > s_max)
286                 screen->scroll_y = 1 + w_y - s_max;
287
288         else
289                 return;
290
291         pad_refresh(screen);
292 }
293
294 static void lang_screen_draw(struct lang_screen *screen,
295                 const struct config *config)
296 {
297         bool repost = false;
298         int height;
299
300         height = ARRAY_SIZE(languages) + 4;
301         if (!screen->pad || getmaxy(screen->pad) < height) {
302                 if (screen->pad)
303                         delwin(screen->pad);
304                 screen->pad = newpad(height, COLS);
305         }
306
307         if (screen->widgetset) {
308                 widgetset_unpost(screen->widgetset);
309                 talloc_free(screen->widgetset);
310                 repost = true;
311         }
312
313         screen->widgetset = widgetset_create(screen, screen->scr.main_ncw,
314                         screen->pad);
315         widgetset_set_widget_focus(screen->widgetset,
316                         lang_screen_widget_focus, screen);
317
318         if (!config) {
319                 lang_screen_setup_empty(screen);
320         } else {
321                 lang_screen_setup_widgets(screen, config);
322                 lang_screen_layout_widgets(screen);
323         }
324
325         if (repost)
326                 widgetset_post(screen->widgetset);
327 }
328
329 void lang_screen_update(struct lang_screen *screen,
330                 const struct config *config)
331 {
332         lang_screen_draw(screen, config);
333         pad_refresh(screen);
334 }
335
336 static int lang_screen_destroy(void *arg)
337 {
338         struct lang_screen *screen = arg;
339         if (screen->pad)
340                 delwin(screen->pad);
341         return 0;
342 }
343
344 struct lang_screen *lang_screen_init(struct cui *cui,
345                 const struct config *config,
346                 void (*on_exit)(struct cui *))
347 {
348         struct lang_screen *screen;
349
350         screen = talloc_zero(cui, struct lang_screen);
351         talloc_set_destructor(screen, lang_screen_destroy);
352         nc_scr_init(&screen->scr, pb_lang_screen_sig, 0,
353                         cui, lang_screen_process_key,
354                         lang_screen_post, lang_screen_unpost,
355                         lang_screen_resize);
356
357         screen->cui = cui;
358         screen->on_exit = on_exit;
359         screen->label_x = 2;
360         screen->field_x = 17;
361
362         screen->scr.frame.ltitle = talloc_strdup(screen,
363                         _("Petitboot Language Selection"));
364         screen->scr.frame.rtitle = NULL;
365         screen->scr.frame.help = talloc_strdup(screen,
366                         _("tab=next, shift+tab=previous, x=exit, h=help"));
367         nc_scr_frame_draw(&screen->scr);
368
369         scrollok(screen->scr.sub_ncw, true);
370
371         lang_screen_draw(screen, config);
372
373         return screen;
374 }