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