Beispiel #1
0
/*
 * Prepare the widget for being placed in the form.  This implements
 * automatically positioning the widget and automatically resizing
 * the form if the widget is too large to fit.
 */
static void
curses_form_widget_prepare(struct curses_form *cf, struct curses_widget *w)
{
	/*
	 * Link the widget to the form.
	 */
	w->form = cf;

	/*
	 * Auto-position the widget to the center of the form,
	 * if requested.
	 */
	if (w->flags & CURSES_WIDGET_CENTER)
		w->x = (cf->width - w->width) / 2;

	/*
	 * If the widget's right edge exceeds the width of
	 * the form, expand the form.
	 */
	dfui_debug("w->x=%d w->width=%d cf->width=%d : ",
	    w->x, w->width, cf->width);
	if ((w->x + w->width + 1) > cf->width)
		cf->width = w->x + w->width + 1;
	dfui_debug("new cf->width=%d\n", cf->width);
}
Beispiel #2
0
/*
 * Ask for, and subsequently receieve, a message from the backend.
 * msgtype should be one of the DFUI_FE_MSG_* constants.
 * This call is synchronous.
 * After this call, the null-terminated, encoded message is
 * available in T_NPIPE(c)->buf.
 */
dfui_err_t
dfui_npipe_fe_ll_request(struct dfui_connection *c, char msgtype, const char *msg)
{
	char *fmsg, *buf;
	int length;

	/*
	 * First, assert that the connection is open.
	 */
	
	if (c == NULL || T_NPIPE(c)->in == NULL || T_NPIPE(c)->out == NULL)
		return(DFUI_FAILURE);

	/*
	 * Construct a message.
	 */

	fmsg = malloc(strlen(msg) + 1);
	fmsg[0] = msgtype;
	strcpy(fmsg + 1, msg);

	dfui_debug("SEND<<%s>>\n", fmsg);

	/*
	 * Send a NUL-terminated message to the backend.
	 */

	length = strlen(fmsg);
	fwrite(&length, 4, 1, T_NPIPE(c)->out);
	fwrite(fmsg, length, 1, T_NPIPE(c)->out);

	/*
	 * Receive a reply from the backend.
	 * If our message was a READY, this should be a message like PRESENT.
	 * Otherwise it should simply be a READY.
	 */

	dfui_debug("WAITING<<>>\n");

	fread(&length, 4, 1, T_NPIPE(c)->in);
	buf = malloc(length + 1);
	fread(buf, length, 1, T_NPIPE(c)->in);
	aura_buffer_set(c->ebuf, buf, length);
	free(buf);

	dfui_debug("RECV<<%s>>\n", aura_buffer_buf(c->ebuf));

	free(fmsg);

	return(DFUI_SUCCESS);
}
Beispiel #3
0
/*
 * Connect to the frontend.
 */
