]> git.ozlabs.org Git - petitboot/blob - ui/ncurses/nc-widgets.c
8f8816ecaa7c472b33fe9c52d0e0305556b6c621
[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 #if defined(HAVE_CONFIG_H)
19 #include "config.h"
20 #endif
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 #include <i18n/i18n.h>
55 #include <fold/fold.h>
56
57 #include "nc-cui.h"
58 #include "nc-widgets.h"
59
60 #undef move
61
62 #define to_checkbox(w) container_of(w, struct nc_widget_checkbox, widget)
63 #define to_textbox(w) container_of(w, struct nc_widget_textbox, widget)
64 #define to_button(w) container_of(w, struct nc_widget_button, widget)
65 #define to_select(w) container_of(w, struct nc_widget_select, widget)
66 #define to_subset(w) container_of(w, struct nc_widget_subset, widget)
67
68 static const char *checkbox_checked_str = "[*]";
69 static const char *checkbox_unchecked_str = "[ ]";
70
71 static const char *select_selected_str = "(*)";
72 static const char *select_unselected_str = "( )";
73
74 struct nc_widgetset {
75         WINDOW  *mainwin;
76         WINDOW  *subwin;
77         FORM    *form;
78         FIELD   **fields;
79         int     n_fields, n_alloc_fields;
80         void    (*widget_focus)(struct nc_widget *, void *);
81         void    *widget_focus_arg;
82         FIELD   *cur_field;
83
84         /* custom validators */
85         FIELDTYPE *ipv4_multi_type;
86 };
87
88 struct nc_widget {
89         FIELD   *field;
90         bool    (*process_key)(struct nc_widget *, FORM *, int);
91         void    (*set_visible)(struct nc_widget *, bool);
92         void    (*move)(struct nc_widget *, int, int);
93         void    (*field_focus)(struct nc_widget *, FIELD *);
94         int     focussed_attr;
95         int     unfocussed_attr;
96         int     height;
97         int     width;
98         int     focus_y;
99         int     x;
100         int     y;
101 };
102
103 struct nc_widget_label {
104         struct nc_widget        widget;
105         const char              *text;
106 };
107
108 struct nc_widget_checkbox {
109         struct nc_widget        widget;
110         bool                    checked;
111 };
112
113 struct nc_widget_textbox {
114         struct nc_widgetset     *set;
115         struct nc_widget        widget;
116 };
117
118 struct nc_widget_subset {
119         struct nc_widget        widget;
120         int                     *active;
121         int                     n_active;
122         struct subset_option {
123                 char            *str;
124                 int             val;
125                 FIELD           *field;
126         } *options;
127         int                     n_options;
128         int                     top, left, size;
129         struct nc_widgetset     *set;
130         void                    (*on_change)(void *, int);
131         void                    *on_change_arg;
132         void                    (*screen_cb)(void *,
133                                         struct nc_widget_subset *, int);
134 };
135
136 struct nc_widget_select {
137         struct nc_widget        widget;
138         struct select_option {
139                 char            *str;
140                 int             val;
141                 FIELD           *field;
142                 int             lines;
143         } *options;
144         int                     top, left, size;
145         int                     n_options, selected_option;
146         struct nc_widgetset     *set;
147         void                    (*on_change)(void *, int);
148         void                    *on_change_arg;
149 };
150
151 struct nc_widget_button {
152         struct nc_widget        widget;
153         void                    (*click)(void *arg);
154         void                    *arg;
155 };
156
157 static void widgetset_add_field(struct nc_widgetset *set, FIELD *field);
158 static void widgetset_remove_field(struct nc_widgetset *set, FIELD *field);
159
160 static bool key_is_select(int key)
161 {
162         return key == ' ' || key == '\r' || key == '\n' || key == KEY_ENTER;
163 }
164
165 static bool process_key_nop(struct nc_widget *widget __attribute__((unused)),
166                 FORM *form __attribute((unused)),
167                 int key __attribute__((unused)))
168 {
169         return false;
170 }
171
172 static void field_set_visible(FIELD *field, bool visible)
173 {
174         int opts = field_opts(field) & ~O_VISIBLE;
175         if (visible)
176                 opts |= O_VISIBLE;
177         set_field_opts(field, opts);
178 }
179
180 static bool field_visible(FIELD *field)
181 {
182         return (field_opts(field) & O_VISIBLE) == O_VISIBLE;
183 }
184
185 static void field_move(FIELD *field, int y, int x)
186 {
187         move_field(field, y, x);
188 }
189
190 static int label_destructor(void *ptr)
191 {
192         struct nc_widget_label *label = ptr;
193         free_field(label->widget.field);
194         return 0;
195 }
196
197
198 struct nc_widget_label *widget_new_label(struct nc_widgetset *set,
199                 int y, int x, char *str)
200 {
201         struct nc_widget_label *label;
202         FIELD *f;
203         int len;
204
205         len = strlen(str);
206
207         label = talloc_zero(set, struct nc_widget_label);
208         label->widget.height = 1;
209         label->widget.width = len;
210         label->widget.x = x;
211         label->widget.y = y;
212         label->widget.process_key = process_key_nop;
213         label->widget.field = f = new_field(1, len, y, x, 0, 0);
214         label->widget.focussed_attr = A_NORMAL;
215         label->widget.unfocussed_attr = A_NORMAL;
216
217         field_opts_off(f, O_ACTIVE);
218         set_field_buffer(f, 0, str);
219         set_field_userptr(f, &label->widget);
220
221         widgetset_add_field(set, label->widget.field);
222         talloc_set_destructor(label, label_destructor);
223
224         return label;
225 }
226
227 bool widget_checkbox_get_value(struct nc_widget_checkbox *checkbox)
228 {
229         return checkbox->checked;
230 }
231
232 static void checkbox_set_buffer(struct nc_widget_checkbox *checkbox)
233 {
234         const char *str;
235         str = checkbox->checked ? checkbox_checked_str : checkbox_unchecked_str;
236         set_field_buffer(checkbox->widget.field, 0, str);
237 }
238
239 static bool checkbox_process_key(struct nc_widget *widget,
240                 FORM *form __attribute__((unused)), int key)
241 {
242         struct nc_widget_checkbox *checkbox = to_checkbox(widget);
243
244         if (!key_is_select(key))
245                 return false;
246
247         checkbox->checked = !checkbox->checked;
248         checkbox_set_buffer(checkbox);
249
250         return true;
251 }
252
253 static int checkbox_destructor(void *ptr)
254 {
255         struct nc_widget_checkbox *checkbox = ptr;
256         free_field(checkbox->widget.field);
257         return 0;
258 }
259
260 struct nc_widget_checkbox *widget_new_checkbox(struct nc_widgetset *set,
261                 int y, int x, bool checked)
262 {
263         struct nc_widget_checkbox *checkbox;
264         FIELD *f;
265
266         checkbox = talloc_zero(set, struct nc_widget_checkbox);
267         checkbox->checked = checked;
268         checkbox->widget.height = 1;
269         checkbox->widget.width = strlen(checkbox_checked_str);
270         checkbox->widget.x = x;
271         checkbox->widget.y = y;
272         checkbox->widget.process_key = checkbox_process_key;
273         checkbox->widget.focussed_attr = A_REVERSE;
274         checkbox->widget.unfocussed_attr = A_NORMAL;
275         checkbox->widget.field = f = new_field(1, strlen(checkbox_checked_str),
276                         y, x, 0, 0);
277
278         field_opts_off(f, O_EDIT);
279         set_field_userptr(f, &checkbox->widget);
280         checkbox_set_buffer(checkbox);
281
282         widgetset_add_field(set, checkbox->widget.field);
283         talloc_set_destructor(checkbox, checkbox_destructor);
284
285         return checkbox;
286 }
287
288 static char *strip_string(char *str)
289 {
290         int len, i;
291
292         len = strlen(str);
293
294         /* clear trailing space */
295         for (i = len - 1; i >= 0; i--) {
296                 if (!isspace(str[i]))
297                         break;
298                 str[i] = '\0';
299         }
300
301         /* increment str past leading space */
302         for (i = 0; i < len; i++) {
303                 if (str[i] == '\0' || !isspace(str[i]))
304                         break;
305         }
306
307         return str + i;
308 }
309
310 char *widget_textbox_get_value(struct nc_widget_textbox *textbox)
311 {
312         char *str = field_buffer(textbox->widget.field, 0);
313         return str ? strip_string(str) : NULL;
314 }
315
316 static bool textbox_process_key(
317                 struct nc_widget *widget __attribute__((unused)),
318                 FORM *form, int key)
319 {
320         switch (key) {
321         case KEY_HOME:
322                 form_driver(form, REQ_BEG_FIELD);
323                 break;
324         case KEY_END:
325                 form_driver(form, REQ_END_FIELD);
326                 break;
327         case KEY_LEFT:
328                 form_driver(form, REQ_LEFT_CHAR);
329                 break;
330         case KEY_RIGHT:
331                 form_driver(form, REQ_RIGHT_CHAR);
332                 break;
333         case KEY_BACKSPACE:
334                 if (form_driver(form, REQ_LEFT_CHAR) != E_OK)
335                         break;
336                 /* fall through */
337         case KEY_DC:
338                 form_driver(form, REQ_DEL_CHAR);
339                 break;
340         default:
341                 form_driver(form, key);
342                 break;
343         }
344
345         return true;
346 }
347
348 static int textbox_destructor(void *ptr)
349 {
350         struct nc_widget_textbox *textbox = ptr;
351         free_field(textbox->widget.field);
352         return 0;
353 }
354
355 struct nc_widget_textbox *widget_new_textbox(struct nc_widgetset *set,
356                 int y, int x, int len, char *str)
357 {
358         struct nc_widget_textbox *textbox;
359         FIELD *f;
360
361         textbox = talloc_zero(set, struct nc_widget_textbox);
362         textbox->set = set;
363         textbox->widget.height = 1;
364         textbox->widget.width = len;
365         textbox->widget.x = x;
366         textbox->widget.y = y;
367         textbox->widget.process_key = textbox_process_key;
368         textbox->widget.field = f = new_field(1, len, y, x, 0, 0);
369         textbox->widget.focussed_attr = A_REVERSE;
370         textbox->widget.unfocussed_attr = A_UNDERLINE;
371
372         field_opts_off(f, O_STATIC | O_WRAP | O_BLANK);
373         set_field_buffer(f, 0, str);
374         set_field_back(f, textbox->widget.unfocussed_attr);
375         set_field_userptr(f, &textbox->widget);
376
377         widgetset_add_field(set, textbox->widget.field);
378         talloc_set_destructor(textbox, textbox_destructor);
379
380         return textbox;
381 }
382
383 void widget_textbox_set_fixed_size(struct nc_widget_textbox *textbox)
384 {
385         field_opts_on(textbox->widget.field, O_STATIC);
386 }
387
388 void widget_textbox_set_validator_integer(struct nc_widget_textbox *textbox,
389                 long min, long max)
390 {
391         set_field_type(textbox->widget.field, TYPE_INTEGER, 1, min, max);
392 }
393
394 void widget_textbox_set_validator_ipv4(struct nc_widget_textbox *textbox)
395 {
396         set_field_type(textbox->widget.field, TYPE_IPV4);
397 }
398
399 static bool check_ipv4_multi_char(int c,
400                 const void *arg __attribute__((unused)))
401 {
402         return isdigit(c) || c == '.' || c == ' ';
403 }
404
405 static bool check_ipv4_multi_field(FIELD *field,
406                 const void *arg __attribute__((unused)))
407 {
408         char *buf = field_buffer(field, 0);
409         unsigned int ip[4];
410         int n, len;
411
412         while (*buf != '\0') {
413                 n = sscanf(buf, "%u.%u.%u.%u%n",
414                                 &ip[0], &ip[1], &ip[2], &ip[3], &len);
415                 if (n != 4)
416                         return false;
417
418                 if (ip[0] > 255 || ip[1] > 255 || ip[2] > 255 || ip[3] > 255)
419                         return false;
420
421                 for (buf += len; *buf != '\0'; buf++) {
422                         if (isspace(*buf))
423                                 continue;
424                         else if (isdigit(*buf))
425                                 break;
426                         else
427                                 return false;
428                 }
429         }
430
431         return true;
432 }
433
434 void widget_textbox_set_validator_ipv4_multi(struct nc_widget_textbox *textbox)
435 {
436         if (!textbox->set->ipv4_multi_type) {
437                 textbox->set->ipv4_multi_type = new_fieldtype(
438                                 check_ipv4_multi_field,
439                                 check_ipv4_multi_char);
440         }
441         set_field_type(textbox->widget.field, textbox->set->ipv4_multi_type);
442 }
443
444 static void subset_update_order(struct nc_widget_subset *subset)
445 {
446         char *str;
447         int i, val;
448
449         for (i = 0; i < subset->n_active; i++) {
450                 val = subset->active[i];
451                 str = talloc_asprintf(subset, "(%d) %s",
452                                       i, subset->options[val].str);
453                 set_field_buffer(subset->options[val].field, 0,
454                                  str);
455                 talloc_free(str);
456         }
457 }
458
459 static void widget_focus_change(struct nc_widget *widget, FIELD *field,
460                 bool focussed);
461
462 static void subset_delete_active(struct nc_widget_subset *subset, int idx)
463 {
464         bool last = idx == (subset->n_active - 1);
465         struct nc_widgetset *set = subset->set;
466         struct nc_widget *widget;
467         size_t rem;
468         int i, val;
469         uint32_t opts;
470
471         /* Shift field focus to nearest active option or next visible field */
472         if (subset->n_active > 1) {
473                 if (last)
474                         val = subset->active[idx - 1];
475                 else
476                         val = subset->active[idx + 1];
477                 set->cur_field = subset->options[val].field;
478         } else {
479                 for (i = 0; i < set->n_fields; i++)
480                         if (field_visible(set->fields[i])) {
481                                 opts = field_opts(set->fields[i]);
482                                 if ((opts & O_ACTIVE) == O_ACTIVE) {
483                                         set->cur_field = set->fields[i];
484                                         break;
485                                 }
486                         }
487         }
488
489         set_current_field(set->form, set->cur_field);
490         widget = field_userptr(set->cur_field);
491         widget_focus_change(widget, set->cur_field, true);
492         if (set->widget_focus)
493                 set->widget_focus(widget, set->widget_focus_arg);
494
495         /* Update active array */
496         rem = sizeof(int) * (subset->n_active - idx - 1);
497         val = subset->active[idx];
498         field_set_visible(subset->options[val].field, false);
499         if (rem)
500                 memmove(&subset->active[idx], &subset->active[idx + 1], rem);
501         subset->n_active--;
502         subset->active = talloc_realloc(subset, subset->active,
503                                          int, subset->n_active);
504
505         subset->widget.height = subset->n_active;
506 }
507
508 static bool subset_process_key(struct nc_widget *w, FORM *form, int key)
509 {
510         struct nc_widget_subset *subset = to_subset(w);
511         int i, val, opt_idx = -1;
512         FIELD *field;
513
514         if (key != '-' && key != '+' && key != KEY_DC && key != KEY_BACKSPACE)
515                 return false;
516
517         field = current_field(form);
518
519         for (i = 0; i < subset->n_active; i++) {
520                 val = subset->active[i];
521                 if (subset->options[val].field == field) {
522                         opt_idx = i;
523                         break;
524                 }
525         }
526
527         if (opt_idx < 0)
528                 return false;
529
530         if (key == KEY_DC || key == KEY_BACKSPACE)
531                 subset_delete_active(subset, opt_idx);
532
533         if (key == '-') {
534                 if (opt_idx == 0)
535                         return true;
536
537                 val = subset->active[opt_idx];
538                 subset->active[opt_idx] = subset->active[opt_idx - 1];
539                 subset->active[opt_idx - 1] = val;
540         }
541
542         if (key == '+') {
543                 if (opt_idx >= subset->n_active - 1)
544                         return true;
545
546                 val = subset->active[opt_idx];
547                 subset->active[opt_idx] = subset->active[opt_idx + 1];
548                 subset->active[opt_idx + 1] = val;
549         }
550
551         subset_update_order(subset);
552
553         if (subset->on_change)
554                 subset->on_change(subset->on_change_arg, 0);
555
556         return true;
557 }
558
559 static void subset_set_visible(struct nc_widget *widget, bool visible)
560 {
561         struct nc_widget_subset *subset = to_subset(widget);
562         int i, val;
563
564         for (i = 0; i < subset->n_active; i++) {
565                 val = subset->active[i];
566                 field_set_visible(subset->options[val].field, visible);
567         }
568 }
569
570 static void subset_move(struct nc_widget *widget, int y, int x)
571 {
572         struct nc_widget_subset *subset = to_subset(widget);
573         int i, val;
574
575         for (i = 0; i < subset->n_active; i++) {
576                 val = subset->active[i];
577                 field_move(subset->options[val].field, y + i , x);
578         }
579 }
580
581 static void subset_field_focus(struct nc_widget *widget, FIELD *field)
582 {
583         struct nc_widget_subset *subset = to_subset(widget);
584         int i, val;
585
586         for (i = 0; i < subset->n_active; i++) {
587                 val = subset->active[i];
588                 if (field == subset->options[val].field) {
589                         widget->focus_y = i;
590                         return;
591                 }
592         }
593 }
594
595 static int subset_destructor(void *ptr)
596 {
597         struct nc_widget_subset *subset = ptr;
598         int i;
599
600         for (i = 0; i < subset->n_options; i++)
601                 free_field(subset->options[i].field);
602
603         return 0;
604 }
605
606 struct nc_widget_subset *widget_new_subset(struct nc_widgetset *set,
607                 int y, int x, int len, void *screen_cb)
608 {
609         struct nc_widget_subset *subset;
610
611         subset = talloc_zero(set, struct nc_widget_subset);
612         subset->widget.width = len;
613         subset->widget.height = 0;
614         subset->widget.x = x;
615         subset->widget.y = y;
616         subset->widget.process_key = subset_process_key;
617         subset->widget.set_visible = subset_set_visible;
618         subset->widget.move = subset_move;
619         subset->widget.field_focus = subset_field_focus;
620         subset->widget.focussed_attr = A_REVERSE;
621         subset->widget.unfocussed_attr = A_NORMAL;
622         subset->top = y;
623         subset->left = x;
624         subset->size = len;
625         subset->set = set;
626         subset->n_active = subset->n_options = 0;
627         subset->active = NULL;
628         subset->options = NULL;
629         subset->screen_cb = screen_cb;
630
631         talloc_set_destructor(subset, subset_destructor);
632
633         return subset;
634 }
635
636 void widget_subset_add_option(struct nc_widget_subset *subset, const char *text)
637 {
638         FIELD *f;
639         int i;
640
641         i = subset->n_options++;
642         subset->options = talloc_realloc(subset, subset->options,
643                                          struct subset_option, i + 1);
644
645         subset->options[i].str = talloc_strdup(subset->options, text);
646
647         subset->options[i].field = f = new_field(1, subset->size, subset->top + i,
648                                                  subset->left, 0, 0);
649
650         field_opts_off(f, O_WRAP | O_EDIT);
651         set_field_userptr(f, &subset->widget);
652         set_field_buffer(f, 0, subset->options[i].str);
653         field_set_visible(f, false);
654         widgetset_add_field(subset->set, f);
655 }
656
657 void widget_subset_make_active(struct nc_widget_subset *subset, int idx)
658 {
659         int i;
660
661         for (i = 0; i < subset->n_active; i++)
662                 if (subset->active[i] == idx) {
663                         pb_debug("%s: Index %d already active\n", __func__, idx);
664                         return;
665                 }
666
667         i = subset->n_active++;
668         subset->widget.height = subset->n_active;
669         subset->active = talloc_realloc(subset, subset->active,
670                                         int, i + 1);
671         subset->active[i] = idx;
672
673         subset_update_order(subset);
674 }
675
676 int widget_subset_get_order(void *ctx, unsigned int **order,
677                 struct nc_widget_subset *subset)
678 {
679         unsigned int *buf = talloc_array(ctx, unsigned int, subset->n_active);
680         int i;
681
682         for (i = 0; i < subset->n_active; i++)
683                 buf[i] = subset->active[i];
684
685         *order = buf;
686         return i;
687 }
688
689 void widget_subset_show_inactive(struct nc_widget_subset *subset,
690                 struct nc_widget_select *select)
691 {
692         bool active = false, first = true;
693         int i, j;
694
695         for (i = 0; i < subset->n_options; i++) {
696                 active = false;
697                 for (j = 0; j < subset->n_active; j++)
698                         if (subset->active[j] == i)
699                                 active = true;
700
701                 if (active)
702                         continue;
703
704                 widget_select_add_option(select, i,
705                                          subset->options[i].str, first);
706                 if (first)
707                         first = false;
708         }
709 }
710
711 int widget_subset_n_inactive(struct nc_widget_subset *subset)
712 {
713         return subset->n_options - subset->n_active;
714 }
715
716 int widget_subset_height(struct nc_widget_subset *subset)
717 {
718         return subset->n_active;
719 }
720
721 void widget_subset_on_change(struct nc_widget_subset *subset,
722                 void (*on_change)(void *, int), void *arg)
723 {
724         subset->on_change = on_change;
725         subset->on_change_arg = arg;
726 }
727
728 void widget_subset_drop_options(struct nc_widget_subset *subset)
729 {
730         struct nc_widgetset *set = subset->set;
731         int i;
732
733         for (i = 0; i < subset->n_options; i++) {
734                 FIELD *field = subset->options[i].field;
735                 widgetset_remove_field(set, field);
736                 if (field == set->cur_field)
737                         set->cur_field = NULL;
738                 free_field(subset->options[i].field);
739         }
740
741         talloc_free(subset->options);
742         talloc_free(subset->active);
743         subset->options = NULL;
744         subset->active = NULL;
745         subset->n_options = 0;
746         subset->n_active = 0;
747         subset->widget.height = 0;
748         subset->widget.focus_y = 0;
749 }
750
751 void widget_subset_clear_active(struct nc_widget_subset *subset)
752 {
753         int i;
754
755         for (i = 0; i < subset->n_options; i++)
756                 field_set_visible(subset->options[i].field, false);
757
758         talloc_free(subset->active);
759         subset->active = NULL;
760         subset->n_active = 0;
761         subset->widget.height = 0;
762         subset->widget.focus_y = 0;
763 }
764
765 void widget_subset_callback(void *arg,
766                 struct nc_widget_subset *subset, int idx)
767 {
768         subset->screen_cb(arg, subset, idx);
769 }
770
771 static void select_option_change(struct select_option *opt, bool selected)
772 {
773         const char *str;
774
775         str = selected ? select_selected_str : select_unselected_str;
776
777         memcpy(opt->str, str, strlen(str));
778         set_field_buffer(opt->field, 0, opt->str);
779 }
780
781 static bool select_process_key(struct nc_widget *w, FORM *form, int key)
782 {
783         struct nc_widget_select *select = to_select(w);
784         struct select_option *new_opt, *old_opt;
785         int i, new_idx;
786         FIELD *field;
787
788         if (!key_is_select(key))
789                 return false;
790
791         field = current_field(form);
792         new_opt = NULL;
793
794         for (i = 0; i < select->n_options; i++) {
795                 if (select->options[i].field == field) {
796                         new_opt = &select->options[i];
797                         new_idx = i;
798                         break;
799                 }
800         }
801
802         if (!new_opt)
803                 return true;
804
805         if (new_idx == select->selected_option)
806                 return true;
807
808         old_opt = &select->options[select->selected_option];
809
810         select_option_change(old_opt, false);
811         select_option_change(new_opt, true);
812
813         select->selected_option = new_idx;
814
815         if (select->on_change)
816                 select->on_change(select->on_change_arg, new_opt->val);
817
818         return true;
819 }
820
821 static void select_set_visible(struct nc_widget *widget, bool visible)
822 {
823         struct nc_widget_select *select = to_select(widget);
824         int i;
825
826         for (i = 0; i < select->n_options; i++)
827                 field_set_visible(select->options[i].field, visible);
828 }
829
830 static void select_move(struct nc_widget *widget, int y, int x)
831 {
832         struct nc_widget_select *select = to_select(widget);
833         int i, cur = 0;
834
835         for (i = 0; i < select->n_options; i++) {
836                 field_move(select->options[i].field, y + cur, x);
837                 cur += select->options[i].lines;
838         }
839 }
840
841 static void select_field_focus(struct nc_widget *widget, FIELD *field)
842 {
843         struct nc_widget_select *select = to_select(widget);
844         int i, cur = 0;
845
846         for (i = 0; i < select->n_options; i++) {
847                 if (field != select->options[i].field) {
848                         cur += select->options[i].lines;
849                         continue;
850                 }
851                 widget->focus_y = cur;
852                 return;
853         }
854 }
855
856 static int select_destructor(void *ptr)
857 {
858         struct nc_widget_select *select = ptr;
859         int i;
860
861         for (i = 0; i < select->n_options; i++)
862                 free_field(select->options[i].field);
863
864         return 0;
865 }
866
867 struct nc_widget_select *widget_new_select(struct nc_widgetset *set,
868                 int y, int x, int len)
869 {
870         struct nc_widget_select *select;
871
872         select = talloc_zero(set, struct nc_widget_select);
873         select->widget.width = len;
874         select->widget.height = 0;
875         select->widget.x = x;
876         select->widget.y = y;
877         select->widget.process_key = select_process_key;
878         select->widget.set_visible = select_set_visible;
879         select->widget.move = select_move;
880         select->widget.field_focus = select_field_focus;
881         select->widget.focussed_attr = A_REVERSE;
882         select->widget.unfocussed_attr = A_NORMAL;
883         select->top = y;
884         select->left = x;
885         select->size = len;
886         select->set = set;
887
888         talloc_set_destructor(select, select_destructor);
889
890         return select;
891 }
892
893 static int widget_select_fold_cb(void *arg, const char *buf, int len)
894 {
895         struct nc_widget_select *select = arg;
896         char *line, *newstr, *padbuf = NULL;
897         int i, pad;
898
899         if (!len)
900                 return 0;
901
902         line = talloc_strndup(select->options, buf, len);
903
904         i = select->n_options - 1;
905         pad = max(0, select->widget.width - strncols(line));
906
907         if (pad) {
908                 padbuf = talloc_array(select->options, char, pad + 1);
909                 memset(padbuf, ' ', pad);
910                 padbuf[pad] = '\0';
911         }
912
913         if (select->options[i].str)
914                 newstr = talloc_asprintf_append(select->options[i].str,
915                                                          "%s%s", line,
916                                                          pad ? padbuf : "");
917         else
918                 newstr = talloc_asprintf(select->options, "%s%s", line,
919                                                          pad ? padbuf : "");
920
921         select->options[i].str = newstr;
922         select->options[i].lines++;
923
924         talloc_free(padbuf);
925         talloc_free(line);
926         return 0;
927 }
928
929 void widget_select_add_option(struct nc_widget_select *select, int value,
930                 const char *text, bool selected)
931 {
932         const char *str;
933         char *full_text;
934         FIELD *f;
935         int i;
936
937         /* if we never see an option with selected set, we want the first
938          * one to be selected */
939         if (select->n_options == 0)
940                 selected = true;
941         else if (selected)
942                 select_option_change(&select->options[select->selected_option],
943                                         false);
944
945         if (selected) {
946                 select->selected_option = select->n_options;
947                 str = select_selected_str;
948         } else
949                 str = select_unselected_str;
950
951         i = select->n_options++;
952         select->options = talloc_realloc(select, select->options,
953                                 struct select_option, i + 2);
954         select->options[i].val = value;
955         select->options[i].lines = 0;
956         select->options[i].str = NULL;
957
958         full_text = talloc_asprintf(select->options, "%s %s", str, text);
959         fold_text(full_text, select->widget.width,
960                   widget_select_fold_cb, select);
961
962         select->options[i].field = f = new_field(select->options[i].lines,
963                                         select->size,
964                                         select->top + select->widget.height,
965                                         select->left, 0, 0);
966
967         select->widget.height += select->options[i].lines;
968
969         field_opts_off(f, O_EDIT);
970         set_field_userptr(f, &select->widget);
971         set_field_buffer(f, 0, select->options[i].str);
972
973         widgetset_add_field(select->set, f);
974         talloc_free(full_text);
975 }
976
977 int widget_select_get_value(struct nc_widget_select *select)
978 {
979         if (!select->n_options)
980                 return -1;
981         return select->options[select->selected_option].val;
982 }
983
984 int widget_select_height(struct nc_widget_select *select)
985 {
986         return select->widget.height;
987 }
988
989 void widget_select_on_change(struct nc_widget_select *select,
990                 void (*on_change)(void *, int), void *arg)
991 {
992         select->on_change = on_change;
993         select->on_change_arg = arg;
994 }
995
996 void widget_select_drop_options(struct nc_widget_select *select)
997 {
998         struct nc_widgetset *set = select->set;
999         int i;
1000
1001         for (i = 0; i < select->n_options; i++) {
1002                 FIELD *field = select->options[i].field;
1003                 widgetset_remove_field(set, field);
1004                 if (field == set->cur_field)
1005                         set->cur_field = NULL;
1006                 free_field(select->options[i].field);
1007         }
1008
1009         talloc_free(select->options);
1010         select->options = NULL;
1011         select->n_options = 0;
1012         select->widget.height = 0;
1013         select->widget.focus_y = 0;
1014
1015 }
1016
1017 static bool button_process_key(struct nc_widget *widget,
1018                 FORM *form __attribute__((unused)), int key)
1019 {
1020         struct nc_widget_button *button = to_button(widget);
1021
1022         if (!button->click)
1023                 return false;
1024
1025         if (!key_is_select(key))
1026                 return false;
1027
1028         button->click(button->arg);
1029         return true;
1030 }
1031
1032 static int button_destructor(void *ptr)
1033 {
1034         struct nc_widget_button *button = ptr;
1035         free_field(button->widget.field);
1036         return 0;
1037 }
1038
1039 struct nc_widget_button *widget_new_button(struct nc_widgetset *set,
1040                 int y, int x, int size, const char *str,
1041                 void (*click)(void *), void *arg)
1042 {
1043         struct nc_widget_button *button;
1044         int idx, len, pad1, pad2, bufsz;
1045         char *text;
1046         FIELD *f;
1047
1048         int field_size = size + 2;
1049
1050         button = talloc_zero(set, struct nc_widget_button);
1051         button->widget.height = 1;
1052         button->widget.width = field_size;
1053         button->widget.x = x;
1054         button->widget.y = y;
1055         button->widget.field = f = new_field(1, field_size, y, x, 0, 0);
1056         button->widget.process_key = button_process_key;
1057         button->widget.focussed_attr = A_REVERSE;
1058         button->widget.unfocussed_attr = A_NORMAL;
1059         button->click = click;
1060         button->arg = arg;
1061
1062         field_opts_off(f, O_EDIT);
1063         set_field_userptr(f, &button->widget);
1064
1065         /* Center str in the field. This depends on the number of columns used
1066          * by the string, not the number of chars in str */
1067         len = strncols(str);
1068         if (len <= size) {
1069                 idx = (field_size - len) / 2;
1070         } else {
1071                 idx = 1;
1072                 pb_log("Warning: '%s' %d columns wide "
1073                        "but button is %d columns wide\n",
1074                        str, len, size);
1075         }
1076
1077         pad1 = max(idx - 1, 0);
1078         pad2 = max(size - len - pad1, 0);
1079         bufsz = 1 + pad1 + strlen(str) + pad2 + 2;
1080
1081         text = talloc_array(button, char, bufsz);
1082         memset(text, ' ', bufsz);
1083         memcpy(text + idx, str, strlen(str));
1084         text[0] = '[';
1085         text[bufsz - 2] = ']';
1086         text[bufsz - 1] = '\0';
1087
1088         set_field_buffer(f, 0, text);
1089
1090         widgetset_add_field(set, button->widget.field);
1091         talloc_set_destructor(button, button_destructor);
1092
1093         return button;
1094 }
1095
1096 void widget_focus_change(struct nc_widget *widget, FIELD *field,
1097                 bool focussed)
1098 {
1099         int attr = focussed ? widget->focussed_attr : widget->unfocussed_attr;
1100         set_field_back(field, attr);
1101 }
1102
1103 bool widgetset_process_key(struct nc_widgetset *set, int key)
1104 {
1105         struct nc_widget *widget;
1106         FIELD *field, *tmp;
1107         int req = 0;
1108         bool tab;
1109
1110         field = current_field(set->form);
1111         assert(field);
1112
1113         tab = false;
1114
1115         /* handle field change events */
1116         switch (key) {
1117         case KEY_BTAB:
1118                 tab = true;
1119                 /* fall through */
1120         case KEY_UP:
1121         case KEY_LEFT:
1122                 req = REQ_SPREV_FIELD;
1123                 break;
1124         case '\t':
1125                 tab = true;
1126                 /* fall through */
1127         case KEY_DOWN:
1128         case KEY_RIGHT:
1129                 req = REQ_SNEXT_FIELD;
1130                 break;
1131         case KEY_PPAGE:
1132                 req = REQ_SFIRST_FIELD;
1133                 break;
1134         case KEY_NPAGE:
1135                 req = REQ_SLAST_FIELD;
1136                 break;
1137         }
1138
1139         widget = field_userptr(field);
1140         if (req) {
1141                 widget_focus_change(widget, field, false);
1142                 form_driver(set->form, req);
1143
1144                 /* if we're doing a tabbed-field-change, skip until we
1145                  * see the next widget */
1146                 tmp = field;
1147                 field = current_field(set->form);
1148
1149                 for (; tab && tmp != field && field_userptr(field) == widget;) {
1150                         form_driver(set->form, req);
1151                         field = current_field(set->form);
1152                 }
1153
1154                 form_driver(set->form, REQ_END_FIELD);
1155                 widget = field_userptr(field);
1156                 widget_focus_change(widget, field, true);
1157                 if (widget->field_focus)
1158                         widget->field_focus(widget, field);
1159                 if (set->widget_focus)
1160                         set->widget_focus(widget, set->widget_focus_arg);
1161                 return true;
1162         }
1163
1164         if (!widget->process_key)
1165                 return false;
1166
1167         return widget->process_key(widget, set->form, key);
1168 }
1169
1170 static int widgetset_destructor(void *ptr)
1171 {
1172         struct nc_widgetset *set = ptr;
1173         free_form(set->form);
1174         if (set->ipv4_multi_type)
1175                 free_fieldtype(set->ipv4_multi_type);
1176         return 0;
1177 }
1178
1179 struct nc_widgetset *widgetset_create(void *ctx, WINDOW *main, WINDOW *sub)
1180 {
1181         struct nc_widgetset *set;
1182
1183         set = talloc_zero(ctx, struct nc_widgetset);
1184         set->n_alloc_fields = 8;
1185         set->mainwin = main;
1186         set->subwin = sub;
1187         set->fields = talloc_array(set, FIELD *, set->n_alloc_fields);
1188         talloc_set_destructor(set, widgetset_destructor);
1189
1190         return set;
1191 }
1192
1193 void widgetset_set_windows(struct nc_widgetset *set,
1194                 WINDOW *main, WINDOW *sub)
1195 {
1196         set->mainwin = main;
1197         set->subwin = sub;
1198 }
1199
1200 void widgetset_set_widget_focus(struct nc_widgetset *set,
1201                 widget_focus_cb cb, void *arg)
1202 {
1203         set->widget_focus = cb;
1204         set->widget_focus_arg = arg;
1205 }
1206
1207 void widgetset_post(struct nc_widgetset *set)
1208 {
1209         struct nc_widget *widget;
1210         FIELD *field;
1211
1212         set->form = new_form(set->fields);
1213         set_form_win(set->form, set->mainwin);
1214         set_form_sub(set->form, set->subwin);
1215         post_form(set->form);
1216         form_driver(set->form, REQ_END_FIELD);
1217
1218         if (set->cur_field)
1219                 set_current_field(set->form, set->cur_field);
1220
1221         field = current_field(set->form);
1222         widget = field_userptr(field);
1223         widget_focus_change(widget, field, true);
1224         if (set->widget_focus)
1225                 set->widget_focus(widget, set->widget_focus_arg);
1226 }
1227
1228 void widgetset_unpost(struct nc_widgetset *set)
1229 {
1230         set->cur_field = current_field(set->form);
1231         unpost_form(set->form);
1232         free_form(set->form);
1233         set->form = NULL;
1234 }
1235
1236 static void widgetset_add_field(struct nc_widgetset *set, FIELD *field)
1237 {
1238         if (set->n_fields == set->n_alloc_fields - 1) {
1239                 set->n_alloc_fields *= 2;
1240                 set->fields = talloc_realloc(set, set->fields,
1241                                 FIELD *, set->n_alloc_fields);
1242         }
1243
1244         set->n_fields++;
1245         set->fields[set->n_fields - 1] = field;
1246         set->fields[set->n_fields] = NULL;
1247 }
1248
1249 static void widgetset_remove_field(struct nc_widgetset *set, FIELD *field)
1250 {
1251         int i;
1252
1253         for (i = 0; i < set->n_fields; i++) {
1254                 if (set->fields[i] == field)
1255                         break;
1256         }
1257
1258         if (i == set->n_fields)
1259                 return;
1260
1261         memmove(&set->fields[i], &set->fields[i+i],
1262                         (set->n_fields - i) * sizeof(set->fields[i]));
1263         set->n_fields--;
1264 }
1265
1266 #define DECLARE_BASEFN(type) \
1267         struct nc_widget *widget_ ## type ## _base              \
1268                 (struct nc_widget_ ## type *w)                  \
1269         { return &w->widget; }
1270
1271 DECLARE_BASEFN(textbox);
1272 DECLARE_BASEFN(checkbox);
1273 DECLARE_BASEFN(subset);
1274 DECLARE_BASEFN(select);
1275 DECLARE_BASEFN(label);
1276 DECLARE_BASEFN(button);
1277
1278 void widget_set_visible(struct nc_widget *widget, bool visible)
1279 {
1280         if (widget->set_visible)
1281                 widget->set_visible(widget, visible);
1282         else
1283                 field_set_visible(widget->field, visible);
1284 }
1285
1286 void widget_move(struct nc_widget *widget, int y, int x)
1287 {
1288         if (widget->move)
1289                 widget->move(widget, y, x);
1290         else
1291                 field_move(widget->field, y, x);
1292
1293         widget->x = x;
1294         widget->y = y;
1295
1296         if (x + widget->width > COLS)
1297                 pb_debug("%s: Widget at %d,%d runs over pad! (%d)", __func__,
1298                        y, x, x + widget->width);
1299 }
1300
1301 int widget_height(struct nc_widget *widget)
1302 {
1303         return widget->height;
1304 }
1305
1306 int widget_width(struct nc_widget *widget)
1307 {
1308         return widget->width;
1309 }
1310
1311 int widget_x(struct nc_widget *widget)
1312 {
1313         return widget->x;
1314 }
1315
1316 int widget_y(struct nc_widget *widget)
1317 {
1318         return widget->y;
1319 }
1320
1321 int widget_focus_y(struct nc_widget *widget)
1322 {
1323         return widget->focus_y;
1324 }
1325