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