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