ui/ncurses: Add simple ncurses form widget set
[petitboot] / ui / ncurses / nc-widgets.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 #define _GNU_SOURCE
19
20 #include <string.h>
21 #include <ctype.h>
22
23 #include <talloc/talloc.h>
24 #include <types/types.h>
25 #include <log/log.h>
26 #include <util/util.h>
27
28 #include "config.h"
29 #include "nc-cui.h"
30 #include "nc-widgets.h"
31
32 #undef move
33
34 #define to_checkbox(w) container_of(w, struct nc_widget_checkbox, widget)
35 #define to_textbox(w) container_of(w, struct nc_widget_textbox, widget)
36 #define to_button(w) container_of(w, struct nc_widget_button, widget)
37 #define to_select(w) container_of(w, struct nc_widget_select, widget)
38
39 static const char *checkbox_checked_str = "[*]";
40 static const char *checkbox_unchecked_str = "[ ]";
41
42 static const char *select_selected_str = "(*)";
43 static const char *select_unselected_str = "( )";
44
45 struct nc_widgetset {
46         WINDOW  *mainwin;
47         WINDOW  *subwin;
48         FORM    *form;
49         FIELD   **fields;
50         int     n_fields, n_alloc_fields;
51         void    (*widget_focus)(struct nc_widget *, void *);
52         void    *widget_focus_arg;
53         FIELD   *cur_field;
54 };
55
56 struct nc_widget {
57         FIELD   *field;
58         bool    (*process_key)(struct nc_widget *, FORM *, int);
59         void    (*set_visible)(struct nc_widget *, bool);
60         void    (*move)(struct nc_widget *, int, int);
61         int     focussed_attr;
62         int     unfocussed_attr;
63         int     height;
64         int     width;
65         int     x;
66         int     y;
67 };
68
69 struct nc_widget_label {
70         struct nc_widget        widget;
71         const char              *text;
72 };
73
74 struct nc_widget_checkbox {
75         struct nc_widget        widget;
76         bool                    checked;
77 };
78
79 struct nc_widget_textbox {
80         struct nc_widget        widget;
81 };
82
83 struct nc_widget_select {
84         struct nc_widget        widget;
85         struct select_option {
86                 char            *str;
87                 int             val;
88                 FIELD           *field;
89         } *options;
90         int                     top, left, size;
91         int                     n_options, selected_option;
92         struct nc_widgetset     *set;
93         void                    (*on_change)(void *, int);
94         void                    *on_change_arg;
95 };
96
97 struct nc_widget_button {
98         struct nc_widget        widget;
99         void                    (*click)(void *arg);
100         void                    *arg;
101 };
102
103 static void widgetset_add_field(struct nc_widgetset *set, FIELD *field);
104
105 static bool process_key_nop(struct nc_widget *widget __attribute__((unused)),
106                 FORM *form __attribute((unused)),
107                 int key __attribute__((unused)))
108 {
109         return false;
110 }
111
112 static void field_set_visible(FIELD *field, bool visible)
113 {
114         int opts = field_opts(field) & ~O_VISIBLE;
115         if (visible)
116                 opts |= O_VISIBLE;
117         set_field_opts(field, opts);
118 }
119
120 static void field_move(FIELD *field, int y, int x)
121 {
122         move_field(field, y, x);
123 }
124
125 static int label_destructor(void *ptr)
126 {
127         struct nc_widget_label *label = ptr;
128         free_field(label->widget.field);
129         return 0;
130 }
131
132
133 struct nc_widget_label *widget_new_label(struct nc_widgetset *set,
134                 int y, int x, char *str)
135 {
136         struct nc_widget_label *label;
137         FIELD *f;
138         int len;
139
140         len = strlen(str);
141
142         label = talloc_zero(set, struct nc_widget_label);
143         label->widget.height = 1;
144         label->widget.width = len;
145         label->widget.x = x;
146         label->widget.y = y;
147         label->widget.process_key = process_key_nop;
148         label->widget.field = f = new_field(1, len, y, x, 0, 0);
149         label->widget.focussed_attr = A_NORMAL;
150         label->widget.unfocussed_attr = A_NORMAL;
151
152         field_opts_off(f, O_ACTIVE);
153         set_field_buffer(f, 0, str);
154         set_field_userptr(f, &label->widget);
155
156         widgetset_add_field(set, label->widget.field);
157         talloc_set_destructor(label, label_destructor);
158
159         return label;
160 }
161
162 bool widget_checkbox_get_value(struct nc_widget_checkbox *checkbox)
163 {
164         return checkbox->checked;
165 }
166
167 static void checkbox_set_buffer(struct nc_widget_checkbox *checkbox)
168 {
169         const char *str;
170         str = checkbox->checked ? checkbox_checked_str : checkbox_unchecked_str;
171         set_field_buffer(checkbox->widget.field, 0, str);
172 }
173
174 static bool checkbox_process_key(struct nc_widget *widget,
175                 FORM *form __attribute__((unused)), int key)
176 {
177         struct nc_widget_checkbox *checkbox = to_checkbox(widget);
178
179         if (key != ' ')
180                 return false;
181
182         checkbox->checked = !checkbox->checked;
183         checkbox_set_buffer(checkbox);
184
185         return true;
186 }
187
188 static int checkbox_destructor(void *ptr)
189 {
190         struct nc_widget_checkbox *checkbox = ptr;
191         free_field(checkbox->widget.field);
192         return 0;
193 }
194
195 struct nc_widget_checkbox *widget_new_checkbox(struct nc_widgetset *set,
196                 int y, int x, bool checked)
197 {
198         struct nc_widget_checkbox *checkbox;
199         FIELD *f;
200
201         checkbox = talloc_zero(set, struct nc_widget_checkbox);
202         checkbox->checked = checked;
203         checkbox->widget.height = 1;
204         checkbox->widget.width = strlen(checkbox_checked_str);
205         checkbox->widget.x = x;
206         checkbox->widget.y = y;
207         checkbox->widget.process_key = checkbox_process_key;
208         checkbox->widget.focussed_attr = A_REVERSE;
209         checkbox->widget.unfocussed_attr = A_NORMAL;
210         checkbox->widget.field = f = new_field(1, strlen(checkbox_checked_str),
211                         y, x, 0, 0);
212
213         field_opts_off(f, O_EDIT);
214         set_field_userptr(f, &checkbox->widget);
215         checkbox_set_buffer(checkbox);
216
217         widgetset_add_field(set, checkbox->widget.field);
218         talloc_set_destructor(checkbox, checkbox_destructor);
219
220         return checkbox;
221 }
222
223 static char *strip_string(char *str)
224 {
225         int len, i;
226
227         len = strlen(str);
228
229         /* clear trailing space */
230         for (i = len - 1; i >= 0; i--) {
231                 if (!isspace(str[i]))
232                         break;
233                 str[i] = '\0';
234         }
235
236         /* increment str past leading space */
237         for (i = 0; i < len; i++) {
238                 if (str[i] == '\0' || !isspace(str[i]))
239                         break;
240         }
241
242         return str + i;
243 }
244
245 char *widget_textbox_get_value(struct nc_widget_textbox *textbox)
246 {
247         char *str = field_buffer(textbox->widget.field, 0);
248         return str ? strip_string(str) : NULL;
249 }
250
251 static bool textbox_process_key(
252                 struct nc_widget *widget __attribute__((unused)),
253                 FORM *form, int key)
254 {
255         switch (key) {
256         case KEY_HOME:
257                 form_driver(form, REQ_BEG_FIELD);
258                 break;
259         case KEY_END:
260                 form_driver(form, REQ_END_FIELD);
261                 break;
262         case KEY_LEFT:
263                 form_driver(form, REQ_LEFT_CHAR);
264                 break;
265         case KEY_RIGHT:
266                 form_driver(form, REQ_RIGHT_CHAR);
267                 break;
268         case KEY_BACKSPACE:
269                 if (form_driver(form, REQ_LEFT_CHAR) != E_OK)
270                         break;
271                 /* fall through */
272         case KEY_DC:
273                 form_driver(form, REQ_DEL_CHAR);
274                 break;
275         default:
276                 form_driver(form, key);
277                 break;
278         }
279
280         return true;
281 }
282
283 static int textbox_destructor(void *ptr)
284 {
285         struct nc_widget_textbox *textbox = ptr;
286         free_field(textbox->widget.field);
287         return 0;
288 }
289
290 struct nc_widget_textbox *widget_new_textbox(struct nc_widgetset *set,
291                 int y, int x, int len, char *str)
292 {
293         struct nc_widget_textbox *textbox;
294         FIELD *f;
295
296         textbox = talloc_zero(set, struct nc_widget_textbox);
297         textbox->widget.height = 1;
298         textbox->widget.width = len;
299         textbox->widget.x = x;
300         textbox->widget.y = y;
301         textbox->widget.process_key = textbox_process_key;
302         textbox->widget.field = f = new_field(1, len, y, x, 0, 0);
303         textbox->widget.focussed_attr = A_REVERSE;
304         textbox->widget.unfocussed_attr = A_UNDERLINE;
305
306         field_opts_off(f, O_STATIC | O_WRAP | O_BLANK);
307         set_field_buffer(f, 0, str);
308         set_field_back(f, textbox->widget.unfocussed_attr);
309         set_field_userptr(f, &textbox->widget);
310
311         widgetset_add_field(set, textbox->widget.field);
312         talloc_set_destructor(textbox, textbox_destructor);
313
314         return textbox;
315 }
316
317 static void select_option_change(struct select_option *opt, bool selected)
318 {
319         const char *str;
320
321         str = selected ? select_selected_str : select_unselected_str;
322
323         memcpy(opt->str, str, strlen(str));
324         set_field_buffer(opt->field, 0, opt->str);
325 }
326
327 static bool select_process_key(struct nc_widget *w, FORM *form, int key)
328 {
329         struct nc_widget_select *select = to_select(w);
330         struct select_option *new_opt, *old_opt;
331         int i, new_idx;
332         FIELD *field;
333
334         switch (key) {
335         case ' ':
336         case KEY_ENTER:
337                 break;
338         default:
339                 return false;
340         }
341
342         field = current_field(form);
343         new_opt = NULL;
344
345         for (i = 0; i < select->n_options; i++) {
346                 if (select->options[i].field == field) {
347                         new_opt = &select->options[i];
348                         new_idx = i;
349                         break;
350                 }
351         }
352
353         if (!new_opt)
354                 return true;
355
356         if (new_idx == select->selected_option)
357                 return true;
358
359         old_opt = &select->options[select->selected_option];
360
361         select_option_change(old_opt, false);
362         select_option_change(new_opt, true);
363
364         select->selected_option = new_idx;
365
366         if (select->on_change)
367                 select->on_change(select->on_change_arg, new_opt->val);
368
369         return true;
370 }
371
372 static void select_set_visible(struct nc_widget *widget, bool visible)
373 {
374         struct nc_widget_select *select = to_select(widget);
375         int i;
376
377         for (i = 0; i < select->n_options; i++)
378                 field_set_visible(select->options[i].field, visible);
379 }
380
381 static void select_move(struct nc_widget *widget, int y, int x)
382 {
383         struct nc_widget_select *select = to_select(widget);
384         int i;
385
386         for (i = 0; i < select->n_options; i++)
387                 field_move(select->options[i].field, y + i, x);
388 }
389
390 static int select_destructor(void *ptr)
391 {
392         struct nc_widget_select *select = ptr;
393         int i;
394
395         for (i = 0; i < select->n_options; i++)
396                 free_field(select->options[i].field);
397
398         return 0;
399 }
400
401 struct nc_widget_select *widget_new_select(struct nc_widgetset *set,
402                 int y, int x, int len)
403 {
404         struct nc_widget_select *select;
405
406         select = talloc_zero(set, struct nc_widget_select);
407         select->widget.width = len;
408         select->widget.height = 0;
409         select->widget.x = x;
410         select->widget.y = y;
411         select->widget.process_key = select_process_key;
412         select->widget.set_visible = select_set_visible;
413         select->widget.move = select_move;
414         select->widget.focussed_attr = A_REVERSE;
415         select->widget.unfocussed_attr = A_NORMAL;
416         select->top = y;
417         select->left = x;
418         select->size = len;
419         select->set = set;
420
421         talloc_set_destructor(select, select_destructor);
422
423         return select;
424 }
425
426 void widget_select_add_option(struct nc_widget_select *select, int value,
427                 const char *text, bool selected)
428 {
429         const char *str;
430         FIELD *f;
431         int i;
432
433         /* if we never see an option with selected set, we want the first
434          * one to be selected */
435         if (select->n_options == 0)
436                 selected = true;
437         else if (selected)
438                 select_option_change(&select->options[select->selected_option],
439                                         false);
440
441         if (selected) {
442                 select->selected_option = select->n_options;
443                 str = select_selected_str;
444         } else
445                 str = select_unselected_str;
446
447         i = select->n_options++;
448         select->widget.height = select->n_options;
449
450         select->options = talloc_realloc(select, select->options,
451                                 struct select_option, i + 2);
452         select->options[i].val = value;
453         select->options[i].str = talloc_asprintf(select->options,
454                                         "%s %s", str, text);
455
456         select->options[i].field = f = new_field(1, select->size,
457                                                 select->top + i,
458                                                 select->left, 0, 0);
459
460         field_opts_off(f, O_WRAP | O_EDIT);
461         set_field_userptr(f, &select->widget);
462         set_field_buffer(f, 0, select->options[i].str);
463
464         widgetset_add_field(select->set, f);
465 }
466
467 int widget_select_get_value(struct nc_widget_select *select)
468 {
469         return select->options[select->selected_option].val;
470 }
471
472 int widget_select_height(struct nc_widget_select *select)
473 {
474         return select->n_options;
475 }
476
477 void widget_select_on_change(struct nc_widget_select *select,
478                 void (*on_change)(void *, int), void *arg)
479 {
480         select->on_change = on_change;
481         select->on_change_arg = arg;
482 }
483
484 static bool button_process_key(struct nc_widget *widget,
485                 FORM *form __attribute__((unused)), int key)
486 {
487         struct nc_widget_button *button = to_button(widget);
488
489         if (!button->click)
490                 return false;
491
492         switch (key) {
493         case ' ':
494         case '\r':
495         case '\n':
496                 button->click(button->arg);
497                 return true;
498         }
499
500         return false;
501 }
502
503 static int button_destructor(void *ptr)
504 {
505         struct nc_widget_button *button = ptr;
506         free_field(button->widget.field);
507         return 0;
508 }
509
510 struct nc_widget_button *widget_new_button(struct nc_widgetset *set,
511                 int y, int x, int size, const char *str,
512                 void (*click)(void *), void *arg)
513 {
514         struct nc_widget_button *button;
515         char *text;
516         FIELD *f;
517         int idx, len;
518
519         button = talloc_zero(set, struct nc_widget_button);
520         button->widget.height = 1;
521         button->widget.width = size;
522         button->widget.x = x;
523         button->widget.y = y;
524         button->widget.field = f = new_field(1, size + 2, y, x, 0, 0);
525         button->widget.process_key = button_process_key;
526         button->widget.focussed_attr = A_REVERSE;
527         button->widget.unfocussed_attr = A_NORMAL;
528         button->click = click;
529         button->arg = arg;
530
531         field_opts_off(f, O_EDIT);
532         set_field_userptr(f, &button->widget);
533
534         /* center str in a size-char buffer, but don't overrun */
535         len = strlen(str);
536         len = min(len, size);
537         idx = (size - len) / 2;
538
539         text = talloc_array(button, char, size + 3);
540         memset(text, ' ', size + 2);
541         memcpy(text + idx + 1, str, len);
542         text[0] = '[';
543         text[size + 1] = ']';
544         text[size + 2] = '\0';
545
546         set_field_buffer(f, 0, text);
547
548         widgetset_add_field(set, button->widget.field);
549         talloc_set_destructor(button, button_destructor);
550
551         return button;
552 }
553
554 static void widget_focus_change(struct nc_widget *widget, FIELD *field,
555                 bool focussed)
556 {
557         int attr = focussed ? widget->focussed_attr : widget->unfocussed_attr;
558         set_field_back(field, attr);
559 }
560
561 bool widgetset_process_key(struct nc_widgetset *set, int key)
562 {
563         struct nc_widget *widget;
564         FIELD *field;
565         int req = 0;
566
567         field = current_field(set->form);
568         assert(field);
569
570         /* handle field change events */
571         switch (key) {
572         case KEY_BTAB:
573         case KEY_UP:
574                 req = REQ_PREV_FIELD;
575                 break;
576         case '\t':
577         case KEY_DOWN:
578                 req = REQ_NEXT_FIELD;
579                 break;
580         case KEY_PPAGE:
581                 req = REQ_FIRST_FIELD;
582                 break;
583         case KEY_NPAGE:
584                 req = REQ_LAST_FIELD;
585                 break;
586         }
587
588         widget = field_userptr(field);
589         if (req) {
590                 widget_focus_change(widget, field, false);
591                 form_driver(set->form, req);
592                 form_driver(set->form, REQ_END_FIELD);
593                 field = current_field(set->form);
594                 widget = field_userptr(field);
595                 widget_focus_change(widget, field, true);
596                 if (set->widget_focus)
597                         set->widget_focus(widget, set->widget_focus_arg);
598                 return true;
599         }
600
601         if (!widget->process_key)
602                 return false;
603
604         return widget->process_key(widget, set->form, key);
605 }
606
607 static int widgetset_destructor(void *ptr)
608 {
609         struct nc_widgetset *set = ptr;
610         free_form(set->form);
611         return 0;
612 }
613
614 struct nc_widgetset *widgetset_create(void *ctx, WINDOW *main, WINDOW *sub)
615 {
616         struct nc_widgetset *set;
617
618         set = talloc_zero(ctx, struct nc_widgetset);
619         set->n_alloc_fields = 8;
620         set->mainwin = main;
621         set->subwin = sub;
622         set->fields = talloc_array(set, FIELD *, set->n_alloc_fields);
623         talloc_set_destructor(set, widgetset_destructor);
624
625         return set;
626 }
627
628 void widgetset_set_widget_focus(struct nc_widgetset *set,
629                 widget_focus_cb cb, void *arg)
630 {
631         set->widget_focus = cb;
632         set->widget_focus_arg = arg;
633 }
634
635 void widgetset_post(struct nc_widgetset *set)
636 {
637         struct nc_widget *widget;
638         FIELD *field;
639
640         set->form = new_form(set->fields);
641         set_form_win(set->form, set->mainwin);
642         set_form_sub(set->form, set->subwin);
643         post_form(set->form);
644         form_driver(set->form, REQ_END_FIELD);
645
646         if (set->cur_field) {
647                 set_current_field(set->form, set->cur_field);
648                 field = set->cur_field;
649         }
650
651         field = current_field(set->form);
652         widget = field_userptr(field);
653         widget_focus_change(widget, field, true);
654         if (set->widget_focus)
655                 set->widget_focus(widget, set->widget_focus_arg);
656 }
657
658 void widgetset_unpost(struct nc_widgetset *set)
659 {
660         set->cur_field = current_field(set->form);
661         unpost_form(set->form);
662         free_form(set->form);
663         set->form = NULL;
664 }
665
666 static void widgetset_add_field(struct nc_widgetset *set, FIELD *field)
667 {
668         if (set->n_fields == set->n_alloc_fields - 1) {
669                 set->n_alloc_fields *= 2;
670                 set->fields = talloc_realloc(set, set->fields,
671                                 FIELD *, set->n_alloc_fields);
672         }
673
674         set->n_fields++;
675         set->fields[set->n_fields - 1] = field;
676         set->fields[set->n_fields] = NULL;
677 }
678
679 #define DECLARE_BASEFN(type) \
680         struct nc_widget *widget_ ## type ## _base              \
681                 (struct nc_widget_ ## type *w)                  \
682         { return &w->widget; }
683
684 DECLARE_BASEFN(textbox);
685 DECLARE_BASEFN(checkbox);
686 DECLARE_BASEFN(select);
687 DECLARE_BASEFN(label);
688 DECLARE_BASEFN(button);
689
690 void widget_set_visible(struct nc_widget *widget, bool visible)
691 {
692         if (widget->set_visible)
693                 widget->set_visible(widget, visible);
694         else
695                 field_set_visible(widget->field, visible);
696 }
697
698 void widget_move(struct nc_widget *widget, int y, int x)
699 {
700         if (widget->move)
701                 widget->move(widget, y, x);
702         else
703                 field_move(widget->field, y, x);
704
705         widget->x = x;
706         widget->y = y;
707 }
708
709 int widget_height(struct nc_widget *widget)
710 {
711         return widget->height;
712 }
713
714 int widget_width(struct nc_widget *widget)
715 {
716         return widget->width;
717 }
718
719 int widget_x(struct nc_widget *widget)
720 {
721         return widget->x;
722 }
723
724 int widget_y(struct nc_widget *widget)
725 {
726         return widget->y;
727 }
728