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_form.c 36 * $Id: curses_form.c,v 1.13 2005/02/08 22:56:06 cpressey Exp $ 37 */ 38 39 #include <ctype.h> 40 #include <ncurses.h> 41 #include <panel.h> 42 #include <stdlib.h> 43 #include <string.h> 44 45 #ifdef ENABLE_NLS 46 #include <libintl.h> 47 #define _(String) gettext (String) 48 #else 49 #define _(String) (String) 50 #endif 51 52 #include "libaura/mem.h" 53 54 #include "libdfui/dump.h" 55 56 #include "curses_form.h" 57 #include "curses_widget.h" 58 #include "curses_util.h" 59 60 /*** FORMS ***/ 61 62 /* 63 * Create a new curses form with the given title text. 64 */ 65 struct curses_form * 66 curses_form_new(const char *title) 67 { 68 struct curses_form *cf; 69 70 AURA_MALLOC(cf, curses_form); 71 72 cf->win = NULL; 73 cf->pan = NULL; 74 75 cf->widget_head = NULL; 76 cf->widget_tail = NULL; 77 cf->widget_focus = NULL; 78 cf->height = 0; 79 cf->width = strlen(title) + 4; 80 cf->x_offset = 0; 81 cf->y_offset = 0; 82 cf->int_width = 0; 83 cf->int_height = 0; 84 cf->want_x = 0; 85 cf->want_y = 0; 86 87 cf->title = aura_strdup(title); 88 89 cf->userdata = NULL; 90 cf->cleanup = 0; 91 92 cf->help_text = NULL; 93 94 return(cf); 95 } 96 97 /* 98 * Deallocate the memory used for the given curses form and all of the 99 * widgets it contains. Note that this only frees any data at the form's 100 * userdata pointer IFF cleanup is non-zero. Also, it does not cause the 101 * screen to be refreshed - call curses_form_refresh(NULL) afterwards to 102 * make the form disappear. 103 */ 104 void 105 curses_form_free(struct curses_form *cf) 106 { 107 struct curses_widget *w, *t; 108 109 w = cf->widget_head; 110 while (w != NULL) { 111 t = w->next; 112 curses_widget_free(w); 113 w = t; 114 } 115 116 if (cf->help_text != NULL) { 117 free(cf->help_text); 118 } 119 120 if (cf->cleanup && cf->userdata != NULL) { 121 free(cf->userdata); 122 } 123 124 if (cf->win != NULL) { 125 del_panel(cf->pan); 126 delwin(cf->win); 127 } 128 129 free(cf->title); 130 AURA_FREE(cf, curses_form); 131 } 132 133 /* 134 * Prepare the widget for being placed in the form. This implements 135 * automatically positioning the widget and automatically resizing 136 * the form if the widget is too large to fit. 137 */ 138 static void 139 curses_form_widget_prepare(struct curses_form *cf, struct curses_widget *w) 140 { 141 /* 142 * Link the widget to the form. 143 */ 144 w->form = cf; 145 146 /* 147 * Auto-position the widget to the center of the form, 148 * if requested. 149 */ 150 if (w->flags & CURSES_WIDGET_CENTER) 151 w->x = (cf->width - w->width) / 2; 152 153 /* 154 * If the widget's right edge exceeds the width of 155 * the form, expand the form. 156 */ 157 dfui_debug("w->x=%d w->width=%d cf->width=%d : ", 158 w->x, w->width, cf->width); 159 if ((w->x + w->width + 1) > cf->width) 160 cf->width = w->x + w->width + 1; 161 dfui_debug("new cf->width=%d\n", cf->width); 162 } 163 164 /* 165 * Create a new curses_widget and add it to an existing curses_form. 166 * If the width of the widget is larger than will fit on the form, the 167 * form will be expanded, unless it would be expanded larger than the 168 * screen, in which case the widget is shrunk. 169 */ 170 struct curses_widget * 171 curses_form_widget_add(struct curses_form *cf, 172 unsigned int x, unsigned int y, 173 unsigned int width, widget_t type, 174 const char *text, unsigned int size, 175 unsigned int flags) 176 { 177 struct curses_widget *w; 178 179 w = curses_widget_new(x, y, width, type, text, size, flags); 180 curses_form_widget_prepare(cf, w); 181 182 if (cf->widget_head == NULL) { 183 cf->widget_head = w; 184 } else { 185 cf->widget_tail->next = w; 186 w->prev = cf->widget_tail; 187 } 188 cf->widget_tail = w; 189 190 return(w); 191 } 192 193 /* 194 * Create a new curses_widget and add it after an existing curses_widget 195 * in an existing curses_form. 196 */ 197 struct curses_widget * 198 curses_form_widget_insert_after(struct curses_widget *cw, 199 unsigned int x, unsigned int y, 200 unsigned int width, widget_t type, 201 const char *text, unsigned int size, 202 unsigned int flags) 203 { 204 struct curses_widget *w; 205 206 w = curses_widget_new(x, y, width, type, text, size, flags); 207 curses_form_widget_prepare(cw->form, w); 208 209 w->prev = cw; 210 w->next = cw->next; 211 if (cw->next == NULL) 212 cw->form->widget_tail = w; 213 else 214 cw->next->prev = w; 215 cw->next = w; 216 217 return(w); 218 } 219 220 /* 221 * Unlink a widget from a form. Does not deallocate the widget. 222 */ 223 void 224 curses_form_widget_remove(struct curses_widget *w) 225 { 226 if (w->prev == NULL) 227 w->form->widget_head = w->next; 228 else 229 w->prev->next = w->next; 230 231 if (w->next == NULL) 232 w->form->widget_tail = w->prev; 233 else 234 w->next->prev = w->prev; 235 236 w->next = NULL; 237 w->prev = NULL; 238 w->form = NULL; 239 } 240 241 int 242 curses_form_descriptive_labels_add(struct curses_form *cf, const char *text, 243 unsigned int x, unsigned int y, 244 unsigned int width) 245 { 246 struct curses_widget *w; 247 int done = 0; 248 int pos = 0; 249 char *line; 250 251 line = aura_malloc(width + 1, "descriptive line"); 252 while (!done) { 253 done = extract_wrapped_line(text, line, width, &pos); 254 dfui_debug("line = `%s', done = %d, width = %d, form width = %d : ", 255 line, done, width, cf->width); 256 w = curses_form_widget_add(cf, x, y++, 0, 257 CURSES_LABEL, line, 0, CURSES_WIDGET_WIDEN); 258 dfui_debug("now %d\n", cf->width); 259 } 260 free(line); 261 return(y); 262 } 263 264 void 265 curses_form_finalize(struct curses_form *cf) 266 { 267 if (cf->widget_head != NULL) { 268 cf->widget_focus = cf->widget_head; 269 curses_form_focus_skip_forward(cf); 270 cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2; 271 cf->want_y = cf->widget_focus->y; 272 } else { 273 cf->widget_focus = NULL; 274 } 275 276 cf->left = (xmax - cf->width) / 2; 277 cf->top = (ymax - cf->height) / 2; 278 279 /* 280 * Set the internal width and height. 281 */ 282 283 cf->int_width = cf->width; 284 cf->int_height = cf->height; 285 286 /* 287 * Limit form size to physical screen dimensions. 288 */ 289 if (cf->width > (xmax - 2)) { 290 cf->width = xmax - 2; 291 cf->left = 1; 292 } 293 if (cf->height > (ymax - 2)) { 294 cf->height = ymax - 2; 295 cf->top = 1; 296 } 297 if (cf->top < 1) 298 cf->top = 1; 299 if (cf->left < 1) 300 cf->left = 1; 301 302 cf->win = newwin(cf->height + 2, cf->width + 2, cf->top - 1, cf->left - 1); 303 if (cf->win == NULL) 304 fprintf(stderr, "Could not allocate %dx%d window @ %d,%d\n", 305 cf->width + 2, cf->height + 2, cf->left - 1, cf->top - 1); 306 307 cf->pan = new_panel(cf->win); 308 } 309 310 /* 311 * Render the given form (and all of the widgets it contains) in the 312 * curses backing store. Does not cause the form to be displayed. 313 */ 314 void 315 curses_form_draw(struct curses_form *cf) 316 { 317 struct curses_widget *w; 318 float sb_factor = 0.0; 319 size_t sb_off = 0, sb_size = 0; 320 321 curses_colors_set(cf->win, CURSES_COLORS_NORMAL); 322 curses_window_blank(cf->win); 323 324 curses_colors_set(cf->win, CURSES_COLORS_BORDER); 325 /* draw_frame(cf->left - 1, cf->top - 1, cf->width + 2, cf->height + 2); */ 326 wborder(cf->win, 0, 0, 0, 0, 0, 0, 0, 0); 327 328 /* 329 * If the internal dimensions of the form exceed the physical 330 * dimensions, draw scrollbar(s) as appropriate. 331 */ 332 if (cf->int_height > cf->height) { 333 sb_factor = (float)cf->height / (float)cf->int_height; 334 sb_size = cf->height * sb_factor; 335 if (sb_size == 0) sb_size = 1; 336 sb_off = cf->y_offset * sb_factor; 337 curses_colors_set(cf->win, CURSES_COLORS_SCROLLAREA); 338 mvwvline(cf->win, 1, cf->width + 1, ACS_CKBOARD, cf->height); 339 curses_colors_set(cf->win, CURSES_COLORS_SCROLLBAR); 340 mvwvline(cf->win, 1 + sb_off, cf->width + 1, ACS_BLOCK, sb_size); 341 } 342 343 if (cf->int_width > cf->width) { 344 sb_factor = (float)cf->width / (float)cf->int_width; 345 sb_size = cf->width * sb_factor; 346 if (sb_size == 0) sb_size = 1; 347 sb_off = cf->x_offset * sb_factor; 348 curses_colors_set(cf->win, CURSES_COLORS_SCROLLAREA); 349 mvwhline(cf->win, cf->height + 1, 1, ACS_CKBOARD, cf->width); 350 curses_colors_set(cf->win, CURSES_COLORS_SCROLLBAR); 351 mvwhline(cf->win, cf->height + 1, 1 + sb_off, ACS_BLOCK, sb_size); 352 } 353 354 curses_colors_set(cf->win, CURSES_COLORS_BORDER); 355 356 /* 357 * Render the title bar text. 358 */ 359 wmove(cf->win, 0, (cf->width - strlen(cf->title)) / 2 - 1); 360 waddch(cf->win, ACS_RTEE); 361 waddch(cf->win, ' '); 362 curses_colors_set(cf->win, CURSES_COLORS_FORMTITLE); 363 waddstr(cf->win, cf->title); 364 curses_colors_set(cf->win, CURSES_COLORS_BORDER); 365 waddch(cf->win, ' '); 366 waddch(cf->win, ACS_LTEE); 367 368 /* 369 * Render a "how to get help" reminder. 370 */ 371 if (cf->help_text != NULL) { 372 static const char *help_msg = "Press F1 for Help"; 373 374 wmove(cf->win, cf->height + 1, 375 (cf->width - strlen(help_msg)) / 2 - 1); 376 waddch(cf->win, ACS_RTEE); 377 waddch(cf->win, ' '); 378 curses_colors_set(cf->win, CURSES_COLORS_FORMTITLE); 379 waddstr(cf->win, help_msg); 380 curses_colors_set(cf->win, CURSES_COLORS_BORDER); 381 waddch(cf->win, ' '); 382 waddch(cf->win, ACS_LTEE); 383 } 384 385 /* 386 * Render the widgets. 387 */ 388 for (w = cf->widget_head; w != NULL; w = w->next) { 389 curses_widget_draw(w); 390 } 391 392 /* to put the cursor there */ 393 curses_widget_draw_tooltip(cf->widget_focus); 394 curses_widget_draw(cf->widget_focus); 395 } 396 397 /* 398 * Cause the given form to be displayed (if it was not displayed previously) 399 * or refreshed (if it was previously displayed.) Passing NULL to this 400 * function causes all visible forms to be refreshed. 401 * 402 * (Implementation note: the argument is actually irrelevant - all visible 403 * forms will be refreshed when any form is displayed or refreshed - but 404 * client code should not rely on this behaviour.) 405 */ 406 void 407 curses_form_refresh(struct curses_form *cf __unused) 408 { 409 update_panels(); 410 doupdate(); 411 } 412 413 void 414 curses_form_focus_skip_forward(struct curses_form *cf) 415 { 416 while (!curses_widget_can_take_focus(cf->widget_focus)) { 417 cf->widget_focus = cf->widget_focus->next; 418 if (cf->widget_focus == NULL) 419 cf->widget_focus = cf->widget_head; 420 } 421 curses_form_widget_ensure_visible(cf->widget_focus); 422 } 423 424 void 425 curses_form_focus_skip_backward(struct curses_form *cf) 426 { 427 while (!curses_widget_can_take_focus(cf->widget_focus)) { 428 cf->widget_focus = cf->widget_focus->prev; 429 if (cf->widget_focus == NULL) 430 cf->widget_focus = cf->widget_tail; 431 } 432 curses_form_widget_ensure_visible(cf->widget_focus); 433 } 434 435 void 436 curses_form_advance(struct curses_form *cf) 437 { 438 struct curses_widget *w; 439 440 w = cf->widget_focus; 441 cf->widget_focus = cf->widget_focus->next; 442 if (cf->widget_focus == NULL) 443 cf->widget_focus = cf->widget_head; 444 curses_form_focus_skip_forward(cf); 445 cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2; 446 cf->want_y = cf->widget_focus->y; 447 curses_widget_draw(w); 448 curses_widget_draw_tooltip(cf->widget_focus); 449 curses_widget_draw(cf->widget_focus); 450 curses_form_refresh(cf); 451 #ifdef DEBUG 452 curses_debug_int(cf->widget_focus->user_id); 453 #endif 454 } 455 456 void 457 curses_form_retreat(struct curses_form *cf) 458 { 459 struct curses_widget *w; 460 461 w = cf->widget_focus; 462 cf->widget_focus = cf->widget_focus->prev; 463 if (cf->widget_focus == NULL) 464 cf->widget_focus = cf->widget_tail; 465 curses_form_focus_skip_backward(cf); 466 cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2; 467 cf->want_y = cf->widget_focus->y; 468 curses_widget_draw(w); 469 curses_widget_draw_tooltip(cf->widget_focus); 470 curses_widget_draw(cf->widget_focus); 471 curses_form_refresh(cf); 472 #ifdef DEBUG 473 curses_debug_int(cf->widget_focus->user_id); 474 #endif 475 } 476 477 /* 478 * Returns the widget at (x, y) within a form, or NULL if 479 * there is no widget at that location. 480 */ 481 struct curses_widget * 482 curses_form_widget_at(struct curses_form *cf, unsigned int x, unsigned int y) 483 { 484 struct curses_widget *w; 485 486 for (w = cf->widget_head; w != NULL; w = w->next) { 487 if (y == w->y && x >= w->x && x <= (w->x + w->width)) 488 return(w); 489 } 490 491 return(NULL); 492 } 493 494 /* 495 * Returns the first (focusable) widget on 496 * the topmost row of the form. 497 */ 498 int 499 curses_form_widget_first_row(struct curses_form *cf) 500 { 501 struct curses_widget *w; 502 503 for (w = cf->widget_head; w != NULL; w = w->next) { 504 if (curses_widget_can_take_focus(w)) 505 return(w->y); 506 } 507 508 return(0); 509 } 510 511 /* 512 * Returns the first (focusable) widget on 513 * the bottommost row of the form. 514 */ 515 int 516 curses_form_widget_last_row(struct curses_form *cf) 517 { 518 struct curses_widget *w; 519 unsigned int best_y = 0; 520 521 for (w = cf->widget_head; w != NULL; w = w->next) { 522 if (curses_widget_can_take_focus(w) && w->y > best_y) { 523 best_y = w->y; 524 } 525 } 526 527 return(best_y); 528 } 529 530 /* 531 * Returns the first (focusable) widget on row y. 532 */ 533 struct curses_widget * 534 curses_form_widget_first_on_row(struct curses_form *cf, unsigned int y) 535 { 536 struct curses_widget *w; 537 538 for (w = cf->widget_head; w != NULL; w = w->next) { 539 if (curses_widget_can_take_focus(w) && y == w->y) 540 return(w); 541 } 542 543 return(NULL); 544 } 545 546 /* 547 * Returns the (focusable) widget on row y closest to x. 548 */ 549 struct curses_widget * 550 curses_form_widget_closest_on_row(struct curses_form *cf, 551 unsigned int x, unsigned int y) 552 { 553 struct curses_widget *w, *best = NULL; 554 int dist, best_dist = 999; 555 556 w = curses_form_widget_first_on_row(cf, y); 557 if (w == NULL) 558 return(NULL); 559 560 for (best = w; w != NULL && w->y == y; w = w->next) { 561 if (!curses_widget_can_take_focus(w)) 562 continue; 563 dist = (w->x + w->width / 2) - x; 564 if (dist < 0) dist *= -1; 565 if (dist < best_dist) { 566 best_dist = dist; 567 best = w; 568 } 569 } 570 571 return(best); 572 } 573 574 /* 575 * Returns the number of (focusable) widgets with y values less than 576 * (above) the given widget. 577 */ 578 int 579 curses_form_widget_count_above(struct curses_form *cf, 580 struct curses_widget *w) 581 { 582 struct curses_widget *lw; 583 int count = 0; 584 585 for (lw = cf->widget_head; lw != NULL; lw = lw->next) { 586 if (curses_widget_can_take_focus(lw) && lw->y < w->y) 587 count++; 588 } 589 590 return(count); 591 } 592 593 /* 594 * Returns the number of (focusable) widgets with y values greater than 595 * (below) the given widget. 596 */ 597 int 598 curses_form_widget_count_below(struct curses_form *cf, 599 struct curses_widget *w) 600 { 601 struct curses_widget *lw; 602 int count = 0; 603 604 for (lw = cf->widget_head; lw != NULL; lw = lw->next) { 605 if (curses_widget_can_take_focus(lw) && lw->y > w->y) 606 count++; 607 } 608 609 return(count); 610 } 611 612 /* 613 * Move to the next widget whose y is greater than the 614 * current want_y, and whose x is closest to want_x. 615 */ 616 void 617 curses_form_advance_row(struct curses_form *cf) 618 { 619 struct curses_widget *w, *c; 620 int wy; 621 622 w = cf->widget_focus; 623 if (curses_form_widget_count_below(cf, w) > 0) { 624 wy = cf->want_y + 1; 625 } else { 626 wy = curses_form_widget_first_row(cf); 627 } 628 do { 629 c = curses_form_widget_closest_on_row(cf, 630 cf->want_x, wy++); 631 } while (c == NULL); 632 633 cf->widget_focus = c; 634 curses_form_focus_skip_forward(cf); 635 cf->want_y = cf->widget_focus->y; 636 curses_widget_draw(w); 637 curses_widget_draw_tooltip(cf->widget_focus); 638 curses_widget_draw(cf->widget_focus); 639 curses_form_refresh(cf); 640 } 641 642 /* 643 * Move to the next widget whose y is less than the 644 * current want_y, and whose x is closest to want_x. 645 */ 646 void 647 curses_form_retreat_row(struct curses_form *cf) 648 { 649 struct curses_widget *w, *c; 650 int wy; 651 652 w = cf->widget_focus; 653 if (curses_form_widget_count_above(cf, w) > 0) { 654 wy = cf->want_y - 1; 655 } else { 656 wy = curses_form_widget_last_row(cf); 657 } 658 do { 659 c = curses_form_widget_closest_on_row(cf, 660 cf->want_x, wy--); 661 } while (c == NULL); 662 663 cf->widget_focus = c; 664 curses_form_focus_skip_backward(cf); 665 cf->want_y = cf->widget_focus->y; 666 curses_widget_draw(w); 667 curses_widget_draw_tooltip(cf->widget_focus); 668 curses_widget_draw(cf->widget_focus); 669 curses_form_refresh(cf); 670 } 671 672 void 673 curses_form_scroll_to(struct curses_form *cf, 674 unsigned int x_off, unsigned int y_off) 675 { 676 cf->x_offset = x_off; 677 cf->y_offset = y_off; 678 } 679 680 void 681 curses_form_scroll_delta(struct curses_form *cf, int dx, int dy) 682 { 683 unsigned int x_off, y_off; 684 685 if (dx < 0 && (unsigned int)-dx > cf->x_offset) { 686 x_off = 0; 687 } else { 688 x_off = cf->x_offset + dx; 689 } 690 if (x_off > (cf->int_width - cf->width)) 691 x_off = cf->int_width - cf->width; 692 693 if (dy < 0 && (unsigned int)-dy > cf->y_offset) { 694 y_off = 0; 695 } else { 696 y_off = cf->y_offset + dy; 697 } 698 if (y_off > (cf->int_height - cf->height)) 699 y_off = cf->int_height - cf->height; 700 701 curses_form_scroll_to(cf, x_off, y_off); 702 } 703 704 static void 705 curses_form_refocus_after_scroll(struct curses_form *cf, int dx, int dy) 706 { 707 struct curses_widget *w; 708 709 w = curses_form_widget_closest_on_row(cf, 710 cf->widget_focus->x + dx, cf->widget_focus->y + dy); 711 712 if (w != NULL) { 713 cf->widget_focus = w; 714 cf->want_x = w->x + w->width / 2; 715 cf->want_y = w->y; 716 } 717 } 718 719 int 720 curses_form_widget_is_visible(struct curses_widget *w) 721 { 722 unsigned int wx, wy; 723 724 wx = w->x + 1 - w->form->x_offset; 725 wy = w->y + 1 - w->form->y_offset; 726 727 if (wy < 1 || wy > w->form->height) 728 return(0); 729 730 return(1); 731 } 732 733 void 734 curses_form_widget_ensure_visible(struct curses_widget *w) 735 { 736 unsigned int wx, wy; 737 int dx = 0, dy = 0; 738 739 /* 740 * If a textbox's offset is such that we can't see 741 * the cursor inside, adjust it. 742 */ 743 if (w->type == CURSES_TEXTBOX) { 744 if (w->curpos - w->offset >= w->width - 2) 745 w->offset = w->curpos - (w->width - 3); 746 if (w->offset > w->curpos) 747 w->offset = w->curpos; 748 } 749 750 if (curses_form_widget_is_visible(w)) 751 return; 752 753 wx = w->x + 1 - w->form->x_offset; 754 wy = w->y + 1 - w->form->y_offset; 755 756 if (wy < 1) 757 dy = -1 * (1 - wy); 758 else if (wy > w->form->height) 759 dy = (wy - w->form->height); 760 761 curses_form_scroll_delta(w->form, dx, dy); 762 curses_form_draw(w->form); 763 curses_form_refresh(w->form); 764 } 765 766 static void 767 curses_form_show_help(const char *text) 768 { 769 struct curses_form *cf; 770 struct curses_widget *w; 771 772 cf = curses_form_new(_("Help")); 773 774 cf->height = curses_form_descriptive_labels_add(cf, text, 1, 1, 72); 775 cf->height += 1; 776 w = curses_form_widget_add(cf, 0, cf->height++, 0, 777 CURSES_BUTTON, _("OK"), 0, CURSES_WIDGET_WIDEN); 778 curses_widget_set_click_cb(w, cb_click_close_form); 779 780 curses_form_finalize(cf); 781 782 curses_form_draw(cf); 783 curses_form_refresh(cf); 784 curses_form_frob(cf); 785 curses_form_free(cf); 786 } 787 788 #define CTRL(c) (char)(c - 'a' + 1) 789 790 struct curses_widget * 791 curses_form_frob(struct curses_form *cf) 792 { 793 int key; 794 795 flushinp(); 796 for (;;) { 797 key = getch(); 798 switch(key) { 799 case KEY_DOWN: 800 case CTRL('n'): 801 curses_form_advance_row(cf); 802 break; 803 case KEY_UP: 804 case CTRL('p'): 805 curses_form_retreat_row(cf); 806 break; 807 case '\t': 808 curses_form_advance(cf); 809 break; 810 case KEY_RIGHT: 811 case CTRL('f'): 812 if (cf->widget_focus->type == CURSES_TEXTBOX) { 813 if (!curses_textbox_advance_char(cf->widget_focus)) 814 curses_form_advance(cf); 815 } else 816 curses_form_advance(cf); 817 break; 818 case KEY_LEFT: 819 case CTRL('b'): 820 if (cf->widget_focus->type == CURSES_TEXTBOX) { 821 if (!curses_textbox_retreat_char(cf->widget_focus)) 822 curses_form_retreat(cf); 823 } else 824 curses_form_retreat(cf); 825 break; 826 case '\n': 827 case '\r': 828 if (cf->widget_focus->type == CURSES_TEXTBOX) { 829 switch (curses_widget_click(cf->widget_focus)) { 830 case -1: 831 curses_form_advance(cf); 832 break; 833 case 0: 834 break; 835 case 1: 836 /* this would be pretty rare */ 837 return(cf->widget_focus); 838 } 839 } else if (cf->widget_focus->type == CURSES_BUTTON) { 840 switch (curses_widget_click(cf->widget_focus)) { 841 case -1: 842 beep(); 843 break; 844 case 0: 845 break; 846 case 1: 847 return(cf->widget_focus); 848 } 849 } else if (cf->widget_focus->type == CURSES_CHECKBOX) { 850 curses_checkbox_toggle(cf->widget_focus); 851 } else { 852 beep(); 853 } 854 break; 855 case '\b': 856 case KEY_BACKSPACE: 857 case 127: /* why is this not KEY_BACKSPACE on xterm?? */ 858 if (cf->widget_focus->type == CURSES_TEXTBOX) { 859 curses_textbox_backspace_char(cf->widget_focus); 860 } else { 861 beep(); 862 } 863 break; 864 case KEY_DC: 865 case CTRL('k'): 866 if (cf->widget_focus->type == CURSES_TEXTBOX) { 867 curses_textbox_delete_char(cf->widget_focus); 868 } else { 869 beep(); 870 } 871 break; 872 case KEY_HOME: 873 case CTRL('a'): 874 if (cf->widget_focus->type == CURSES_TEXTBOX) { 875 curses_textbox_home(cf->widget_focus); 876 } else { 877 beep(); 878 } 879 break; 880 case KEY_END: 881 case CTRL('e'): 882 if (cf->widget_focus->type == CURSES_TEXTBOX) { 883 curses_textbox_end(cf->widget_focus); 884 } else { 885 beep(); 886 } 887 break; 888 case KEY_NPAGE: 889 case CTRL('g'): 890 curses_form_scroll_delta(cf, 0, cf->height - 1); 891 curses_form_refocus_after_scroll(cf, 0, cf->height - 1); 892 curses_form_draw(cf); 893 curses_form_refresh(cf); 894 break; 895 case KEY_PPAGE: 896 case CTRL('t'): 897 curses_form_scroll_delta(cf, 0, -1 * (cf->height - 1)); 898 curses_form_refocus_after_scroll(cf, 0, -1 * (cf->height - 1)); 899 curses_form_draw(cf); 900 curses_form_refresh(cf); 901 break; 902 case ' ': 903 if (cf->widget_focus->type == CURSES_TEXTBOX) { 904 /* XXX if non-editable, click it */ 905 curses_textbox_insert_char(cf->widget_focus, ' '); 906 } else if (cf->widget_focus->type == CURSES_BUTTON) { 907 switch (curses_widget_click(cf->widget_focus)) { 908 case -1: 909 beep(); 910 break; 911 case 0: 912 break; 913 case 1: 914 return(cf->widget_focus); 915 } 916 } else if (cf->widget_focus->type == CURSES_CHECKBOX) { 917 curses_checkbox_toggle(cf->widget_focus); 918 } else { 919 beep(); 920 } 921 break; 922 case KEY_F(1): /* why does this not work in xterm??? */ 923 case CTRL('w'): 924 if (cf->help_text != NULL) { 925 curses_form_show_help(cf->help_text); 926 curses_form_refresh(cf); 927 } 928 break; 929 case KEY_F(10): 930 case CTRL('l'): 931 redrawwin(stdscr); 932 curses_form_refresh(NULL); 933 break; 934 default: 935 if (isprint(key) && cf->widget_focus->type == CURSES_TEXTBOX) { 936 curses_textbox_insert_char(cf->widget_focus, (char)key); 937 } else { 938 struct curses_widget *cw; 939 940 for (cw = cf->widget_head; cw != NULL; cw = cw->next) { 941 if (toupper(key) == cw->accel) { 942 /* 943 * To just refocus: 944 */ 945 /* 946 cf->widget_focus = cw; 947 curses_form_widget_ensure_visible(cw); 948 curses_form_draw(cf); 949 curses_form_refresh(cf); 950 */ 951 /* 952 * To actually activate: 953 */ 954 switch (curses_widget_click(cw)) { 955 case -1: 956 beep(); 957 break; 958 case 0: 959 break; 960 case 1: 961 return(cw); 962 } 963 964 break; 965 } 966 } 967 #ifdef DEBUG 968 curses_debug_key(key); 969 #endif 970 beep(); 971 } 972 break; 973 } 974 } 975 } 976 977 /*** GENERIC CALLBACKS ***/ 978 979 /* 980 * Callback to give to curses_button_set_click_cb, for buttons 981 * that simply close the form they are in when they are clicked. 982 * These usually map to dfui actions. 983 */ 984 int 985 cb_click_close_form(struct curses_widget *w __unused) 986 { 987 return(1); 988 } 989