installer: Remove some unused variables.
[dragonfly.git] / usr.sbin / installer / dfuife_curses / curses_xlat.c
1 /*
2  * Copyright (c)2004 Cat's Eye Technologies.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  *   Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  *
11  *   Redistributions in binary form must reproduce the above copyright
12  *   notice, this list of conditions and the following disclaimer in
13  *   the documentation and/or other materials provided with the
14  *   distribution.
15  *
16  *   Neither the name of Cat's Eye Technologies nor the names of its
17  *   contributors may be used to endorse or promote products derived
18  *   from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33
34 /*
35  * curses_xlat.c
36  * Translate DFUI forms to curses forms.
37  * $Id: curses_xlat.c,v 1.20 2005/02/08 21:39:42 cpressey Exp $
38  */
39
40 #include <sys/time.h>
41
42 #include <ctype.h>
43 #include <stdlib.h>
44 #include <string.h>
45
46 #include "libaura/mem.h"
47
48 #include "libdfui/dfui.h"
49 #include "libdfui/dump.h"
50
51 #include "curses_form.h"
52 #include "curses_widget.h"
53 #include "curses_util.h"
54 #include "curses_xlat.h"
55
56 #define MAX(a, b) (a > b ? a : b)
57 #define MIN(a, b) (a < b ? a : b)
58
59 /*** CALLBACKS ***/
60
61 static struct timeval last_update;
62 static unsigned int last_y;
63
64 /*
65  * Callback to give to curses_widget_set_click_cb, for buttons
66  * that remove the same row of widgets that they are on.
67  */
68 static int
69 cb_click_remove_row(struct curses_widget *w)
70 {
71         struct curses_form *cf = w->form;
72         struct curses_widget *few;
73         int id = w->user_id;
74
75         /*
76          * Since we're going to be deleting the widget with
77          * the focus, first move the focus onto a widget
78          * that we won't be deleting.
79          */
80         do {
81                 if (cf->widget_focus == NULL)
82                         cf->widget_focus = cf->widget_head;
83                 while (cf->widget_focus->user_id == id)
84                         cf->widget_focus = cf->widget_focus->prev;
85         } while (cf->widget_focus == NULL);
86
87         /*
88          * Delete all widgets with the same id as the focused one.
89          */
90         for (few = cf->widget_head; few != NULL; few = few->next) {
91                 if (few->user_id == id) {
92                         curses_form_widget_remove(few);
93                         /*
94                          * Reset the iterator, as the previous command
95                          * may have obliterated the current widget.
96                          */
97                         few = cf->widget_head;
98                 }
99         }
100
101         /*
102          * Slide the remaining widgets up a row.
103          */
104         for (few = cf->widget_head; few != NULL; few = few->next) {
105                 if (few->user_id > id) {
106                         /*
107                          * Slide the rows below the deleted row up one row.
108                          */
109                         few->user_id--;
110                         few->y--;
111                 } else if (few->user_id == -1) {
112                         /*
113                          * Slide the buttons, too.
114                          */
115                         few->y--;
116                 }
117         }
118
119         cf->int_height--;
120
121         /*
122          * Now that the widgets are deleted, make sure the focus is
123          * on a usable widget (not a label.)
124          */
125         curses_form_focus_skip_forward(cf);
126         cf->want_y = cf->widget_focus->y;
127
128         /*
129          * Repaint the form.  XXX Might not be necessary anymore?
130          */
131         curses_form_draw(cf);
132         curses_form_refresh(cf);
133         return(0);
134 }
135
136 /*
137  * Callback to give to curses_widget_set_click_cb, for textboxes
138  * that pop up a list of options from which the user can select.
139  */
140 static int
141 cb_click_select_option(struct curses_widget *w)
142 {
143         struct dfui_field *fi = w->userdata;
144         struct dfui_option *o;
145         struct curses_form *cf;
146         struct curses_widget *button, *cw;
147
148         cf = curses_form_new("* select *");
149
150         for (o = dfui_field_option_get_first(fi); o != NULL;
151              o = dfui_option_get_next(o)) {
152                 button = curses_form_widget_add(cf, 1,
153                     cf->height++, 0, CURSES_BUTTON,
154                     dfui_option_get_value(o), 0, CURSES_WIDGET_WIDEN);
155                 curses_widget_set_click_cb(button, cb_click_close_form);
156         }
157
158         curses_form_finalize(cf);
159
160         curses_form_draw(cf);
161         curses_form_refresh(cf);
162         cw = curses_form_frob(cf);
163
164         curses_textbox_set_text(w, cw->text);
165
166         curses_form_free(cf);
167         curses_form_refresh(NULL);
168
169         return(0);
170 }
171
172 /*
173  * XXX this should maybe be in libdfui.
174  */
175 static struct dfui_dataset *
176 create_default_dataset(const struct dfui_form *f)
177 {
178         struct dfui_dataset *ds;
179         struct dfui_field *fi;
180
181         ds = dfui_dataset_new();
182         for (fi = dfui_form_field_get_first(f); fi != NULL;
183              fi = dfui_field_get_next(fi)) {
184                 dfui_dataset_celldata_add(ds,
185                      dfui_field_get_id(fi), "");
186         }
187
188         return(ds);
189 }
190
191 /*
192  * Callback to give to curses_widget_set_click_cb, for buttons
193  * that insert a row of widgets before the row that they are on.
194  */
195 static int
196 cb_click_insert_row(struct curses_widget *w)
197 {
198         struct curses_form *cf = w->form;
199         struct curses_widget *few, *lw;
200         int id = w->user_id;
201         int top = w->y;
202         struct dfui_dataset *ds;
203         struct curses_form_userdata *cfu = cf->userdata;
204
205         /*
206          * Find the last widget in the tab order that is of the prev row.
207          */
208
209         for (lw = w; lw != NULL; lw = lw->prev) {
210                 if (lw->user_id == id - 1)
211                         break;
212         }
213
214         /*
215          * Slide widgets below the row we're going to insert, down.
216          */
217         for (few = cf->widget_head; few != NULL; few = few->next) {
218                 if (few->user_id >= id) {
219                         /*
220                          * Slide the rows below the deleted row up one row.
221                          */
222                         few->user_id++;
223                         few->y++;
224                 } else if (few->user_id == -1) {
225                         /*
226                          * Slide the buttons, too.
227                          */
228                         few->y++;
229                 }
230         }
231         cf->int_height++;
232
233         /*
234          * Insert a new row of widgets.
235          */
236         ds = create_default_dataset(cfu->f);
237         curses_form_create_widget_row(cf, lw, ds, 1, top, id);
238         dfui_dataset_free(ds);
239
240         /*
241          * Repaint the form.
242          */
243         curses_form_widget_ensure_visible(cf->widget_focus);
244         cf->want_y = cf->widget_focus->y;
245         curses_form_draw(cf);
246         curses_form_refresh(cf);
247         return(0);
248 }
249
250 /*
251  * Create a row of widgets in a multiple=true form.
252  * Returns the x position of the "Ins" button, if any.
253  */
254 int
255 curses_form_create_widget_row(struct curses_form *cf, struct curses_widget *cw,
256         const struct dfui_dataset *ds, int left, int top, int row)
257 {
258         struct curses_widget *xbox, *button;
259         struct dfui_field *fi;
260         struct dfui_celldata *cd;
261         const char *value;
262         int col = 0, ins_x = left;
263         struct curses_form_userdata *cfu = cf->userdata;
264         const struct dfui_form *f = cfu->f;
265
266         /*
267          * Create one input underneath each field heading.
268          */
269         for (fi = dfui_form_field_get_first(f); fi != NULL;
270              fi = dfui_field_get_next(fi)) {
271                 cd = dfui_dataset_celldata_find(ds, dfui_field_get_id(fi));
272                 value = dfui_celldata_get_value(cd);
273                 if (cw == NULL) {
274                         if (dfui_field_property_is(fi, "control", "checkbox")) {
275                                 xbox = curses_form_widget_add(cf,
276                                     left, top, 4, CURSES_CHECKBOX, "", 0, 0);
277                                 xbox->amount = (value[0] == 'Y' ? 1 : 0);
278                         } else {
279                                 xbox = curses_form_widget_add(cf, left, top,
280                                     cfu->widths[col] - 1, CURSES_TEXTBOX,
281                                     value, 256, 0);
282                         }
283                 } else {
284                         if (dfui_field_property_is(fi, "control", "checkbox")) {
285                                 xbox = curses_form_widget_insert_after(cw,
286                                     left, top, 4, CURSES_CHECKBOX, "", 0, 0);
287                                 xbox->amount = (value[0] == 'Y' ? 1 : 0);
288                         } else {
289                                 xbox = curses_form_widget_insert_after(cw,
290                                     left, top, cfu->widths[col] - 1,
291                                     CURSES_TEXTBOX, value, 256, 0);
292                         }
293                         cw = xbox;
294                 }
295                 curses_widget_tooltip_set(xbox,
296                     dfui_info_get_short_desc(dfui_field_get_info(fi)));
297                 xbox->user_id = row;
298                 xbox->userdata = fi;
299
300                 if (dfui_field_property_is(fi, "editable", "false"))
301                         xbox->editable = 0;
302                 if (dfui_field_property_is(fi, "obscured", "true"))
303                         xbox->obscured = 1;
304
305                 if (dfui_field_option_get_first(fi) != NULL) {
306                         curses_widget_set_click_cb(xbox, cb_click_select_option);
307                 }
308
309                 left += cfu->widths[col++];
310         }
311
312         /*
313          * If this is an extensible form,
314          * create buttons for each dataset.
315          */
316         if (dfui_form_is_extensible(f)) {
317                 if (cw == NULL) {
318                         button = curses_form_widget_add(cf, left,
319                             top, 0, CURSES_BUTTON, "Ins", 0,
320                             CURSES_WIDGET_WIDEN);
321                 } else {
322                         button = curses_form_widget_insert_after(cw, left,
323                             top, 0, CURSES_BUTTON, "Ins", 0,
324                             CURSES_WIDGET_WIDEN);
325                         cw = button;
326                 }
327                 ins_x = left;
328                 button->user_id = row;
329                 curses_widget_set_click_cb(button, cb_click_insert_row);
330
331                 left += button->width + 1;
332
333                 if (cw == NULL) {
334                         button = curses_form_widget_add(cf, left,
335                             top, 0, CURSES_BUTTON, "Del", 0,
336                             CURSES_WIDGET_WIDEN);
337                 } else {
338                         button = curses_form_widget_insert_after(cw, left,
339                             top, 0, CURSES_BUTTON, "Del", 0,
340                             CURSES_WIDGET_WIDEN);
341                         cw = button;
342                 }
343                 button->user_id = row;
344                 curses_widget_set_click_cb(button, cb_click_remove_row);
345         }
346
347         return(ins_x);
348 }
349
350 static struct curses_widget *
351 center_buttons(struct curses_form *cf, struct curses_widget *row_start, int is_menu)
352 {
353         struct curses_widget *w;
354         int row_width, row_offset;
355
356         /*
357          * Center the previous row of buttons on the form
358          * if this is not a menu.
359          */
360         if (!is_menu) {
361                 /* Find the width of all buttons on the previous row. */
362                 row_width = 0;
363                 for (w = row_start; w != NULL; w = w->next) {
364                         row_width += w->width + 2;
365                 }
366
367                 /*
368                  * Adjust the x position of each of button by
369                  * a calculated offset.
370                  */
371                 row_offset = (cf->width - row_width) / 2;
372                 for (w = row_start; w != NULL; w = w->next) {
373                         w->x += row_offset;
374                 }
375
376                 /*
377                  * Mark the next button we will create
378                  * as the first button of a row.
379                  */
380                 row_start = NULL;
381         }
382
383         return(row_start);
384 }
385
386 /*
387  * Create a row of buttons, one for each action, at
388  * the bottom of a curses_form.
389  */
390 static void
391 create_buttons(const struct dfui_form *f, struct curses_form *cf, int is_menu)
392 {
393         struct curses_widget *w;
394         char name[80];
395         struct dfui_action *a;
396         struct curses_widget *row_start = NULL;
397         int left_acc = 1;
398         const char *accel;
399
400         for (a = dfui_form_action_get_first(f); a != NULL;
401              a = dfui_action_get_next(a)) {
402                 strlcpy(name, dfui_info_get_name(dfui_action_get_info(a)), 70);
403
404                 dfui_debug("creating button `%s' (%d) @ %d / %d\n",
405                         name, strlen(name), left_acc, cf->width);
406
407                 /*
408                  * Check for overflow.  If the next button would appear
409                  * off the right side of the form, start putting buttons
410                  * on the next row.  Or, if this is a menu, always put the
411                  * next button on the next line.
412                  */
413                 if (is_menu ||
414                     ((left_acc + strlen(name) + 6) > cf->width &&
415                     left_acc > 1)) {
416                         row_start = center_buttons(cf, row_start, is_menu);
417                         cf->height++;
418                         left_acc = 1;
419                 }
420
421                 w = curses_form_widget_add(cf, left_acc,
422                     cf->height, 0, CURSES_BUTTON, name, 0, CURSES_WIDGET_WIDEN);
423                 curses_widget_tooltip_set(w,
424                     dfui_info_get_short_desc(dfui_action_get_info(a)));
425
426                 accel = dfui_action_property_get(a, "accelerator");
427                 if (strlen(accel) > 0) {
428                         if (strcmp(accel, "ESC") == 0) {
429                                 w->accel = '\e';
430                         } else {
431                                 w->accel = toupper(accel[0]);
432                         }
433                 }
434
435                 left_acc += (w->width + 2);
436                 w->user_id = -1;
437                 w->userdata = a;
438                 curses_widget_set_click_cb(w, cb_click_close_form);
439                 if (row_start == NULL)
440                         row_start = w;
441         }
442
443         center_buttons(cf, row_start, is_menu);
444 }
445
446 static void
447 set_help(const struct dfui_form *f, struct curses_form *cf)
448 {
449         const char *help_text;
450
451         help_text = dfui_info_get_long_desc(dfui_form_get_info(f));
452         if (cf->help_text != NULL) {
453                 free(cf->help_text);
454         }
455         if (strlen(help_text) > 0) {
456                 cf->help_text = aura_strdup(help_text);
457         } else {
458                 cf->help_text = NULL;
459         }
460 }
461
462 /*** FORM TRANSLATORS ***/
463
464 static struct curses_form *
465 curses_form_construct_from_dfui_form_single(const struct dfui_form *f)
466 {
467         struct curses_form *cf;
468         struct curses_form_userdata *cfu;
469         const char *min_width_str;
470         unsigned int desc_width, min_width = 0;
471         unsigned int len, max_label_width, total_label_width;
472         unsigned int max_button_width, total_button_width;
473         struct dfui_field *fi;
474         struct dfui_action *a;
475         struct curses_widget *xbox;
476         struct dfui_celldata *cd;
477         const char *value;
478         int is_menu;
479
480         dfui_debug("-----\nconstructing single form: %s\n",
481             dfui_info_get_name(dfui_form_get_info(f)));
482
483         is_menu = dfui_form_property_is(f, "role", "menu");
484         cf = curses_form_new(dfui_info_get_name(dfui_form_get_info(f)));
485         AURA_MALLOC(cfu, curses_form_userdata);
486         cfu->f = f;
487         cf->userdata = cfu;
488         cf->cleanup = 1;
489
490         set_help(f, cf);
491
492         /* Calculate offsets for nice positioning of labels and buttons. */
493
494         /*
495          * Determine the widths of the widest field and the widest
496          * button, and the total widths of all fields and all buttons.
497          */
498
499         max_label_width = 0;
500         total_label_width = 0;
501         max_button_width = 0;
502         total_button_width = 0;
503
504         for (fi = dfui_form_field_get_first(f); fi != NULL;
505              fi = dfui_field_get_next(fi)) {
506                 len = MIN(60, strlen(dfui_info_get_name(dfui_field_get_info(fi))));
507                 if (len > max_label_width)
508                         max_label_width = len;
509                 total_label_width += (len + 2);
510         }
511         for (a = dfui_form_action_get_first(f); a != NULL;
512              a = dfui_action_get_next(a)) {
513                 len = strlen(dfui_info_get_name(dfui_action_get_info(a)));
514                 if (len > max_button_width)
515                         max_button_width = len;
516                 total_button_width += (len + 6);
517         }
518
519         if (total_label_width > (xmax - 2))
520                 total_label_width = (xmax - 2);         /* XXX scroll/wrap? */
521
522         /* Take the short description and turn it into a set of labels. */
523
524         if ((min_width_str = dfui_form_property_get(f, "minimum_width")) != NULL)
525                 min_width = atoi(min_width_str);
526
527         desc_width = 40;
528         desc_width = MAX(desc_width, min_width);
529         if (is_menu) {
530                 desc_width = MAX(desc_width, max_button_width);
531         } else {
532                 desc_width = MAX(desc_width, total_button_width);
533         }
534         desc_width = MAX(desc_width, max_label_width);  /* XXX + max_field_width */
535         desc_width = MIN(desc_width, xmax - 4); /* -2 for borders, -2 for spaces */
536
537         dfui_debug("min width: %d\n", min_width);
538         dfui_debug("button width: %d\n", total_button_width);
539         dfui_debug("label width: %d\n", total_label_width);
540         dfui_debug("resulting width: %d\n", desc_width);
541         dfui_debug("form width: %d\n", cf->width);
542
543         cf->height = curses_form_descriptive_labels_add(cf,
544             dfui_info_get_short_desc(dfui_form_get_info(f)),
545             1, cf->height + 1, desc_width);
546
547         dfui_debug("form width now: %d\n", cf->width);
548
549         if (!is_menu)
550                 cf->height++;
551
552         /*
553          * Add one label and one textbox (or other control) to a
554          * curses_form for each field in the dfui_form.  Each set of
555          * labels and controls is added one row below the previous set.
556          */
557         for (fi = dfui_form_field_get_first(f); fi != NULL;
558              fi = dfui_field_get_next(fi)) {
559                 curses_form_widget_add(cf, 1,
560                     cf->height, max_label_width, CURSES_LABEL,
561                     dfui_info_get_name(dfui_field_get_info(fi)), 0, 0);
562
563                 cd = dfui_dataset_celldata_find(dfui_form_dataset_get_first(f),
564                     dfui_field_get_id(fi));
565
566                 value = dfui_celldata_get_value(cd);
567
568                 if (dfui_field_property_is(fi, "control", "checkbox")) {
569                         xbox = curses_form_widget_add(cf,
570                             max_label_width + 3,
571                             cf->height, 4, CURSES_CHECKBOX, "", 0, 0);
572                         xbox->amount = (value[0] == 'Y' ? 1 : 0);
573                 } else {
574                         xbox = curses_form_widget_add(cf,
575                             max_label_width + 3,
576                             cf->height, 20, CURSES_TEXTBOX, value, 256, 0);
577                 }
578                 curses_widget_tooltip_set(xbox,
579                     dfui_info_get_short_desc(dfui_field_get_info(fi)));
580                 xbox->user_id = 1;
581                 xbox->userdata = fi;
582
583                 if (dfui_field_property_is(fi, "editable", "false"))
584                         xbox->editable = 0;
585                 if (dfui_field_property_is(fi, "obscured", "true"))
586                         xbox->obscured = 1;
587
588                 if (dfui_field_option_get_first(fi) != NULL) {
589                         curses_widget_set_click_cb(xbox, cb_click_select_option);
590                 }
591
592                 cf->height++;
593         }
594
595         if (dfui_form_field_get_first(f) != NULL)
596                 cf->height++;
597
598         create_buttons(f, cf, is_menu);
599
600         cf->height++;
601
602         curses_form_finalize(cf);
603
604         return(cf);
605 }
606
607 static struct curses_form *
608 curses_form_construct_from_dfui_form_multiple(const struct dfui_form *f)
609 {
610         struct curses_form *cf;
611         struct curses_form_userdata *cfu;
612         const char *min_width_str;
613         unsigned int desc_width, min_width = 0;
614         unsigned int len, max_label_width, total_label_width;
615         unsigned int max_button_width, total_button_width;
616         struct dfui_field *fi;
617         struct dfui_action *a;
618         struct curses_widget *label, *button;
619         struct dfui_dataset *ds;
620         const char *name;
621         int left_acc, top_acc;
622         int row = 1, col = 0, ins_x = 1, is_menu = 0;
623
624         dfui_debug("-----\nconstructing multiple form: %s\n",
625             dfui_info_get_name(dfui_form_get_info(f)));
626
627         cf = curses_form_new(dfui_info_get_name(dfui_form_get_info(f)));
628         AURA_MALLOC(cfu, curses_form_userdata);
629         cfu->f = f;
630         cf->userdata = cfu;
631         cf->cleanup = 1;
632
633         set_help(f, cf);
634
635         /* Calculate offsets for nice positioning of labels and buttons. */
636
637         /*
638          * Determine the widths of the widest field and the widest
639          * button, and the total widths of all fields and all buttons.
640          */
641
642         max_label_width = 0;
643         total_label_width = 0;
644         max_button_width = 0;
645         total_button_width = 0;
646
647         for (fi = dfui_form_field_get_first(f); fi != NULL;
648              fi = dfui_field_get_next(fi)) {
649                 len = MIN(60, strlen(dfui_info_get_name(dfui_field_get_info(fi))));
650                 if (len > max_label_width)
651                         max_label_width = len;
652                 total_label_width += (len + 2);
653         }
654         for (a = dfui_form_action_get_first(f); a != NULL;
655              a = dfui_action_get_next(a)) {
656                 len = strlen(dfui_info_get_name(dfui_action_get_info(a)));
657                 if (len > max_button_width)
658                         max_button_width = len;
659                 total_button_width += (len + 6);
660         }
661
662         /* Take the short description and turn it into a set of labels. */
663
664         if ((min_width_str = dfui_form_property_get(f, "minimum_width")) != NULL)
665                 min_width = atoi(min_width_str);
666
667         desc_width = 40;
668         desc_width = MAX(desc_width, min_width);
669         desc_width = MAX(desc_width, total_button_width);
670         desc_width = MAX(desc_width, total_label_width);
671         desc_width = MIN(desc_width, xmax - 3);
672
673         dfui_debug("min width: %d\n", min_width);
674         dfui_debug("button width: %d\n", total_button_width);
675         dfui_debug("label width: %d\n", total_label_width);
676         dfui_debug("resulting width: %d\n", desc_width);
677         dfui_debug("form width: %d\n", cf->width);
678
679         cf->height = curses_form_descriptive_labels_add(cf,
680             dfui_info_get_short_desc(dfui_form_get_info(f)),
681             1, cf->height + 1, desc_width);
682
683         dfui_debug("form width now: %d\n", cf->width);
684
685         /* Add the fields. */
686
687         top_acc = cf->height + 1;
688         cf->height += dfui_form_dataset_count(f) + 2;
689
690         /*
691          * Create the widgets for a multiple=true form.  For each field
692          * in the form, a label containing the field's name, which serves
693          * as a heading, is created.  Underneath these labels, for each
694          * dataset in the form, a row of input widgets (typically textboxes)
695          * is added.  Non-action, manipulation buttons are also added to
696          * the right of each row.
697          */
698         left_acc = 1;
699         for (fi = dfui_form_field_get_first(f); fi != NULL;
700              fi = dfui_field_get_next(fi)) {
701                 /*
702                  * Create a label to serve as a heading for the column.
703                  */
704                 name = dfui_info_get_name(dfui_field_get_info(fi));
705                 label = curses_form_widget_add(cf, left_acc,
706                     top_acc, 0, CURSES_LABEL, name, 0,
707                     CURSES_WIDGET_WIDEN);
708                 cfu->widths[col++] = label->width + 2;
709                 left_acc += (label->width + 2);
710         }
711
712         /*
713          * Create a row of widgets for each dataset.
714          */
715         top_acc++;
716         for (ds = dfui_form_dataset_get_first(f); ds != NULL;
717              ds = dfui_dataset_get_next(ds)) {
718                 ins_x = curses_form_create_widget_row(cf, NULL, ds,
719                      1, top_acc++, row++);
720         }
721
722         /*
723          * Finally, create an 'Add' button to add a new row
724          * if this is an extensible form.
725          */
726         if (dfui_form_is_extensible(f)) {
727                 button = curses_form_widget_add(cf,
728                     ins_x, top_acc, 0,
729                     CURSES_BUTTON, "Add", 0, CURSES_WIDGET_WIDEN);
730                 button->user_id = row;
731                 curses_widget_set_click_cb(button, cb_click_insert_row);
732                 cf->height++;
733         }
734
735         cf->height++;
736
737         /* Add the buttons. */
738
739         create_buttons(f, cf, is_menu);
740
741         cf->height++;
742
743         curses_form_finalize(cf);
744
745         return(cf);
746 }
747
748 struct curses_form *
749 curses_form_construct_from_dfui_form(const struct dfui_form *f)
750 {
751         if (dfui_form_is_multiple(f))
752                 return(curses_form_construct_from_dfui_form_multiple(f));
753         else
754                 return(curses_form_construct_from_dfui_form_single(f));
755 }
756
757 #define FIFTY_EIGHT_SPACES "                                                          "
758
759 static void
760 strcpy_max(char *dest, const char *src, unsigned int max)
761 {
762         unsigned int i;
763
764         strncpy(dest, src, max);
765         if (strlen(src) > max) {
766                 strcpy(dest + (max - 3), "...");
767         } else {
768                 strncpy(dest + strlen(src),
769                     FIFTY_EIGHT_SPACES, max - strlen(src));
770         }
771         for (i = 0; i < strlen(dest); i++) {
772                 if (isspace(dest[i]))
773                         dest[i] = ' ';
774         }
775 }
776
777 struct curses_form *
778 curses_form_construct_from_dfui_progress(const struct dfui_progress *pr,
779                                          struct curses_widget **pbar,
780                                          struct curses_widget **plab,
781                                          struct curses_widget **pcan)
782 {
783         struct curses_form *cf;
784         const char *desc;
785
786         desc = dfui_info_get_short_desc(dfui_progress_get_info(pr));
787
788         cf = curses_form_new(dfui_info_get_name(dfui_progress_get_info(pr)));
789
790         cf->width = 60;
791         cf->height = 6;
792
793         if (dfui_progress_get_streaming(pr)) {
794                 cf->height = 20;
795         }
796
797         *plab = curses_form_widget_add(cf, 0, 1, 58,
798             CURSES_LABEL, FIFTY_EIGHT_SPACES, 0, CURSES_WIDGET_CENTER);
799         strcpy_max((*plab)->text, desc, 58);
800         *pbar = curses_form_widget_add(cf, 0, 3, 40,
801             CURSES_PROGRESS, "", 0, CURSES_WIDGET_CENTER);
802         *pcan = curses_form_widget_add(cf, 0, 5, 0,
803             CURSES_BUTTON, "Cancel", 0,
804             CURSES_WIDGET_CENTER | CURSES_WIDGET_WIDEN);
805         (*pbar)->amount = dfui_progress_get_amount(pr);
806
807         last_y = (*pbar)->y + 2;
808
809         curses_form_finalize(cf);
810
811         gettimeofday(&last_update, NULL);
812
813         return(cf);
814 }
815
816 void
817 curses_widgets_update_from_dfui_progress(const struct dfui_progress *pr,
818                                          struct curses_widget *pbar,
819                                          struct curses_widget *plab,
820                                          struct curses_widget *pcan)
821 {
822         const char *short_desc;
823         struct timeval now;
824         long msec_diff;
825         struct curses_widget *w;
826         int short_desc_changed;
827
828         gettimeofday(&now, NULL);
829         msec_diff = (now.tv_sec - last_update.tv_sec) * 1000 +
830                     (now.tv_usec - last_update.tv_usec) / 1000;
831
832         short_desc = dfui_info_get_short_desc(dfui_progress_get_info(pr));
833         short_desc_changed = (strncmp(plab->text, short_desc, MIN(55, strlen(short_desc))) != 0);
834
835         if (msec_diff < 100 && !dfui_progress_get_streaming(pr) && !short_desc_changed)
836                 return;
837
838         if (dfui_progress_get_amount(pr) != pbar->amount ||
839             short_desc_changed ||
840             dfui_progress_get_streaming(pr)) {
841                 strcpy_max(plab->text, short_desc, 58);
842                 curses_widget_draw(plab);
843                 pbar->amount = dfui_progress_get_amount(pr);
844                 curses_widget_draw(pbar);
845                 if (dfui_progress_get_streaming(pr)) {
846                         /* add a label with the text */
847                         w = curses_form_widget_add(pbar->form, 0, ++last_y, 58,
848                             CURSES_LABEL, FIFTY_EIGHT_SPACES, 0, CURSES_WIDGET_CENTER);
849                         strcpy_max(w->text, dfui_progress_get_msg_line(pr), 58);
850                         if (last_y >= pbar->form->int_height) {
851                                 pbar->form->int_height = last_y + 1;
852                         }
853                         curses_form_widget_ensure_visible(w);
854                         curses_widget_draw(w);
855                 }
856         } else {
857                 curses_progress_spin(pbar);
858         }
859         wmove(pcan->form->win, pcan->y + 1, pcan->x + pcan->width + 1);
860         curses_form_refresh(NULL);
861         last_update = now;
862 }
863
864 static const char *
865 curses_widget_xlat_value(const struct curses_widget *cw)
866 {
867         if (cw->type == CURSES_TEXTBOX)
868                 return(cw->text);
869         else if (cw->type == CURSES_CHECKBOX)
870                 return(cw->amount ? "Y" : "N");
871         else
872                 return("");
873 }
874
875 static struct dfui_response *
876 response_construct_from_curses_form_single(const struct dfui_form *f,
877                                            const struct curses_form *cf,
878                                            const struct curses_widget *cw)
879 {
880         struct dfui_response *r = NULL;
881         struct dfui_action *selected = NULL;
882         struct dfui_dataset *ds = NULL;
883         const char *id;
884         const char *value;
885
886         selected = cw->userdata;
887         r = dfui_response_new(dfui_form_get_id(f),
888                               dfui_action_get_id(selected));
889         ds = dfui_dataset_new();
890         for (cw = cf->widget_head; cw != NULL; cw = cw->next) {
891                 if (cw->user_id > 0) {
892                         id = dfui_field_get_id((struct dfui_field *)cw->userdata);
893                         value = curses_widget_xlat_value(cw);
894                         dfui_dataset_celldata_add(ds, id, value);
895                 }
896         }
897         dfui_response_dataset_add(r, ds);
898
899         return(r);
900 }
901
902 static struct dfui_response *
903 response_construct_from_curses_form_multiple(const struct dfui_form *f,
904                                              const struct curses_form *cf,
905                                              const struct curses_widget *cw)
906 {
907         struct dfui_response *r = NULL;
908         struct dfui_action *selected = NULL;
909         struct dfui_dataset *ds = NULL;
910         const char *id;
911         const char *value;
912         int row = 0;
913         int rows = 100; /* XXX obviously we'd prefer something more efficient here! */
914         int cds_added = 0;
915
916         selected = cw->userdata;
917         r = dfui_response_new(dfui_form_get_id(f),
918                               dfui_action_get_id(selected));
919
920         /* Create one dataset per row. */
921         for (row = 1; row < rows; row++) {
922                 ds = dfui_dataset_new();
923                 cds_added = 0;
924                 for (cw = cf->widget_head; cw != NULL; cw = cw->next) {
925                         if (cw->user_id == row &&
926                             (cw->type == CURSES_TEXTBOX || cw->type == CURSES_CHECKBOX)) {
927                                 id = dfui_field_get_id((struct dfui_field *)cw->userdata);
928                                 value = curses_widget_xlat_value(cw);
929                                 dfui_dataset_celldata_add(ds, id, value);
930                                 cds_added += 1;
931                         }
932                 }
933                 if (cds_added > 0) {
934                         dfui_response_dataset_add(r, ds);
935                 } else {
936                         dfui_dataset_free(ds);
937                 }
938         }
939
940         return(r);
941 }
942
943 struct dfui_response *
944 response_construct_from_curses_form(const struct dfui_form *f,
945                                     const struct curses_form *cf,
946                                     const struct curses_widget *cw)
947 {
948         if (dfui_form_is_multiple(f))
949                 return(response_construct_from_curses_form_multiple(f, cf, cw));
950         else
951                 return(response_construct_from_curses_form_single(f, cf, cw));
952 }