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