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 int done = 0; 247 int pos = 0; 248 char *line; 249 250 line = aura_malloc(width + 1, "descriptive line"); 251 while (!done) { 252 done = extract_wrapped_line(text, line, width, &pos); 253 dfui_debug("line = `%s', done = %d, width = %d, form width = %d : ", 254 line, done, width, cf->width); 255 curses_form_widget_add(cf, x, y++, 0, 256 CURSES_LABEL, line, 0, CURSES_WIDGET_WIDEN); 257 dfui_debug("now %d\n", cf->width); 258 } 259 free(line); 260 return(y); 261 } 262 263 void 264 curses_form_finalize(struct curses_form *cf) 265 { 266 if (cf->widget_head != NULL) { 267 cf->widget_focus = cf->widget_head; 268 curses_form_focus_skip_forward(cf); 269 cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2; 270 cf->want_y = cf->widget_focus->y; 271 } else { 272 cf->widget_focus = NULL; 273 } 274 275 cf->left = (xmax - cf->width) / 2; 276 cf->top = (ymax - cf->height) / 2; 277 278 /* 279 * Set the internal width and height. 280 */ 281 282 cf->int_width = cf->width; 283 cf->int_height = cf->height; 284 285 /* 286 * Limit form size to physical screen dimensions. 287 */ 288 if (cf->width > (xmax - 2)) { 289 cf->width = xmax - 2; 290 cf->left = 1; 291 } 292 if (cf->height > (ymax - 2)) { 293 cf->height = ymax - 2; 294 cf->top = 1; 295 } 296 if (cf->top < 1) 297 cf->top = 1; 298 if (cf->left < 1) 299 cf->left = 1; 300 301 cf->win = newwin(cf->height + 2, cf->width + 2, cf->top - 1, cf->left - 1); 302 if (cf->win == NULL) 303 fprintf(stderr, "Could not allocate %dx%d window @ %d,%d\n", 304 cf->width + 2, cf->height + 2, cf->left - 1, cf->top - 1); 305 306 cf->pan = new_panel(cf->win); 307 } 308 309 /* 310 * Render the given form (and all of the widgets it contains) in the 311 * curses backing store. Does not cause the form to be displayed. 312 */ 313 void 314 curses_form_draw(struct curses_form *cf) 315 { 316 struct curses_widget *w; 317 float sb_factor = 0.0; 318 size_t sb_off = 0, sb_size = 0; 319 320 curses_colors_set(cf->win, CURSES_COLORS_NORMAL); 321 curses_window_blank(cf->win); 322 323 curses_colors_set(cf->win, CURSES_COLORS_BORDER); 324 /* draw_frame(cf->left - 1, cf->top - 1, cf->width + 2, cf->height + 2); */ 325 wborder(cf->win, 0, 0, 0, 0, 0, 0, 0, 0); 326 327 /* 328 * If the internal dimensions of the form exceed the physical 329 * dimensions, draw scrollbar(s) as appropriate. 330 */ 331 if (cf->int_height > cf->height) { 332 sb_factor = (float)cf->height / (float)cf->int_height; 333 sb_size = cf->height * sb_factor; 334 if (sb_size == 0) sb_size = 1; 335 sb_off = cf->y_offset * sb_factor; 336 curses_colors_set(cf->win, CURSES_COLORS_SCROLLAREA); 337 mvwvline(cf->win, 1, cf->width + 1, ACS_CKBOARD, cf->height); 338 curses_colors_set(cf->win, CURSES_COLORS_SCROLLBAR); 339 mvwvline(cf->win, 1 + sb_off, cf->width + 1, ACS_BLOCK, sb_size); 340 } 341 342 if (cf->int_width > cf->width) { 343 sb_factor = (float)cf->width / (float)cf->int_width; 344 sb_size = cf->width * sb_factor; 345 if (sb_size == 0) sb_size = 1; 346 sb_off = cf->x_offset * sb_factor; 347 curses_colors_set(cf->win, CURSES_COLORS_SCROLLAREA); 348 mvwhline(cf->win, cf->height + 1, 1, ACS_CKBOARD, cf->width); 349 curses_colors_set(cf->win, CURSES_COLORS_SCROLLBAR); 350 mvwhline(cf->win, cf->height + 1, 1 + sb_off, ACS_BLOCK, sb_size); 351 } 352 353 curses_colors_set(cf->win, CURSES_COLORS_BORDER); 354 355 /* 356 * Render the title bar text. 357 */ 358 wmove(cf->win, 0, (cf->width - strlen(cf->title)) / 2 - 1); 359 waddch(cf->win, ACS_RTEE); 360 waddch(cf->win, ' '); 361 curses_colors_set(cf->win, CURSES_COLORS_FORMTITLE); 362 waddstr(cf->win, cf->title); 363 curses_colors_set(cf->win, CURSES_COLORS_BORDER); 364 waddch(cf->win, ' '); 365 waddch(cf->win, ACS_LTEE); 366 367 /* 368 * Render a "how to get help" reminder. 369 */ 370 if (cf->help_text != NULL) { 371 static const char *help_msg = "Press F1 for Help"; 372 373 wmove(cf->win, cf->height + 1, 374 (cf->width - strlen(help_msg)) / 2 - 1); 375 waddch(cf->win, ACS_RTEE); 376 waddch(cf->win, ' '); 377 curses_colors_set(cf->win, CURSES_COLORS_FORMTITLE); 378 waddstr(cf->win, help_msg); 379 curses_colors_set(cf->win, CURSES_COLORS_BORDER); 380 waddch(cf->win, ' '); 381 waddch(cf->win, ACS_LTEE); 382 } 383 384 /* 385 * Render the widgets. 386 */ 387 for (w = cf->widget_head; w != NULL; w = w->next) { 388 curses_widget_draw(w); 389 } 390 391 /* to put the cursor there */ 392 curses_widget_draw_tooltip(cf->widget_focus); 393 curses_widget_draw(cf->widget_focus); 394 } 395 396 /* 397 * Cause the given form to be displayed (if it was not displayed previously) 398 * or refreshed (if it was previously displayed.) Passing NULL to this 399 * function causes all visible forms to be refreshed. 400 * 401 * (Implementation note: the argument is actually irrelevant - all visible 402 * forms will be refreshed when any form is displayed or refreshed - but 403 * client code should not rely on this behaviour.) 404 */ 405 void 406 curses_form_refresh(struct curses_form *cf __unused) 407 { 408 update_panels(); 409 doupdate(); 410 } 411 412 void 413 curses_form_focus_skip_forward(struct curses_form *cf) 414 { 415 while (!curses_widget_can_take_focus(cf->widget_focus)) { 416 cf->widget_focus = cf->widget_focus->next; 417 if (cf->widget_focus == NULL) 418 cf->widget_focus = cf->widget_head; 419 } 420 curses_form_widget_ensure_visible(cf->widget_focus); 421 } 422 423 void 424 curses_form_focus_skip_backward(struct curses_form *cf) 425 { 426 while (!curses_widget_can_take_focus(cf->widget_focus)) { 427 cf->widget_focus = cf->widget_focus->prev; 428 if (cf->widget_focus == NULL) 429 cf->widget_focus = cf->widget_tail; 430 } 431 curses_form_widget_ensure_visible(cf->widget_focus); 432 } 433 434 void 435 curses_form_advance(struct curses_form *cf) 436 { 437 struct curses_widget *w; 438 439 w = cf->widget_focus; 440 cf->widget_focus = cf->widget_focus->next; 441 if (cf->widget_focus == NULL) 442 cf->widget_focus = cf->widget_head; 443 curses_form_focus_skip_forward(cf); 444 cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2; 445 cf->want_y = cf->widget_focus->y; 446 curses_widget_draw(w); 447 curses_widget_draw_tooltip(cf->widget_focus); 448 curses_widget_draw(cf->widget_focus); 449 curses_form_refresh(cf); 450 #ifdef DEBUG 451 curses_debug_int(cf->widget_focus->user_id); 452 #endif 453 } 454 455 void 456 curses_form_retreat(struct curses_form *cf) 457 { 458 struct curses_widget *w; 459 460 w = cf->widget_focus; 461 cf->widget_focus = cf->widget_focus->prev; 462 if (cf->widget_focus == NULL) 463 cf->widget_focus = cf->widget_tail; 464 curses_form_focus_skip_backward(cf); 465 cf->want_x = cf->widget_focus->x + cf->widget_focus->width / 2; 466 cf->want_y = cf->widget_focus->y; 467 curses_widget_draw(w); 468 curses_widget_draw_tooltip(cf->widget_focus); 469 curses_widget_draw(cf->widget_focus); 470 curses_form_refresh(cf); 471 #ifdef DEBUG 472 curses_debug_int(cf->widget_focus->user_id); 473 #endif 474 } 475 476 /* 477 * Returns the widget at (x, y) within a form, or NULL if 478 * there is no widget at that location. 479 */ 480 struct curses_widget * 481 curses_form_widget_at(struct curses_form *cf, unsigned int x, unsigned int y) 482 { 483 struct curses_widget *w; 484 485 for (w = cf->widget_head; w != NULL; w = w->next) { 486 if (y == w->y && x >= w->x && x <= (w->x + w->width)) 487 return(w); 488 } 489 490 return(NULL); 491 } 492 493 /* 494 * Returns the first (focusable) widget on 495 * the topmost row of the form. 496 */ 497 int 498 curses_form_widget_first_row(struct curses_form *cf) 499 { 500 struct curses_widget *w; 501 502 for (w = cf->widget_head; w != NULL; w = w->next) { 503 if (curses_widget_can_take_focus(w)) 504 return(w->y); 505 } 506 507 return(0); 508 } 509 510 /* 511 * Returns the first (focusable) widget on 512 * the bottommost row of the form. 513 */ 514 int 515 curses_form_widget_last_row(struct curses_form *cf) 516 { 517 struct curses_widget *w; 518 unsigned int best_y = 0; 519 520 for (w = cf->widget_head; w != NULL; w = w->next) { 521 if (curses_widget_can_take_focus(w) && w->y > best_y) { 522 best_y = w->y; 523 } 524 } 525 526 return(best_y); 527 } 528 529 /* 530 * Returns the first (focusable) widget on row y. 531 */ 532 struct curses_widget * 533 curses_form_widget_first_on_row(struct curses_form *cf, unsigned int y) 534 { 535 struct curses_widget *w; 536 537 for (w = cf->widget_head; w != NULL; w = w->next) { 538 if (curses_widget_can_take_focus(w) && y == w->y) 539 return(w); 540 } 541 542 return(NULL); 543 } 544 545 /* 546 * Returns the (focusable) widget on row y closest to x. 547 */ 548 struct curses_widget * 549 curses_form_widget_closest_on_row(struct curses_form *cf, 550 unsigned int x, unsigned int y) 551 { 552 struct curses_widget *w, *best = NULL; 553 int dist, best_dist = 999; 554 555 w = curses_form_widget_first_on_row(cf, y); 556 if (w == NULL) 557 return(NULL); 558 559 for (best = w; w != NULL && w->y == y; w = w->next) { 560 if (!curses_widget_can_take_focus(w)) 561 continue; 562 dist = (w->x + w->width / 2) - x; 563 if (dist < 0) dist *= -1; 564 if (dist < best_dist) { 565 best_dist = dist; 566 best = w; 567 } 568 } 569 570 return(best); 571 } 572 573 /* 574 * Returns the number of (focusable) widgets with y values less than 575 * (above) the given widget. 576 */ 577 int 578 curses_form_widget_count_above(struct curses_form *cf, 579 struct curses_widget *w) 580 { 581 struct curses_widget *lw; 582 int count = 0; 583 584 for (lw = cf->widget_head; lw != NULL; lw = lw->next) { 585 if (curses_widget_can_take_focus(lw) && lw->y < w->y) 586 count++; 587 } 588 589 return(count); 590 } 591 592 /* 593 * Returns the number of (focusable) widgets with y values greater than 594 * (below) the given widget. 595 */ 596 int 597 curses_form_widget_count_below(struct curses_form *cf, 598 struct curses_widget *w) 599 { 600 struct curses_widget *lw; 601 int count = 0; 602 603 for (lw = cf->widget_head; lw != NULL; lw = lw->next) { 604 if (curses_widget_can_take_focus(lw) && lw->y > w->y) 605 count++; 606 } 607 608 return(count); 609 } 610 611 /* 612 * Move to the next widget whose y is greater than the 613 * current want_y, and whose x is closest to want_x. 614 */ 615 void 616 curses_form_advance_row(struct curses_form *cf) 617 { 618 struct curses_widget *w, *c; 619 int wy; 620 621 w = cf->widget_focus; 622 if (curses_form_widget_count_below(cf, w) > 0) { 623 wy = cf->want_y + 1; 624 } else { 625 wy = curses_form_widget_first_row(cf); 626 } 627 do { 628 c = curses_form_widget_closest_on_row(cf, 629 cf->want_x, wy++); 630 } while (c == NULL); 631 632 cf->widget_focus = c; 633 curses_form_focus_skip_forward(cf); 634 cf->want_y = cf->widget_focus->y; 635 curses_widget_draw(w); 636 curses_widget_draw_tooltip(cf->widget_focus); 637 curses_widget_draw(cf->widget_focus); 638 curses_form_refresh(cf); 639 } 640 641 /* 642 * Move to the next widget whose y is less than the 643 * current want_y, and whose x is closest to want_x. 644 */ 645 void 646 curses_form_retreat_row(struct curses_form *cf) 647 { 648 struct curses_widget *w, *c; 649 int wy; 650 651 w = cf->widget_focus; 652 if (curses_form_widget_count_above(cf, w) > 0) { 653 wy = cf->want_y - 1; 654 } else { 655 wy = curses_form_widget_last_row(cf); 656 } 657 do { 658 c = curses_form_widget_closest_on_row(cf, 659 cf->want_x, wy--); 660 } while (c == NULL); 661 662 cf->widget_focus = c; 663 curses_form_focus_skip_backward(cf); 664 cf->want_y = cf->widget_focus->y; 665 curses_widget_draw(w); 666 curses_widget_draw_tooltip(cf->widget_focus); 667 curses_widget_draw(cf->widget_focus); 668 curses_form_refresh(cf); 669 } 670 671 void 672 curses_form_scroll_to(struct curses_form *cf, 673 unsigned int x_off, unsigned int y_off) 674 { 675 cf->x_offset = x_off; 676 cf->y_offset = y_off; 677 } 678 679 void 680 curses_form_scroll_delta(struct curses_form *cf, int dx, int dy) 681 { 682 unsigned int x_off, y_off; 683 684 if (dx < 0 && (unsigned int)-dx > cf->x_offset) { 685 x_off = 0; 686 } else { 687 x_off = cf->x_offset + dx; 688 } 689 if (x_off > (cf->int_width - cf->width)) 690 x_off = cf->int_width - cf->width; 691 692 if (dy < 0 && (unsigned int)-dy > cf->y_offset) { 693 y_off = 0; 694 } else { 695 y_off = cf->y_offset + dy; 696 } 697 if (y_off > (cf->int_height - cf->height)) 698 y_off = cf->int_height - cf->height; 699 700 curses_form_scroll_to(cf, x_off, y_off); 701 } 702 703 static void 704 curses_form_refocus_after_scroll(struct curses_form *cf, int dx, int dy) 705 { 706 struct curses_widget *w; 707 708 w = curses_form_widget_closest_on_row(cf, 709 cf->widget_focus->x + dx, cf->widget_focus->y + dy); 710 711 if (w != NULL) { 712 cf->widget_focus = w; 713 cf->want_x = w->x + w->width / 2; 714 cf->want_y = w->y; 715 } 716 } 717 718 int 719 curses_form_widget_is_visible(struct curses_widget *w) 720 { 721 unsigned int wy; 722 723 wy = w->y + 1 - w->form->y_offset; 724 725 if (wy < 1 || wy > w->form->height) 726 return(0); 727 728 return(1); 729 } 730 731 void 732 curses_form_widget_ensure_visible(struct curses_widget *w) 733 { 734 unsigned int wy; 735 int dx = 0, dy = 0; 736 737 /* 738 * If a textbox's offset is such that we can't see 739 * the cursor inside, adjust it. 740 */ 741 if (w->type == CURSES_TEXTBOX) { 742 if (w->curpos - w->offset >= w->width - 2) 743 w->offset = w->curpos - (w->width - 3); 744 if (w->offset > w->curpos) 745 w->offset = w->curpos; 746 } 747 748 if (curses_form_widget_is_visible(w)) 749 return; 750 751 wy = w->y + 1 - w->form->y_offset; 752 753 if (wy < 1) 754 dy = -1 * (1 - wy); 755 else if (wy > w->form->height) 756 dy = (wy - w->form->height); 757 758 curses_form_scroll_delta(w->form, dx, dy); 759 curses_form_draw(w->form); 760 curses_form_refresh(w->form); 761 } 762 763 static void 764 curses_form_show_help(const char *text) 765 { 766 struct curses_form *cf; 767 struct curses_widget *w; 768 769 cf = curses_form_new(_("Help")); 770 771 cf->height = curses_form_descriptive_labels_add(cf, text, 1, 1, 72); 772 cf->height += 1; 773 w = curses_form_widget_add(cf, 0, cf->height++, 0, 774 CURSES_BUTTON, _("OK"), 0, CURSES_WIDGET_WIDEN); 775 curses_widget_set_click_cb(w, cb_click_close_form); 776 777 curses_form_finalize(cf); 778 779 curses_form_draw(cf); 780 curses_form_refresh(cf); 781 curses_form_frob(cf); 782 curses_form_free(cf); 783 } 784 785 #define CTRL(c) (char)(c - 'a' + 1) 786 787 struct curses_widget * 788 curses_form_frob(struct curses_form *cf) 789 { 790 int key; 791 792 flushinp(); 793 for (;;) { 794 key = getch(); 795 switch(key) { 796 case KEY_DOWN: 797 case CTRL('n'): 798 curses_form_advance_row(cf); 799 break; 800 case KEY_UP: 801 case CTRL('p'): 802 curses_form_retreat_row(cf); 803 break; 804 case '\t': 805 curses_form_advance(cf); 806 break; 807 case KEY_RIGHT: 808 case CTRL('f'): 809 if (cf->widget_focus->type == CURSES_TEXTBOX) { 810 if (!curses_textbox_advance_char(cf->widget_focus)) 811 curses_form_advance(cf); 812 } else 813 curses_form_advance(cf); 814 break; 815 case KEY_LEFT: 816 case CTRL('b'): 817 if (cf->widget_focus->type == CURSES_TEXTBOX) { 818 if (!curses_textbox_retreat_char(cf->widget_focus)) 819 curses_form_retreat(cf); 820 } else 821 curses_form_retreat(cf); 822 break; 823 case '\n': 824 case '\r': 825 if (cf->widget_focus->type == CURSES_TEXTBOX) { 826 switch (curses_widget_click(cf->widget_focus)) { 827 case -1: 828 curses_form_advance(cf); 829 break; 830 case 0: 831 break; 832 case 1: 833 /* this would be pretty rare */ 834 return(cf->widget_focus); 835 } 836 } else if (cf->widget_focus->type == CURSES_BUTTON) { 837 switch (curses_widget_click(cf->widget_focus)) { 838 case -1: 839 beep(); 840 break; 841 case 0: 842 break; 843 case 1: 844 return(cf->widget_focus); 845 } 846 } else if (cf->widget_focus->type == CURSES_CHECKBOX) { 847 curses_checkbox_toggle(cf->widget_focus); 848 } else { 849 beep(); 850 } 851 break; 852 case '\b': 853 case KEY_BACKSPACE: 854 case 127: /* why is this not KEY_BACKSPACE on xterm?? */ 855 if (cf->widget_focus->type == CURSES_TEXTBOX) { 856 curses_textbox_backspace_char(cf->widget_focus); 857 } else { 858 beep(); 859 } 860 break; 861 case KEY_DC: 862 case CTRL('k'): 863 if (cf->widget_focus->type == CURSES_TEXTBOX) { 864 curses_textbox_delete_char(cf->widget_focus); 865 } else { 866 beep(); 867 } 868 break; 869 case KEY_HOME: 870 case CTRL('a'): 871 if (cf->widget_focus->type == CURSES_TEXTBOX) { 872 curses_textbox_home(cf->widget_focus); 873 } else { 874 beep(); 875 } 876 break; 877 case KEY_END: 878 case CTRL('e'): 879 if (cf->widget_focus->type == CURSES_TEXTBOX) { 880 curses_textbox_end(cf->widget_focus); 881 } else { 882 beep(); 883 } 884 break; 885 case KEY_NPAGE: 886 case CTRL('g'): 887 curses_form_scroll_delta(cf, 0, cf->height - 1); 888 curses_form_refocus_after_scroll(cf, 0, cf->height - 1); 889 curses_form_draw(cf); 890 curses_form_refresh(cf); 891 break; 892 case KEY_PPAGE: 893 case CTRL('t'): 894 curses_form_scroll_delta(cf, 0, -1 * (cf->height - 1)); 895 curses_form_refocus_after_scroll(cf, 0, -1 * (cf->height - 1)); 896 curses_form_draw(cf); 897 curses_form_refresh(cf); 898 break; 899 case ' ': 900 if (cf->widget_focus->type == CURSES_TEXTBOX) { 901 /* XXX if non-editable, click it */ 902 curses_textbox_insert_char(cf->widget_focus, ' '); 903 } else if (cf->widget_focus->type == CURSES_BUTTON) { 904 switch (curses_widget_click(cf->widget_focus)) { 905 case -1: 906 beep(); 907 break; 908 case 0: 909 break; 910 case 1: 911 return(cf->widget_focus); 912 } 913 } else if (cf->widget_focus->type == CURSES_CHECKBOX) { 914 curses_checkbox_toggle(cf->widget_focus); 915 } else { 916 beep(); 917 } 918 break; 919 case KEY_F(1): /* why does this not work in xterm??? */ 920 case CTRL('w'): 921 if (cf->help_text != NULL) { 922 curses_form_show_help(cf->help_text); 923 curses_form_refresh(cf); 924 } 925 break; 926 case KEY_F(10): 927 case CTRL('l'): 928 redrawwin(stdscr); 929 curses_form_refresh(NULL); 930 break; 931 default: 932 if (isprint(key) && cf->widget_focus->type == CURSES_TEXTBOX) { 933 curses_textbox_insert_char(cf->widget_focus, (char)key); 934 } else { 935 struct curses_widget *cw; 936 937 for (cw = cf->widget_head; cw != NULL; cw = cw->next) { 938 if (toupper(key) == cw->accel) { 939 /* 940 * To just refocus: 941 */ 942 /* 943 cf->widget_focus = cw; 944 curses_form_widget_ensure_visible(cw); 945 curses_form_draw(cf); 946 curses_form_refresh(cf); 947 */ 948 /* 949 * To actually activate: 950 */ 951 switch (curses_widget_click(cw)) { 952 case -1: 953 beep(); 954 break; 955 case 0: 956 break; 957 case 1: 958 return(cw); 959 } 960 961 break; 962 } 963 } 964 #ifdef DEBUG 965 curses_debug_key(key); 966 #endif 967 beep(); 968 } 969 break; 970 } 971 } 972 } 973 974 /*** GENERIC CALLBACKS ***/ 975 976 /* 977 * Callback to give to curses_button_set_click_cb, for buttons 978 * that simply close the form they are in when they are clicked. 979 * These usually map to dfui actions. 980 */ 981 int 982 cb_click_close_form(struct curses_widget *w __unused) 983 { 984 return(1); 985 } 986