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