1 /* 2 * $Id: buttons.c,v 1.106 2021/01/17 17:03:16 tom Exp $ 3 * 4 * buttons.c -- draw buttons, e.g., OK/Cancel 5 * 6 * Copyright 2000-2020,2021 Thomas E. Dickey 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License, version 2.1 10 * as published by the Free Software Foundation. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program; if not, write to 19 * Free Software Foundation, Inc. 20 * 51 Franklin St., Fifth Floor 21 * Boston, MA 02110, USA. 22 */ 23 24 #include <dialog.h> 25 #include <dlg_keys.h> 26 27 #ifdef NEED_WCHAR_H 28 #include <wchar.h> 29 #endif 30 31 #define MIN_BUTTON (-dialog_state.visit_cols) 32 #define CHR_BUTTON (!dialog_state.plain_buttons) 33 34 static void 35 center_label(char *buffer, int longest, const char *label) 36 { 37 int len = dlg_count_columns(label); 38 int right = 0; 39 40 *buffer = 0; 41 if (len < longest) { 42 int left = (longest - len) / 2; 43 right = (longest - len - left); 44 if (left > 0) 45 sprintf(buffer, "%*s", left, " "); 46 } 47 strcat(buffer, label); 48 if (right > 0) 49 sprintf(buffer + strlen(buffer), "%*s", right, " "); 50 } 51 52 /* 53 * Parse a multibyte character out of the string, set it past the parsed 54 * character. 55 */ 56 static int 57 string_to_char(const char **stringp) 58 { 59 int result; 60 #ifdef USE_WIDE_CURSES 61 const char *string = *stringp; 62 size_t have = strlen(string); 63 size_t len; 64 wchar_t cmp2[2]; 65 mbstate_t state; 66 67 memset(&state, 0, sizeof(state)); 68 len = mbrlen(string, have, &state); 69 70 if ((int) len > 0 && len <= have) { 71 size_t check; 72 73 memset(&state, 0, sizeof(state)); 74 memset(cmp2, 0, sizeof(cmp2)); 75 check = mbrtowc(cmp2, string, len, &state); 76 if ((int) check <= 0) 77 cmp2[0] = 0; 78 *stringp += len; 79 } else { 80 cmp2[0] = UCH(*string); 81 *stringp += 1; 82 } 83 result = cmp2[0]; 84 #else 85 const char *string = *stringp; 86 result = UCH(*string); 87 *stringp += 1; 88 #endif 89 return result; 90 } 91 92 static size_t 93 count_labels(const char **labels) 94 { 95 size_t result = 0; 96 if (labels != 0) { 97 while (*labels++ != 0) { 98 ++result; 99 } 100 } 101 return result; 102 } 103 104 /* 105 * Check if the latest key should be added to the hotkey list. 106 */ 107 static int 108 was_hotkey(int this_key, int *used_keys, size_t next) 109 { 110 int result = FALSE; 111 112 if (next != 0) { 113 size_t n; 114 for (n = 0; n < next; ++n) { 115 if (used_keys[n] == this_key) { 116 result = TRUE; 117 break; 118 } 119 } 120 } 121 return result; 122 } 123 124 /* 125 * Determine the hot-keys for a set of button-labels. Normally these are 126 * the first uppercase character in each label. However, if more than one 127 * button has the same first-uppercase, then we will (attempt to) look for 128 * an alternate. 129 * 130 * This allocates data which must be freed by the caller. 131 */ 132 static int * 133 get_hotkeys(const char **labels) 134 { 135 int *result = 0; 136 size_t count = count_labels(labels); 137 138 if ((result = dlg_calloc(int, count + 1)) != 0) { 139 size_t n; 140 141 for (n = 0; n < count; ++n) { 142 const char *label = labels[n]; 143 const int *indx = dlg_index_wchars(label); 144 int limit = dlg_count_wchars(label); 145 int i; 146 147 for (i = 0; i < limit; ++i) { 148 int first = indx[i]; 149 int check = UCH(label[first]); 150 #ifdef USE_WIDE_CURSES 151 int last = indx[i + 1]; 152 if ((last - first) != 1) { 153 const char *temp = (label + first); 154 check = string_to_char(&temp); 155 } 156 #endif 157 if (dlg_isupper(check) && !was_hotkey(check, result, n)) { 158 result[n] = check; 159 break; 160 } 161 } 162 } 163 } 164 return result; 165 } 166 167 typedef enum { 168 sFIND_KEY = 0 169 ,sHAVE_KEY = 1 170 ,sHAD_KEY = 2 171 } HOTKEY; 172 173 /* 174 * Print a button 175 */ 176 static void 177 print_button(WINDOW *win, char *label, int hotkey, int y, int x, int selected) 178 { 179 int i; 180 HOTKEY state = sFIND_KEY; 181 const int *indx = dlg_index_wchars(label); 182 int limit = dlg_count_wchars(label); 183 chtype key_attr = (selected 184 ? button_key_active_attr 185 : button_key_inactive_attr); 186 chtype label_attr = (selected 187 ? button_label_active_attr 188 : button_label_inactive_attr); 189 190 (void) wmove(win, y, x); 191 dlg_attrset(win, selected 192 ? button_active_attr 193 : button_inactive_attr); 194 (void) waddstr(win, "<"); 195 dlg_attrset(win, label_attr); 196 for (i = 0; i < limit; ++i) { 197 int check; 198 int first = indx[i]; 199 int last = indx[i + 1]; 200 201 switch (state) { 202 case sFIND_KEY: 203 check = UCH(label[first]); 204 #ifdef USE_WIDE_CURSES 205 if ((last - first) != 1) { 206 const char *temp = (label + first); 207 check = string_to_char(&temp); 208 } 209 #endif 210 if (check == hotkey) { 211 dlg_attrset(win, key_attr); 212 state = sHAVE_KEY; 213 } 214 break; 215 case sHAVE_KEY: 216 dlg_attrset(win, label_attr); 217 state = sHAD_KEY; 218 break; 219 default: 220 break; 221 } 222 waddnstr(win, label + first, last - first); 223 } 224 dlg_attrset(win, selected 225 ? button_active_attr 226 : button_inactive_attr); 227 (void) waddstr(win, ">"); 228 if (!dialog_vars.cursor_off_label) { 229 (void) wmove(win, y, x + ((int) (strspn) (label, " ")) + 1); 230 } 231 } 232 233 /* 234 * Count the buttons in the list. 235 */ 236 int 237 dlg_button_count(const char **labels) 238 { 239 int result = 0; 240 while (*labels++ != 0) 241 ++result; 242 return result; 243 } 244 245 /* 246 * Compute the size of the button array in columns. Return the total number of 247 * columns in *length, and the longest button's columns in *longest 248 */ 249 void 250 dlg_button_sizes(const char **labels, 251 int vertical, 252 int *longest, 253 int *length) 254 { 255 int n; 256 257 *length = 0; 258 *longest = 0; 259 for (n = 0; labels[n] != 0; n++) { 260 if (vertical) { 261 *length += 1; 262 *longest = 1; 263 } else { 264 int len = dlg_count_columns(labels[n]); 265 if (len > *longest) 266 *longest = len; 267 *length += len; 268 } 269 } 270 /* 271 * If we can, make all of the buttons the same size. This is only optional 272 * for buttons laid out horizontally. 273 */ 274 if (*longest < 6 - (*longest & 1)) 275 *longest = 6 - (*longest & 1); 276 if (!vertical) 277 *length = *longest * n; 278 } 279 280 /* 281 * Compute the size of the button array. 282 */ 283 int 284 dlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step) 285 { 286 int count = dlg_button_count(labels); 287 int longest; 288 int length; 289 int result; 290 291 *margin = 0; 292 if (count != 0) { 293 int unused; 294 int used; 295 296 dlg_button_sizes(labels, FALSE, &longest, &length); 297 used = (length + (count * 2)); 298 unused = limit - used; 299 300 if ((*gap = unused / (count + 3)) <= 0) { 301 if ((*gap = unused / (count + 1)) <= 0) 302 *gap = 1; 303 *margin = *gap; 304 } else { 305 *margin = *gap * 2; 306 } 307 *step = *gap + (used + count - 1) / count; 308 result = (*gap > 0) && (unused >= 0); 309 } else { 310 result = 0; 311 } 312 return result; 313 } 314 315 /* 316 * Make sure there is enough space for the buttons 317 */ 318 void 319 dlg_button_layout(const char **labels, int *limit) 320 { 321 int gap, margin, step; 322 323 if (labels != 0 && dlg_button_count(labels)) { 324 int width = 1; 325 326 while (!dlg_button_x_step(labels, width, &gap, &margin, &step)) 327 ++width; 328 width += (4 * MARGIN); 329 if (width > COLS) 330 width = COLS; 331 if (width > *limit) 332 *limit = width; 333 } 334 } 335 336 /* 337 * Print a list of buttons at the given position. 338 */ 339 void 340 dlg_draw_buttons(WINDOW *win, 341 int y, int x, 342 const char **labels, 343 int selected, 344 int vertical, 345 int limit) 346 { 347 chtype save = dlg_get_attrs(win); 348 int step = 0; 349 int length; 350 int longest; 351 int final_x; 352 int final_y; 353 int gap; 354 int margin; 355 size_t need; 356 357 dlg_mouse_setbase(getbegx(win), getbegy(win)); 358 359 getyx(win, final_y, final_x); 360 361 dlg_button_sizes(labels, vertical, &longest, &length); 362 363 if (vertical) { 364 y += 1; 365 step = 1; 366 } else { 367 dlg_button_x_step(labels, limit, &gap, &margin, &step); 368 x += margin; 369 } 370 371 /* 372 * Allocate a buffer big enough for any label. 373 */ 374 need = (size_t) longest; 375 if (need != 0) { 376 char *buffer; 377 int n; 378 int *hotkeys = get_hotkeys(labels); 379 380 assert_ptr(hotkeys, "dlg_draw_buttons"); 381 382 for (n = 0; labels[n] != 0; ++n) { 383 need += strlen(labels[n]) + 1; 384 } 385 buffer = dlg_malloc(char, need); 386 assert_ptr(buffer, "dlg_draw_buttons"); 387 388 /* 389 * Draw the labels. 390 */ 391 for (n = 0; labels[n] != 0; n++) { 392 center_label(buffer, longest, labels[n]); 393 mouse_mkbutton(y, x, dlg_count_columns(buffer), n); 394 print_button(win, buffer, 395 CHR_BUTTON ? hotkeys[n] : -1, 396 y, x, 397 (selected == n) || (n == 0 && selected < 0)); 398 if (selected == n) 399 getyx(win, final_y, final_x); 400 401 if (vertical) { 402 if ((y += step) > limit) 403 break; 404 } else { 405 if ((x += step) > limit) 406 break; 407 } 408 } 409 (void) wmove(win, final_y, final_x); 410 wrefresh(win); 411 dlg_attrset(win, save); 412 free(buffer); 413 free(hotkeys); 414 } 415 } 416 417 /* 418 * Match a given character against the beginning of the string, ignoring case 419 * of the given character. The matching string must begin with an uppercase 420 * character. 421 */ 422 int 423 dlg_match_char(int ch, const char *string) 424 { 425 if (!dialog_vars.no_hot_list) { 426 if (string != 0) { 427 int cmp2 = string_to_char(&string); 428 #ifdef USE_WIDE_CURSES 429 wint_t cmp1 = dlg_toupper(ch); 430 if (cmp2 != 0 && (wchar_t) cmp1 == (wchar_t) dlg_toupper(cmp2)) { 431 return TRUE; 432 } 433 #else 434 if (ch > 0 && ch < 256) { 435 if (dlg_toupper(ch) == dlg_toupper(cmp2)) 436 return TRUE; 437 } 438 #endif 439 } 440 } 441 return FALSE; 442 } 443 444 /* 445 * Find the first uppercase character in the label, which we may use for an 446 * abbreviation. 447 */ 448 int 449 dlg_button_to_char(const char *label) 450 { 451 int cmp = -1; 452 453 while (*label != 0) { 454 int ch = string_to_char(&label); 455 if (dlg_isupper(ch)) { 456 cmp = ch; 457 break; 458 } 459 } 460 return cmp; 461 } 462 463 /* 464 * Given a list of button labels, and a character which may be the abbreviation 465 * for one, find it, if it exists. An abbreviation will be the first character 466 * which happens to be capitalized in the label. 467 */ 468 int 469 dlg_char_to_button(int ch, const char **labels) 470 { 471 int result = DLG_EXIT_UNKNOWN; 472 473 if (labels != 0) { 474 int *hotkeys = get_hotkeys(labels); 475 476 ch = (int) dlg_toupper(dlg_last_getc()); 477 478 if (hotkeys != 0) { 479 int j; 480 481 for (j = 0; labels[j] != 0; ++j) { 482 if (ch == hotkeys[j]) { 483 dlg_flush_getc(); 484 result = j; 485 break; 486 } 487 } 488 free(hotkeys); 489 } 490 } 491 492 return result; 493 } 494 495 static const char * 496 my_yes_label(void) 497 { 498 return (dialog_vars.yes_label != NULL) 499 ? dialog_vars.yes_label 500 : _("Yes"); 501 } 502 503 static const char * 504 my_no_label(void) 505 { 506 return (dialog_vars.no_label != NULL) 507 ? dialog_vars.no_label 508 : _("No"); 509 } 510 511 static const char * 512 my_ok_label(void) 513 { 514 return (dialog_vars.ok_label != NULL) 515 ? dialog_vars.ok_label 516 : _("OK"); 517 } 518 519 static const char * 520 my_cancel_label(void) 521 { 522 return (dialog_vars.cancel_label != NULL) 523 ? dialog_vars.cancel_label 524 : _("Cancel"); 525 } 526 527 static const char * 528 my_exit_label(void) 529 { 530 return (dialog_vars.exit_label != NULL) 531 ? dialog_vars.exit_label 532 : _("EXIT"); 533 } 534 535 static const char * 536 my_extra_label(void) 537 { 538 return (dialog_vars.extra_label != NULL) 539 ? dialog_vars.extra_label 540 : _("Extra"); 541 } 542 543 static const char * 544 my_help_label(void) 545 { 546 return (dialog_vars.help_label != NULL) 547 ? dialog_vars.help_label 548 : _("Help"); 549 } 550 551 /* 552 * Return a list of button labels. 553 */ 554 const char ** 555 dlg_exit_label(void) 556 { 557 const char **result; 558 DIALOG_VARS save; 559 560 if (dialog_vars.extra_button) { 561 dlg_save_vars(&save); 562 dialog_vars.nocancel = TRUE; 563 result = dlg_ok_labels(); 564 dlg_restore_vars(&save); 565 } else { 566 static const char *labels[3]; 567 int n = 0; 568 569 if (!dialog_vars.nook) 570 labels[n++] = my_exit_label(); 571 if (dialog_vars.help_button) 572 labels[n++] = my_help_label(); 573 if (n == 0) 574 labels[n++] = my_exit_label(); 575 labels[n] = 0; 576 577 result = labels; 578 } 579 return result; 580 } 581 582 /* 583 * Map the given button index for dlg_exit_label() into our exit-code. 584 */ 585 int 586 dlg_exit_buttoncode(int button) 587 { 588 int result; 589 DIALOG_VARS save; 590 591 dlg_save_vars(&save); 592 dialog_vars.nocancel = TRUE; 593 594 result = dlg_ok_buttoncode(button); 595 596 dlg_restore_vars(&save); 597 598 return result; 599 } 600 601 static const char ** 602 finish_ok_label(const char **labels, int n) 603 { 604 if (n == 0) { 605 labels[n++] = my_ok_label(); 606 dialog_vars.nook = FALSE; 607 dlg_trace_msg("# ignore --nook, since at least one button is needed\n"); 608 } 609 610 labels[n] = NULL; 611 return labels; 612 } 613 614 /* 615 * Return a list of button labels for the OK (no Cancel) group, used in msgbox 616 * and progressbox. 617 */ 618 const char ** 619 dlg_ok_label(void) 620 { 621 static const char *labels[4]; 622 int n = 0; 623 624 if (!dialog_vars.nook) 625 labels[n++] = my_ok_label(); 626 if (dialog_vars.extra_button) 627 labels[n++] = my_extra_label(); 628 if (dialog_vars.help_button) 629 labels[n++] = my_help_label(); 630 631 return finish_ok_label(labels, n); 632 } 633 634 /* 635 * Return a list of button labels for the OK/Cancel group, used in most widgets 636 * that select an option or data. 637 */ 638 const char ** 639 dlg_ok_labels(void) 640 { 641 static const char *labels[5]; 642 int n = 0; 643 644 if (!dialog_vars.nook) 645 labels[n++] = my_ok_label(); 646 if (dialog_vars.extra_button) 647 labels[n++] = my_extra_label(); 648 if (!dialog_vars.nocancel) 649 labels[n++] = my_cancel_label(); 650 if (dialog_vars.help_button) 651 labels[n++] = my_help_label(); 652 653 return finish_ok_label(labels, n); 654 } 655 656 /* 657 * Map the given button index for dlg_ok_labels() into our exit-code 658 */ 659 int 660 dlg_ok_buttoncode(int button) 661 { 662 int result = DLG_EXIT_ERROR; 663 int n = !dialog_vars.nook; 664 665 if (!dialog_vars.nook && (button <= 0)) { 666 result = DLG_EXIT_OK; 667 } else if (dialog_vars.extra_button && (button == n++)) { 668 result = DLG_EXIT_EXTRA; 669 } else if (!dialog_vars.nocancel && (button == n++)) { 670 result = DLG_EXIT_CANCEL; 671 } else if (dialog_vars.help_button && (button == n)) { 672 result = DLG_EXIT_HELP; 673 } 674 DLG_TRACE(("# dlg_ok_buttoncode(%d) = %d:%s\n", 675 button, result, dlg_exitcode2s(result))); 676 return result; 677 } 678 679 /* 680 * Given that we're using dlg_ok_labels() to list buttons, find the next index 681 * in the list of buttons. The 'extra' parameter if negative provides a way to 682 * enumerate extra active areas on the widget. 683 */ 684 int 685 dlg_next_ok_buttonindex(int current, int extra) 686 { 687 int result = current + 1; 688 689 if (current >= 0 690 && dlg_ok_buttoncode(result) < 0) 691 result = extra; 692 return result; 693 } 694 695 /* 696 * Similarly, find the previous button index. 697 */ 698 int 699 dlg_prev_ok_buttonindex(int current, int extra) 700 { 701 int result = current - 1; 702 703 if (result < extra) { 704 for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) { 705 ; 706 } 707 } 708 return result; 709 } 710 711 /* 712 * Find the button-index for the "OK" or "Cancel" button, according to 713 * whether --defaultno is given. If --nocancel was given, we always return 714 * the index for the first button (usually "OK" unless --nook was used). 715 */ 716 int 717 dlg_defaultno_button(void) 718 { 719 int result = 0; 720 721 if (dialog_vars.defaultno && !dialog_vars.nocancel) { 722 while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL) 723 ++result; 724 } 725 DLG_TRACE(("# dlg_defaultno_button() = %d\n", result)); 726 return result; 727 } 728 729 /* 730 * Find the button-index for a button named with --default-button. If the 731 * option was not specified, or if the selected button does not exist, return 732 * the index of the first button (usually "OK" unless --nook was used). 733 */ 734 int 735 dlg_default_button(void) 736 { 737 int result = 0; 738 739 if (dialog_vars.default_button >= 0) { 740 int i, n; 741 742 for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) { 743 if (n == dialog_vars.default_button) { 744 result = i; 745 break; 746 } 747 } 748 } 749 DLG_TRACE(("# dlg_default_button() = %d\n", result)); 750 return result; 751 } 752 753 /* 754 * Return a list of buttons for Yes/No labels. 755 */ 756 const char ** 757 dlg_yes_labels(void) 758 { 759 const char **result; 760 761 if (dialog_vars.extra_button) { 762 result = dlg_ok_labels(); 763 } else { 764 static const char *labels[4]; 765 int n = 0; 766 767 labels[n++] = my_yes_label(); 768 labels[n++] = my_no_label(); 769 if (dialog_vars.help_button) 770 labels[n++] = my_help_label(); 771 labels[n] = 0; 772 773 result = labels; 774 } 775 776 return result; 777 } 778 779 /* 780 * Map the given button index for dlg_yes_labels() into our exit-code. 781 */ 782 int 783 dlg_yes_buttoncode(int button) 784 { 785 int result = DLG_EXIT_ERROR; 786 787 if (dialog_vars.extra_button) { 788 result = dlg_ok_buttoncode(button); 789 } else if (button == 0) { 790 result = DLG_EXIT_OK; 791 } else if (button == 1) { 792 result = DLG_EXIT_CANCEL; 793 } else if (button == 2 && dialog_vars.help_button) { 794 result = DLG_EXIT_HELP; 795 } 796 797 return result; 798 } 799 800 /* 801 * Return the next index in labels[]; 802 */ 803 int 804 dlg_next_button(const char **labels, int button) 805 { 806 if (button < -1) 807 button = -1; 808 809 if (labels[button + 1] != 0) { 810 ++button; 811 } else { 812 button = MIN_BUTTON; 813 } 814 return button; 815 } 816 817 /* 818 * Return the previous index in labels[]; 819 */ 820 int 821 dlg_prev_button(const char **labels, int button) 822 { 823 if (button > MIN_BUTTON) { 824 --button; 825 } else { 826 if (button < -1) 827 button = -1; 828 829 while (labels[button + 1] != 0) 830 ++button; 831 } 832 return button; 833 } 834