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 *label, *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 label = 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