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