dfui_err_t
dfui_tcp_be_start(struct dfui_connection *c)
{
	struct sockaddr_in servaddr;
	int server_port;
	int tru = 1;

	server_port = atoi(c->rendezvous);

	/*
	 * Create the tcp socket
	 */
	errno = 0;
	if ((T_TCP(c)->listen_sd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		return(DFUI_FAILURE);
	dfui_debug("LISTEN_SOCKET<<%d>>\n", T_TCP(c)->listen_sd);

	if (setsockopt(T_TCP(c)->listen_sd, SOL_SOCKET, SO_REUSEADDR,
	    &tru, sizeof(tru)) == -1) {
		return(DFUI_FAILURE);		
	}

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(server_port);
	switch(inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr)) {
	case 0:
		warnx("inet_pton(): address not parseable");
		return(DFUI_FAILURE);	
	case 1:
		break;
	default:
		warn("inet_pton()");
		return(DFUI_FAILURE);
	}
	
	if (bind(T_TCP(c)->listen_sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
		warn("bind()");
		return(DFUI_FAILURE);
	}
	dfui_debug("BOUND_ON<<%d>>\n", T_TCP(c)->listen_sd);
	if (listen(T_TCP(c)->listen_sd, 0) == -1)
		return(DFUI_FAILURE);
	dfui_debug("LISTENING_ON<<%d>>\n", T_TCP(c)->listen_sd);
	/* at this point we should be listening on the rendezvous port */
	return(DFUI_SUCCESS);
}
Beispiel #4
0
/*
 * Create a row of buttons, one for each action, at
 * the bottom of a curses_form.
 */
static void
create_buttons(const struct dfui_form *f, struct curses_form *cf, int is_menu)
{
	struct curses_widget *w;
	char name[80];
	struct dfui_action *a;
	struct curses_widget *row_start = NULL;
	int left_acc = 1;
	const char *accel;

	for (a = dfui_form_action_get_first(f); a != NULL;
	     a = dfui_action_get_next(a)) {
		strlcpy(name, dfui_info_get_name(dfui_action_get_info(a)), 70);

		dfui_debug("creating button `%s' (%d) @ %d / %d\n",
			name, strlen(name), left_acc, cf->width);

		/*
		 * Check for overflow.  If the next button would appear
		 * off the right side of the form, start putting buttons
		 * on the next row.  Or, if this is a menu, always put the
		 * next button on the next line.
		 */
		if (is_menu ||
		    ((left_acc + strlen(name) + 6) > cf->width &&
		    left_acc > 1)) {
			row_start = center_buttons(cf, row_start, is_menu);
			cf->height++;
			left_acc = 1;
		}

		w = curses_form_widget_add(cf, left_acc,
		    cf->height, 0, CURSES_BUTTON, name, 0, CURSES_WIDGET_WIDEN);
		curses_widget_tooltip_set(w,
		    dfui_info_get_short_desc(dfui_action_get_info(a)));

		accel = dfui_action_property_get(a, "accelerator");
		if (strlen(accel) > 0) {
			if (strcmp(accel, "ESC") == 0) {
				w->accel = '\e';
			} else {
				w->accel = toupper(accel[0]);
			}
		}

		left_acc += (w->width + 2);
		w->user_id = -1;
		w->userdata = a;
		curses_widget_set_click_cb(w, cb_click_close_form);
		if (row_start == NULL)
			row_start = w;
	}

	center_buttons(cf, row_start, is_menu);
}
Beispiel #5
0
int
read_data(FILE *f, char *buf, int n)
{
	int bcount;	/* counts bytes read */ 
	int br;		/* bytes read this pass */ 

	bcount = 0;
	br = 0;
	while (bcount < n) {
		if ((br = fread(buf, 1, n - bcount, f)) > 0) {
			dfui_debug("READ_BYTES<<%d>>\n", br);
			bcount += br;
			buf += br;
		} else if (br <= 0) {
			dfui_debug("read_data_error<<%d>>\n", br);
			return(-1);
		}
	}
	return(bcount);
}
Beispiel #6
0
int
write_data(FILE *f, const char *buf, int n)
{
        int bcount;	/* counts bytes written */
        int bw;		/* bytes written this pass */

        bcount = 0;
        bw = 0;
        while (bcount < n) {
                if ((bw = fwrite(buf, 1, n - bcount, f)) > 0) {
			dfui_debug("WROTE_BYTES<<%d>>\n", bw);
                        bcount += bw;
                        buf += bw;
                } else if (bw <= 0) {
			dfui_debug("write_data_error<<%d>>\n", bw);
			return(-1);
		}
        }
        return(bcount);
}
Beispiel #7
0
/*
 * Receive a message from the frontend.
 * This call is synchronous.
 * After this call, the NUL-terminated message is available in
 * c->ebuf.
 */
dfui_err_t
dfui_npipe_be_ll_receive(struct dfui_connection *c)
{
	int length;
	char *buf;

	dfui_debug("WAITING<<>>\n");

	fread(&length, 4, 1, T_NPIPE(c)->in);

	dfui_debug("LENGTH<<%d>>\n", length);

	buf = malloc(length + 1);
	fread(buf, length, 1, T_NPIPE(c)->in);
	aura_buffer_set(c->ebuf, buf, length);
	free(buf);

	dfui_debug("RECEIVED<<%s>>\n", aura_buffer_buf(c->ebuf));

	return(DFUI_SUCCESS);
}
Beispiel #8
0
/*
 * Send a NUL-terminated reply to the frontend.
 */
dfui_err_t
dfui_tcp_be_ll_reply(struct dfui_connection *c, const char *fmsg)
{
	int length;

	dfui_debug("SEND<<%s>>\n", fmsg);
	length = strlen(fmsg);
	write_data(T_TCP(c)->stream, (char *)&length, sizeof(length));
	write_data(T_TCP(c)->stream, fmsg, length);

	return(DFUI_SUCCESS);
}
Beispiel #9
0
dfui_err_t
dfui_tcp_fe_connect(struct dfui_connection *c)
{
        struct sockaddr_in servaddr;
        int server_port;

        server_port = atoi(c->rendezvous);

        /*
         * Create the tcp socket
         */
	while (!T_TCP(c)->is_connected) {
		errno = 0;
		if ((T_TCP(c)->connected_sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
			return(DFUI_FAILURE);
		}

		dfui_debug("CLIENT_SOCKET<<%d>>\n", T_TCP(c)->connected_sd);
		bzero(&servaddr, sizeof(servaddr));
		servaddr.sin_family = AF_INET;
		servaddr.sin_port = htons(server_port);
		inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);

		if (connect(T_TCP(c)->connected_sd, (struct sockaddr *)&servaddr,
		    sizeof(servaddr)) == 0) {
			dfui_debug("CONNECTED<<>>\n");
			T_TCP(c)->is_connected = 1;
		} else {
			dfui_debug("NO_CONNECT<<>>\n");
			close(T_TCP(c)->connected_sd);
			sleep(1);
		}
	}

        /* at this point we should be connected */

	T_TCP(c)->stream = fdopen(T_TCP(c)->connected_sd, "r+");

        return(DFUI_SUCCESS);
}
Beispiel #10
0
int
curses_form_descriptive_labels_add(struct curses_form *cf, const char *text,
				   unsigned int x, unsigned int y,
				   unsigned int width)
{
	struct curses_widget *w;
	int done = 0;
	int pos = 0;
	char *line;

	line = aura_malloc(width + 1, "descriptive line");
	while (!done) {
		done = extract_wrapped_line(text, line, width, &pos);
		dfui_debug("line = `%s', done = %d, width = %d, form width = %d : ",
		   line, done, width, cf->width);
		w = curses_form_widget_add(cf, x, y++, 0,
		    CURSES_LABEL, line, 0, CURSES_WIDGET_WIDEN);
		dfui_debug("now %d\n", cf->width);
	}
	free(line);
	return(y);
}
Beispiel #11
0
/*
 * Ask for, and subsequently receieve, a message from the backend.
 * msgtype should be one of the DFUI_FE_MSG_* constants.
 * This call is synchronous.
 * After this call, the null-terminated, encoded message is
 * available in T_TCP(c)->buf.
 */
dfui_err_t
dfui_tcp_fe_ll_request(struct dfui_connection *c, char msgtype, const char *msg)
{
	char *fmsg, *buf;
	int length, result;

	/*
	 * First, assert that the connection is open.
	 */
	
	if (c == NULL || T_TCP(c)->connected_sd == -1)
		return(DFUI_FAILURE);

	/*
	 * Construct a message.
	 */

	fmsg = malloc(strlen(msg) + 2);
	fmsg[0] = msgtype;
	strcpy(fmsg + 1, msg);
	dfui_debug("SEND<<%s>>\n", fmsg);

	/*
	 * Send a NUL-terminated message to the backend.
	 */

        length = strlen(fmsg);
        result = write_data(T_TCP(c)->stream, (char *)&length, sizeof(length));
	dfui_debug("result<<%d>>\n", result);
	result = write_data(T_TCP(c)->stream, (char *)fmsg, length);
	dfui_debug("result<<%d>>\n", result);

	/*
	 * Receive a reply from the backend.
	 * If our message was a READY, this should be a message like PRESENT.
	 * Otherwise it should simply be a READY.
	 */

	dfui_debug("WAITING<<>>\n");
        result = read_data(T_TCP(c)->stream, (char *)&length, sizeof(length));
	dfui_debug("result<<%d>>\n", result);
        buf = malloc(length + 1);
        result = read_data(T_TCP(c)->stream, buf, length);
	dfui_debug("result<<%d>>\n", result);
        aura_buffer_set(c->ebuf, buf, length);
        free(buf);

	dfui_debug("RECV<<%s>>\n", aura_buffer_buf(c->ebuf));

	free(fmsg);

	return(DFUI_SUCCESS);
}
Beispiel #12
0
/*
 * Send a NUL-terminated reply to the frontend.
 */
dfui_err_t
dfui_npipe_be_ll_reply(struct dfui_connection *c, const char *fmsg)
{
	int length;

	dfui_debug("SEND<<%s>>\n", fmsg);

	length = strlen(fmsg);

	fwrite(&length, 4, 1, T_NPIPE(c)->out);
	fwrite(fmsg, length, 1, T_NPIPE(c)->out);

	return(DFUI_SUCCESS);
}
Beispiel #13
0
dfui_err_t
dfui_npipe_fe_connect(struct dfui_connection *c)
{
	asprintf(&T_NPIPE(c)->in_pipename, "/tmp/dfui.%s.to_fe", c->rendezvous);
	asprintf(&T_NPIPE(c)->out_pipename, "/tmp/dfui.%s.from_fe", c->rendezvous);

	dfui_debug("waiting for named pipes...\n");

	/*
	 * Wait for named pipes to be created.
	 */
	if (!is_named_pipe(T_NPIPE(c)->in_pipename)) {
		while (!is_named_pipe(T_NPIPE(c)->in_pipename)) {
			sleep(1);
		}
		sleep(1);
	}

	dfui_debug("opening inflow pipe...\n");

	if ((T_NPIPE(c)->in = fopen(T_NPIPE(c)->in_pipename, "r")) == NULL) {
		return(DFUI_FAILURE);
	}

	dfui_debug("opening outflow pipe...\n");

	if ((T_NPIPE(c)->out = fopen(T_NPIPE(c)->out_pipename, "w")) == NULL) {
		fclose(T_NPIPE(c)->in);
		return(DFUI_FAILURE);
	}

	dfui_debug("making outflow pipe raw...\n");

	setvbuf(T_NPIPE(c)->out, NULL, _IONBF, 0);
	return(DFUI_SUCCESS);
}
Beispiel #14
0
/*
 * Connect to the frontend.
 */
dfui_err_t
dfui_npipe_be_start(struct dfui_connection *c)
{
	asprintf(&T_NPIPE(c)->out_pipename, "/tmp/dfui.%s.to_fe", c->rendezvous);
	asprintf(&T_NPIPE(c)->in_pipename, "/tmp/dfui.%s.from_fe", c->rendezvous);

	/*
	 * Create the named pipes.
	 */
	errno = 0;
	if (mkfifo(T_NPIPE(c)->in_pipename, 0600) < 0) {
		if (errno != EEXIST) {
			warn("mkfifo (to_be)");
			return(DFUI_FAILURE);
		}
	}
	errno = 0;
	if (mkfifo(T_NPIPE(c)->out_pipename, 0600) < 0) {
		if (errno != EEXIST) {
			warn("mkfifo (to_fe)");
			return(DFUI_FAILURE);
		}
	}
	dfui_debug("opening pipes...\n");
	if ((T_NPIPE(c)->out = fopen(T_NPIPE(c)->out_pipename, "w")) == NULL) {
		return(DFUI_FAILURE);
	}
	dfui_debug("opened to_fe pipe\n");
	setvbuf(T_NPIPE(c)->out, NULL, _IONBF, 0);
	if ((T_NPIPE(c)->in = fopen(T_NPIPE(c)->in_pipename, "r")) == NULL) {
		fclose(T_NPIPE(c)->out);
		return(DFUI_FAILURE);
	}
	dfui_debug("opened to_be pipe\n");
	return(DFUI_SUCCESS);
}
Beispiel #15
0
/*
 * Receive a message from the frontend.
 * This call is synchronous.
 * After this call, the NUL-terminated message is available in
 * c->ebuf.
 */
dfui_err_t
dfui_tcp_be_ll_receive(struct dfui_connection *c)
{
	int length;
	char *buf;

	top:

	if (!T_TCP(c)->is_connected) {
		dfui_debug("NOT_CONNECTED,ACCEPTING_ON<<%d>>\n", T_TCP(c)->listen_sd);
		T_TCP(c)->connected_sd = accept(T_TCP(c)->listen_sd, NULL, NULL);
		dfui_debug("ACCEPTED<<%d>>\n", T_TCP(c)->connected_sd);
		T_TCP(c)->stream = fdopen(T_TCP(c)->connected_sd, "r+");
		T_TCP(c)->is_connected = 1;
	} else {
		dfui_debug("ALREADY_CONNECTED<<>>\n");
	}

	dfui_debug("WAITING<<>>\n");

	if (read_data(T_TCP(c)->stream, (char *)&length, sizeof(length)) == -1) {
		dfui_debug("LOST_THEM<<>>\n");
		fclose(T_TCP(c)->stream);
		T_TCP(c)->is_connected = 0;
		goto top;
	}

	buf = malloc(length + 1);
	if (read_data(T_TCP(c)->stream, buf, length) == -1) {
		dfui_debug("LOST_THEM<<>>\n");
		fclose(T_TCP(c)->stream);
		T_TCP(c)->is_connected = 0;
		goto top;
	}

	aura_buffer_set(c->ebuf, buf, length);
	free(buf);

	dfui_debug("RECEIVED<<%s>>\n", aura_buffer_buf(c->ebuf));

	return(DFUI_SUCCESS);
}
Beispiel #16
0
static struct curses_form *
curses_form_construct_from_dfui_form_single(const struct dfui_form *f)
{
	struct curses_form *cf;
	struct curses_form_userdata *cfu;
	const char *min_width_str;
	unsigned int desc_width, min_width = 0;
	unsigned int len, max_label_width, total_label_width;
	unsigned int max_button_width, total_button_width;
	struct dfui_field *fi;
	struct dfui_action *a;
	struct curses_widget *label, *xbox;
	struct dfui_celldata *cd;
	const char *value;
	int is_menu;

	dfui_debug("-----\nconstructing single form: %s\n",
	    dfui_info_get_name(dfui_form_get_info(f)));

	is_menu = dfui_form_property_is(f, "role", "menu");
	cf = curses_form_new(dfui_info_get_name(dfui_form_get_info(f)));
	AURA_MALLOC(cfu, curses_form_userdata);
	cfu->f = f;
	cf->userdata = cfu;
	cf->cleanup = 1;

	set_help(f, cf);

	/* Calculate offsets for nice positioning of labels and buttons. */

	/*
	 * Determine the widths of the widest field and the widest
	 * button, and the total widths of all fields and all buttons.
	 */

	max_label_width = 0;
	total_label_width = 0;
	max_button_width = 0;
	total_button_width = 0;

	for (fi = dfui_form_field_get_first(f); fi != NULL;
	     fi = dfui_field_get_next(fi)) {
		len = MIN(60, strlen(dfui_info_get_name(dfui_field_get_info(fi))));
		if (len > max_label_width)
			max_label_width = len;
		total_label_width += (len + 2);
	}
	for (a = dfui_form_action_get_first(f); a != NULL;
	     a = dfui_action_get_next(a)) {
		len = strlen(dfui_info_get_name(dfui_action_get_info(a)));
		if (len > max_button_width)
			max_button_width = len;
		total_button_width += (len + 6);
	}

	if (total_label_width > (xmax - 2))
		total_label_width = (xmax - 2);		/* XXX scroll/wrap? */

	/* Take the short description and turn it into a set of labels. */

	if ((min_width_str = dfui_form_property_get(f, "minimum_width")) != NULL)
		min_width = atoi(min_width_str);

	desc_width = 40;
	desc_width = MAX(desc_width, min_width);
	if (is_menu) {
		desc_width = MAX(desc_width, max_button_width);
	} else {
		desc_width = MAX(desc_width, total_button_width);
	}
	desc_width = MAX(desc_width, max_label_width);  /* XXX + max_field_width */
	desc_width = MIN(desc_width, xmax - 4); /* -2 for borders, -2 for spaces */

	dfui_debug("min width: %d\n", min_width);
	dfui_debug("button width: %d\n", total_button_width);
	dfui_debug("label width: %d\n", total_label_width);
	dfui_debug("resulting width: %d\n", desc_width);
	dfui_debug("form width: %d\n", cf->width);

	cf->height = curses_form_descriptive_labels_add(cf,
	    dfui_info_get_short_desc(dfui_form_get_info(f)),
	    1, cf->height + 1, desc_width);

	dfui_debug("form width now: %d\n", cf->width);

	if (!is_menu)
		cf->height++;

	/*
	 * Add one label and one textbox (or other control) to a
	 * curses_form for each field in the dfui_form.  Each set of
	 * labels and controls is added one row below the previous set.
	 */
	for (fi = dfui_form_field_get_first(f); fi != NULL;
	     fi = dfui_field_get_next(fi)) {
		label = curses_form_widget_add(cf, 1,
		    cf->height, max_label_width, CURSES_LABEL,
		    dfui_info_get_name(dfui_field_get_info(fi)), 0, 0);

		cd = dfui_dataset_celldata_find(dfui_form_dataset_get_first(f),
		    dfui_field_get_id(fi));

		value = dfui_celldata_get_value(cd);

		if (dfui_field_property_is(fi, "control", "checkbox")) {
			xbox = curses_form_widget_add(cf,
			    max_label_width + 3,
			    cf->height, 4, CURSES_CHECKBOX, "", 0, 0);
			xbox->amount = (value[0] == 'Y' ? 1 : 0);
		} else {
			xbox = curses_form_widget_add(cf,
			    max_label_width + 3,
			    cf->height, 20, CURSES_TEXTBOX, value, 256, 0);
		}
		curses_widget_tooltip_set(xbox,
		    dfui_info_get_short_desc(dfui_field_get_info(fi)));
		xbox->user_id = 1;
		xbox->userdata = fi;

		if (dfui_field_property_is(fi, "editable", "false"))
			xbox->editable = 0;
		if (dfui_field_property_is(fi, "obscured", "true"))
			xbox->obscured = 1;

		if (dfui_field_option_get_first(fi) != NULL) {
			curses_widget_set_click_cb(xbox, cb_click_select_option);
		}

		cf->height++;
	}

	if (dfui_form_field_get_first(f) != NULL)
		cf->height++;

	create_buttons(f, cf, is_menu);

	cf->height++;

	curses_form_finalize(cf);

	return(cf);
}
Beispiel #17
0
static struct curses_form *
curses_form_construct_from_dfui_form_multiple(const struct dfui_form *f)
{
	struct curses_form *cf;
	struct curses_form_userdata *cfu;
	const char *min_width_str;
	unsigned int desc_width, min_width = 0;
	unsigned int len, max_label_width, total_label_width;
	unsigned int max_button_width, total_button_width;
	struct dfui_field *fi;
	struct dfui_action *a;
	struct curses_widget *label, *button;
	struct dfui_dataset *ds;
	const char *name;
	int left_acc, top_acc;
	int row = 1, col = 0, ins_x = 1, is_menu = 0;

	dfui_debug("-----\nconstructing multiple form: %s\n",
	    dfui_info_get_name(dfui_form_get_info(f)));

	cf = curses_form_new(dfui_info_get_name(dfui_form_get_info(f)));
	AURA_MALLOC(cfu, curses_form_userdata);
	cfu->f = f;
	cf->userdata = cfu;
	cf->cleanup = 1;

	set_help(f, cf);

	/* Calculate offsets for nice positioning of labels and buttons. */

	/*
	 * Determine the widths of the widest field and the widest
	 * button, and the total widths of all fields and all buttons.
	 */

	max_label_width = 0;
	total_label_width = 0;
	max_button_width = 0;
	total_button_width = 0;

	for (fi = dfui_form_field_get_first(f); fi != NULL;
	     fi = dfui_field_get_next(fi)) {
		len = MIN(60, strlen(dfui_info_get_name(dfui_field_get_info(fi))));
		if (len > max_label_width)
			max_label_width = len;
		total_label_width += (len + 2);
	}
	for (a = dfui_form_action_get_first(f); a != NULL;
	     a = dfui_action_get_next(a)) {
		len = strlen(dfui_info_get_name(dfui_action_get_info(a)));
		if (len > max_button_width)
			max_button_width = len;
		total_button_width += (len + 6);
	}

	/* Take the short description and turn it into a set of labels. */

	if ((min_width_str = dfui_form_property_get(f, "minimum_width")) != NULL)
		min_width = atoi(min_width_str);

	desc_width = 40;
	desc_width = MAX(desc_width, min_width);
	desc_width = MAX(desc_width, total_button_width);
	desc_width = MAX(desc_width, total_label_width);
	desc_width = MIN(desc_width, xmax - 3);

	dfui_debug("min width: %d\n", min_width);
	dfui_debug("button width: %d\n", total_button_width);
	dfui_debug("label width: %d\n", total_label_width);
	dfui_debug("resulting width: %d\n", desc_width);
	dfui_debug("form width: %d\n", cf->width);

	cf->height = curses_form_descriptive_labels_add(cf,
	    dfui_info_get_short_desc(dfui_form_get_info(f)),
	    1, cf->height + 1, desc_width);

	dfui_debug("form width now: %d\n", cf->width);

	/* Add the fields. */

	top_acc = cf->height + 1;
	cf->height += dfui_form_dataset_count(f) + 2;

	/*
	 * Create the widgets for a multiple=true form.  For each field
	 * in the form, a label containing the field's name, which serves
	 * as a heading, is created.  Underneath these labels, for each
	 * dataset in the form, a row of input widgets (typically textboxes)
	 * is added.  Non-action, manipulation buttons are also added to
	 * the right of each row.
	 */
	left_acc = 1;
	for (fi = dfui_form_field_get_first(f); fi != NULL;
	     fi = dfui_field_get_next(fi)) {
		/*
		 * Create a label to serve as a heading for the column.
		 */
		name = dfui_info_get_name(dfui_field_get_info(fi));
		label = curses_form_widget_add(cf, left_acc,
		    top_acc, 0, CURSES_LABEL, name, 0,
		    CURSES_WIDGET_WIDEN);
		cfu->widths[col++] = label->width + 2;
		left_acc += (label->width + 2);
	}

	/*
	 * Create a row of widgets for each dataset.
	 */
	top_acc++;
	for (ds = dfui_form_dataset_get_first(f); ds != NULL;
	     ds = dfui_dataset_get_next(ds)) {
		ins_x = curses_form_create_widget_row(cf, NULL, ds,
		     1, top_acc++, row++);
	}

	/*
	 * Finally, create an 'Add' button to add a new row
	 * if this is an extensible form.
	 */
	if (dfui_form_is_extensible(f)) {
		button = curses_form_widget_add(cf,
		    ins_x, top_acc, 0,
		    CURSES_BUTTON, "Add", 0, CURSES_WIDGET_WIDEN);
		button->user_id = row;
		curses_widget_set_click_cb(button, cb_click_insert_row);
		cf->height++;
	}

	cf->height++;

	/* Add the buttons. */

	create_buttons(f, cf, is_menu);

	cf->height++;

	curses_form_finalize(cf);

	return(cf);
}