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