1 /* 2 * $Id: buttons.c,v 1.101 2019/08/05 20:46:00 tom Exp $ 3 * 4 * buttons.c -- draw buttons, e.g., OK/Cancel 5 * 6 * Copyright 2000-2018,2019 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 (void) wmove(win, y, x + ((int) (strspn) (label, " ")) + 1); 229 } 230 231 /* 232 * Count the buttons in the list. 233 */ 234 int 235 dlg_button_count(const char **labels) 236 { 237 int result = 0; 238 while (*labels++ != 0) 239 ++result; 240 return result; 241 } 242 243 /* 244 * Compute the size of the button array in columns. Return the total number of 245 * columns in *length, and the longest button's columns in *longest 246 */ 247 void 248 dlg_button_sizes(const char **labels, 249 int vertical, 250 int *longest, 251 int *length) 252 { 253 int n; 254 255 *length = 0; 256 *longest = 0; 257 for (n = 0; labels[n] != 0; n++) { 258 if (vertical) { 259 *length += 1; 260 *longest = 1; 261 } else { 262 int len = dlg_count_columns(labels[n]); 263 if (len > *longest) 264 *longest = len; 265 *length += len; 266 } 267 } 268 /* 269 * If we can, make all of the buttons the same size. This is only optional 270 * for buttons laid out horizontally. 271 */ 272 if (*longest < 6 - (*longest & 1)) 273 *longest = 6 - (*longest & 1); 274 if (!vertical) 275 *length = *longest * n; 276 } 277 278 /* 279 * Compute the size of the button array. 280 */ 281 int 282 dlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step) 283 { 284 int count = dlg_button_count(labels); 285 int longest; 286 int length; 287 int result; 288 289 *margin = 0; 290 if (count != 0) { 291 int unused; 292 int used; 293 294 dlg_button_sizes(labels, FALSE, &longest, &length); 295 used = (length + (count * 2)); 296 unused = limit - used; 297 298 if ((*gap = unused / (count + 3)) <= 0) { 299 if ((*gap = unused / (count + 1)) <= 0) 300 *gap = 1; 301 *margin = *gap; 302 } else { 303 *margin = *gap * 2; 304 } 305 *step = *gap + (used + count - 1) / count; 306 result = (*gap > 0) && (unused >= 0); 307 } else { 308 result = 0; 309 } 310 return result; 311 } 312 313 /* 314 * Make sure there is enough space for the buttons 315 */ 316 void 317 dlg_button_layout(const char **labels, int *limit) 318 { 319 int gap, margin, step; 320 321 if (labels != 0 && dlg_button_count(labels)) { 322 int width = 1; 323 324 while (!dlg_button_x_step(labels, width, &gap, &margin, &step)) 325 ++width; 326 width += (4 * MARGIN); 327 if (width > COLS) 328 width = COLS; 329 if (width > *limit) 330 *limit = width; 331 } 332 } 333 334 /* 335 * Print a list of buttons at the given position. 336 */ 337 void 338 dlg_draw_buttons(WINDOW *win, 339 int y, int x, 340 const char **labels, 341 int selected, 342 int vertical, 343 int limit) 344 { 345 chtype save = dlg_get_attrs(win); 346 int step = 0; 347 int length; 348 int longest; 349 int final_x; 350 int final_y; 351 int gap; 352 int margin; 353 size_t need; 354 355 dlg_mouse_setbase(getbegx(win), getbegy(win)); 356 357 getyx(win, final_y, final_x); 358 359 dlg_button_sizes(labels, vertical, &longest, &length); 360 361 if (vertical) { 362 y += 1; 363 step = 1; 364 } else { 365 dlg_button_x_step(labels, limit, &gap, &margin, &step); 366 x += margin; 367 } 368 369 /* 370 * Allocate a buffer big enough for any label. 371 */ 372 need = (size_t) longest; 373 if (need != 0) { 374 char *buffer; 375 int n; 376 int *hotkeys = get_hotkeys(labels); 377 378 assert_ptr(hotkeys, "dlg_draw_buttons"); 379 380 for (n = 0; labels[n] != 0; ++n) { 381 need += strlen(labels[n]) + 1; 382 } 383 buffer = dlg_malloc(char, need); 384 assert_ptr(buffer, "dlg_draw_buttons"); 385 386 /* 387 * Draw the labels. 388 */ 389 for (n = 0; labels[n] != 0; n++) { 390 center_label(buffer, longest, labels[n]); 391 mouse_mkbutton(y, x, dlg_count_columns(buffer), n); 392 print_button(win, buffer, 393 CHR_BUTTON ? hotkeys[n] : -1, 394 y, x, 395 (selected == n) || (n == 0 && selected < 0)); 396 if (selected == n) 397 getyx(win, final_y, final_x); 398 399 if (vertical) { 400 if ((y += step) > limit) 401 break; 402 } else { 403 if ((x += step) > limit) 404 break; 405 } 406 } 407 (void) wmove(win, final_y, final_x); 408 wrefresh(win); 409 dlg_attrset(win, save); 410 free(buffer); 411 free(hotkeys); 412 } 413 } 414 415 /* 416 * Match a given character against the beginning of the string, ignoring case 417 * of the given character. The matching string must begin with an uppercase 418 * character. 419 */ 420 int 421 dlg_match_char(int ch, const char *string) 422 { 423 if (string != 0) { 424 int cmp2 = string_to_char(&string); 425 #ifdef USE_WIDE_CURSES 426 wint_t cmp1 = dlg_toupper(ch); 427 if (cmp2 != 0 && (wchar_t) cmp1 == (wchar_t) dlg_toupper(cmp2)) { 428 return TRUE; 429 } 430 #else 431 if (ch > 0 && ch < 256) { 432 if (dlg_toupper(ch) == dlg_toupper(cmp2)) 433 return TRUE; 434 } 435 #endif 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 cmp = string_to_char(&label); 451 if (dlg_isupper(cmp)) { 452 break; 453 } 454 } 455 return cmp; 456 } 457 458 /* 459 * Given a list of button labels, and a character which may be the abbreviation 460 * for one, find it, if it exists. An abbreviation will be the first character 461 * which happens to be capitalized in the label. 462 */ 463 int 464 dlg_char_to_button(int ch, const char **labels) 465 { 466 int result = DLG_EXIT_UNKNOWN; 467 468 if (labels != 0) { 469 int *hotkeys = get_hotkeys(labels); 470 471 ch = (int) dlg_toupper(dlg_last_getc()); 472 473 if (hotkeys != 0) { 474 int j; 475 476 for (j = 0; labels[j] != 0; ++j) { 477 if (ch == hotkeys[j]) { 478 dlg_flush_getc(); 479 result = j; 480 break; 481 } 482 } 483 free(hotkeys); 484 } 485 } 486 487 return result; 488 } 489 490 static const char * 491 my_yes_label(void) 492 { 493 return (dialog_vars.yes_label != NULL) 494 ? dialog_vars.yes_label 495 : _("Yes"); 496 } 497 498 static const char * 499 my_no_label(void) 500 { 501 return (dialog_vars.no_label != NULL) 502 ? dialog_vars.no_label 503 : _("No"); 504 } 505 506 static const char * 507 my_ok_label(void) 508 { 509 return (dialog_vars.ok_label != NULL) 510 ? dialog_vars.ok_label 511 : _("OK"); 512 } 513 514 static const char * 515 my_cancel_label(void) 516 { 517 return (dialog_vars.cancel_label != NULL) 518 ? dialog_vars.cancel_label 519 : _("Cancel"); 520 } 521 522 static const char * 523 my_exit_label(void) 524 { 525 return (dialog_vars.exit_label != NULL) 526 ? dialog_vars.exit_label 527 : _("EXIT"); 528 } 529 530 static const char * 531 my_extra_label(void) 532 { 533 return (dialog_vars.extra_label != NULL) 534 ? dialog_vars.extra_label 535 : _("Extra"); 536 } 537 538 static const char * 539 my_help_label(void) 540 { 541 return (dialog_vars.help_label != NULL) 542 ? dialog_vars.help_label 543 : _("Help"); 544 } 545 546 /* 547 * Return a list of button labels. 548 */ 549 const char ** 550 dlg_exit_label(void) 551 { 552 const char **result; 553 DIALOG_VARS save; 554 555 if (dialog_vars.extra_button) { 556 dlg_save_vars(&save); 557 dialog_vars.nocancel = TRUE; 558 result = dlg_ok_labels(); 559 dlg_restore_vars(&save); 560 } else { 561 static const char *labels[3]; 562 int n = 0; 563 564 if (!dialog_vars.nook) 565 labels[n++] = my_exit_label(); 566 if (dialog_vars.help_button) 567 labels[n++] = my_help_label(); 568 if (n == 0) 569 labels[n++] = my_exit_label(); 570 labels[n] = 0; 571 572 result = labels; 573 } 574 return result; 575 } 576 577 /* 578 * Map the given button index for dlg_exit_label() into our exit-code. 579 */ 580 int 581 dlg_exit_buttoncode(int button) 582 { 583 int result; 584 DIALOG_VARS save; 585 586 dlg_save_vars(&save); 587 dialog_vars.nocancel = TRUE; 588 589 result = dlg_ok_buttoncode(button); 590 591 dlg_restore_vars(&save); 592 593 return result; 594 } 595 596 const char ** 597 dlg_ok_label(void) 598 { 599 static const char *labels[4]; 600 int n = 0; 601 602 labels[n++] = my_ok_label(); 603 if (dialog_vars.extra_button) 604 labels[n++] = my_extra_label(); 605 if (dialog_vars.help_button) 606 labels[n++] = my_help_label(); 607 labels[n] = 0; 608 return labels; 609 } 610 611 /* 612 * Return a list of button labels for the OK/Cancel group. 613 */ 614 const char ** 615 dlg_ok_labels(void) 616 { 617 static const char *labels[5]; 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.nocancel) 625 labels[n++] = my_cancel_label(); 626 if (dialog_vars.help_button) 627 labels[n++] = my_help_label(); 628 labels[n] = 0; 629 return labels; 630 } 631 632 /* 633 * Map the given button index for dlg_ok_labels() into our exit-code 634 */ 635 int 636 dlg_ok_buttoncode(int button) 637 { 638 int result = DLG_EXIT_ERROR; 639 int n = !dialog_vars.nook; 640 641 if (!dialog_vars.nook && (button <= 0)) { 642 result = DLG_EXIT_OK; 643 } else if (dialog_vars.extra_button && (button == n++)) { 644 result = DLG_EXIT_EXTRA; 645 } else if (!dialog_vars.nocancel && (button == n++)) { 646 result = DLG_EXIT_CANCEL; 647 } else if (dialog_vars.help_button && (button == n)) { 648 result = DLG_EXIT_HELP; 649 } 650 DLG_TRACE(("# dlg_ok_buttoncode(%d) = %d:%s\n", 651 button, result, dlg_exitcode2s(result))); 652 return result; 653 } 654 655 /* 656 * Given that we're using dlg_ok_labels() to list buttons, find the next index 657 * in the list of buttons. The 'extra' parameter if negative provides a way to 658 * enumerate extra active areas on the widget. 659 */ 660 int 661 dlg_next_ok_buttonindex(int current, int extra) 662 { 663 int result = current + 1; 664 665 if (current >= 0 666 && dlg_ok_buttoncode(result) < 0) 667 result = extra; 668 return result; 669 } 670 671 /* 672 * Similarly, find the previous button index. 673 */ 674 int 675 dlg_prev_ok_buttonindex(int current, int extra) 676 { 677 int result = current - 1; 678 679 if (result < extra) { 680 for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) { 681 ; 682 } 683 } 684 return result; 685 } 686 687 /* 688 * Find the button-index for the "OK" or "Cancel" button, according to 689 * whether --defaultno is given. If --nocancel was given, we always return 690 * the index for the first button (usually "OK" unless --nook was used). 691 */ 692 int 693 dlg_defaultno_button(void) 694 { 695 int result = 0; 696 697 if (dialog_vars.defaultno && !dialog_vars.nocancel) { 698 while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL) 699 ++result; 700 } 701 DLG_TRACE(("# dlg_defaultno_button() = %d\n", result)); 702 return result; 703 } 704 705 /* 706 * Find the button-index for a button named with --default-button. If the 707 * option was not specified, or if the selected button does not exist, return 708 * the index of the first button (usually "OK" unless --nook was used). 709 */ 710 int 711 dlg_default_button(void) 712 { 713 int result = 0; 714 715 if (dialog_vars.default_button >= 0) { 716 int i, n; 717 718 for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) { 719 if (n == dialog_vars.default_button) { 720 result = i; 721 break; 722 } 723 } 724 } 725 DLG_TRACE(("# dlg_default_button() = %d\n", result)); 726 return result; 727 } 728 729 /* 730 * Return a list of buttons for Yes/No labels. 731 */ 732 const char ** 733 dlg_yes_labels(void) 734 { 735 const char **result; 736 737 if (dialog_vars.extra_button) { 738 result = dlg_ok_labels(); 739 } else { 740 static const char *labels[4]; 741 int n = 0; 742 743 labels[n++] = my_yes_label(); 744 labels[n++] = my_no_label(); 745 if (dialog_vars.help_button) 746 labels[n++] = my_help_label(); 747 labels[n] = 0; 748 749 result = labels; 750 } 751 752 return result; 753 } 754 755 /* 756 * Map the given button index for dlg_yes_labels() into our exit-code. 757 */ 758 int 759 dlg_yes_buttoncode(int button) 760 { 761 int result = DLG_EXIT_ERROR; 762 763 if (dialog_vars.extra_button) { 764 result = dlg_ok_buttoncode(button); 765 } else if (button == 0) { 766 result = DLG_EXIT_OK; 767 } else if (button == 1) { 768 result = DLG_EXIT_CANCEL; 769 } else if (button == 2 && dialog_vars.help_button) { 770 result = DLG_EXIT_HELP; 771 } 772 773 return result; 774 } 775 776 /* 777 * Return the next index in labels[]; 778 */ 779 int 780 dlg_next_button(const char **labels, int button) 781 { 782 if (button < -1) 783 button = -1; 784 785 if (labels[button + 1] != 0) { 786 ++button; 787 } else { 788 button = MIN_BUTTON; 789 } 790 return button; 791 } 792 793 /* 794 * Return the previous index in labels[]; 795 */ 796 int 797 dlg_prev_button(const char **labels, int button) 798 { 799 if (button > MIN_BUTTON) { 800 --button; 801 } else { 802 if (button < -1) 803 button = -1; 804 805 while (labels[button + 1] != 0) 806 ++button; 807 } 808 return button; 809 } 810