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