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