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