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 34static void 35center_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 */ 56static int 57string_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 92static size_t 93count_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 */ 107static int 108was_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 */ 132static int * 133get_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 167typedef enum { 168 sFIND_KEY = 0 169 ,sHAVE_KEY = 1 170 ,sHAD_KEY = 2 171} HOTKEY; 172 173/* 174 * Print a button 175 */ 176static void 177print_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 */ 236int 237dlg_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 */ 249void 250dlg_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 */ 283int 284dlg_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 */ 318void 319dlg_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 */ 339void 340dlg_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 */ 422int 423dlg_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 */ 448int 449dlg_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 */ 468int 469dlg_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 495static const char * 496my_yes_label(void) 497{ 498 return (dialog_vars.yes_label != NULL) 499 ? dialog_vars.yes_label 500 : _("Yes"); 501} 502 503static const char * 504my_no_label(void) 505{ 506 return (dialog_vars.no_label != NULL) 507 ? dialog_vars.no_label 508 : _("No"); 509} 510 511static const char * 512my_ok_label(void) 513{ 514 return (dialog_vars.ok_label != NULL) 515 ? dialog_vars.ok_label 516 : _("OK"); 517} 518 519static const char * 520my_cancel_label(void) 521{ 522 return (dialog_vars.cancel_label != NULL) 523 ? dialog_vars.cancel_label 524 : _("Cancel"); 525} 526 527static const char * 528my_exit_label(void) 529{ 530 return (dialog_vars.exit_label != NULL) 531 ? dialog_vars.exit_label 532 : _("EXIT"); 533} 534 535static const char * 536my_extra_label(void) 537{ 538 return (dialog_vars.extra_label != NULL) 539 ? dialog_vars.extra_label 540 : _("Extra"); 541} 542 543static const char * 544my_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 */ 554const char ** 555dlg_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 */ 585int 586dlg_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 601static const char ** 602finish_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 */ 618const char ** 619dlg_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 */ 638const char ** 639dlg_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 */ 659int 660dlg_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 */ 684int 685dlg_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 */ 698int 699dlg_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 */ 716int 717dlg_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 */ 734int 735dlg_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 */ 756const char ** 757dlg_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 */ 782int 783dlg_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 */ 803int 804dlg_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 */ 820int 821dlg_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