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