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 }
953