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