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