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
cb_click_remove_row(struct curses_widget * w)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
cb_click_select_option(struct curses_widget * w)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 *
create_default_dataset(const struct dfui_form * f)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
cb_click_insert_row(struct curses_widget * w)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
curses_form_create_widget_row(struct curses_form * cf,struct curses_widget * cw,const struct dfui_dataset * ds,int left,int top,int row)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 *
center_buttons(struct curses_form * cf,struct curses_widget * row_start,int is_menu)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
create_buttons(const struct dfui_form * f,struct curses_form * cf,int is_menu)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
set_help(const struct dfui_form * f,struct curses_form * cf)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 *
curses_form_construct_from_dfui_form_single(const struct dfui_form * f)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 *
curses_form_construct_from_dfui_form_multiple(const struct dfui_form * f)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 *
curses_form_construct_from_dfui_form(const struct dfui_form * f)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
strcpy_max(char * dest,const char * src,unsigned int max)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 *
curses_form_construct_from_dfui_progress(const struct dfui_progress * pr,struct curses_widget ** pbar,struct curses_widget ** plab,struct curses_widget ** pcan)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
curses_widgets_update_from_dfui_progress(const struct dfui_progress * pr,struct curses_widget * pbar,struct curses_widget * plab,struct curses_widget * pcan)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 *
curses_widget_xlat_value(const struct curses_widget * cw)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 *
response_construct_from_curses_form_single(const struct dfui_form * f,const struct curses_form * cf,const struct curses_widget * cw)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 *
response_construct_from_curses_form_multiple(const struct dfui_form * f,const struct curses_form * cf,const struct curses_widget * cw)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 *
response_construct_from_curses_form(const struct dfui_form * f,const struct curses_form * cf,const struct curses_widget * cw)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