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